From d28a184416a503f8749ddeeae840b04e886249d5 Mon Sep 17 00:00:00 2001 From: sid sri Date: Thu, 20 Aug 2026 21:53:41 +0530 Subject: [PATCH] fix(rowbinary): support compiling empty Tuple() type AST When a type string contains an empty Tuple (such as Tuple() or Tuple(Tuple())), the datatype parser returns a DataType node with name: 'Tuple' and empty arguments rather than a TupleDataType node. Without a case in dataTypeReader, folding the AST threw a RowBinaryTypeError: unsupported RowBinary type: Tuple. This adds case 'Tuple' to dataTypeReader and ensures tupleReader only treats tuples as named when names.length > 0. --- .../src/readers/compile.ts | 6 +++++- .../tests/compile.test.ts | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/skills/clickhouse-js-node-rowbinary/src/readers/compile.ts b/skills/clickhouse-js-node-rowbinary/src/readers/compile.ts index 9f3861be..5a3dd099 100644 --- a/skills/clickhouse-js-node-rowbinary/src/readers/compile.ts +++ b/skills/clickhouse-js-node-rowbinary/src/readers/compile.ts @@ -162,6 +162,8 @@ function dataTypeReader(node: Node): Reader { return variantReader(node); case "Nested": return nestedReader(node); + case "Tuple": + return tupleReader(node); // --- parameterized scalars --- case "FixedString": @@ -211,7 +213,9 @@ function tupleReader(node: Node): Reader { const readers = node.arguments.map(astToReader); const names = node.element_names; const named = - names.length === readers.length && names.every((n) => n.length > 0); + names.length > 0 && + names.length === readers.length && + names.every((n) => n.length > 0); if (named) { const fields: Record> = {}; for (let i = 0; i < names.length; i++) fields[names[i]!] = readers[i]!; diff --git a/skills/clickhouse-js-node-rowbinary/tests/compile.test.ts b/skills/clickhouse-js-node-rowbinary/tests/compile.test.ts index 475cec9e..6f48516a 100644 --- a/skills/clickhouse-js-node-rowbinary/tests/compile.test.ts +++ b/skills/clickhouse-js-node-rowbinary/tests/compile.test.ts @@ -111,4 +111,25 @@ describe("astToReader (AST -> Reader fold)", () => { /cannot build a column reader/, ); }); + + it("folds an empty Tuple to an empty array reader", () => { + const read = reader("Tuple()"); + const s = new Cursor(Buffer.alloc(0)); + expect(read(s)).toEqual([]); + expect(s.pos).toBe(0); + }); + + it("folds a nested empty Tuple", () => { + const read = reader("Tuple(Tuple())"); + const s = new Cursor(Buffer.alloc(0)); + expect(read(s)).toEqual([[]]); + expect(s.pos).toBe(0); + }); + + it("folds an empty Tuple alongside other columns", () => { + const read = reader("Tuple(Tuple(), UInt8)"); + const s = new Cursor(Buffer.from([0x05])); + expect(read(s)).toEqual([[], 5]); + expect(s.pos).toBe(1); + }); });