From e6d358a18d24f7e6d46bb60947aba3793ae1b090 Mon Sep 17 00:00:00 2001 From: sid sri Date: Fri, 24 Jul 2026 19:02:07 +0530 Subject: [PATCH 1/3] fix: parse named tuple elements --- .../unit/parse_column_types_tuple.test.ts | 34 +++++++++++++++++++ .../client-common/src/parse/column_types.ts | 28 ++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/packages/client-common/__tests__/unit/parse_column_types_tuple.test.ts b/packages/client-common/__tests__/unit/parse_column_types_tuple.test.ts index 171064a15..8da77082c 100644 --- a/packages/client-common/__tests__/unit/parse_column_types_tuple.test.ts +++ b/packages/client-common/__tests__/unit/parse_column_types_tuple.test.ts @@ -44,6 +44,40 @@ describe("Columns types parser - Tuple", () => { }); }); + it("should parse Tuple with named elements", async () => { + const args: TestArgs[] = [ + { + sourceType: "Tuple(s String, i Int64)", + expected: { + type: "Tuple", + elements: [ + { type: "Simple", columnType: "String", sourceType: "String" }, + { type: "Simple", columnType: "Int64", sourceType: "Int64" }, + ], + sourceType: "Tuple(s String, i Int64)", + }, + }, + { + sourceType: 'Tuple(`display name` String, "item count" UInt64)', + expected: { + type: "Tuple", + elements: [ + { type: "Simple", columnType: "String", sourceType: "String" }, + { type: "Simple", columnType: "UInt64", sourceType: "UInt64" }, + ], + sourceType: 'Tuple(`display name` String, "item count" UInt64)', + }, + }, + ]; + args.forEach(({ expected, sourceType }) => { + const result = parseTupleType({ columnType: sourceType, sourceType }); + expect( + result, + `Expected ${sourceType} to have ${joinElements(expected)} elements`, + ).toEqual(expected); + }); + }); + it("should parse Tuple with Decimals", async () => { const args: TestArgs[] = [ { diff --git a/packages/client-common/src/parse/column_types.ts b/packages/client-common/src/parse/column_types.ts index f5de59d98..321f75242 100644 --- a/packages/client-common/src/parse/column_types.ts +++ b/packages/client-common/src/parse/column_types.ts @@ -542,7 +542,7 @@ export function parseTupleType({ } columnType = columnType.slice(TuplePrefix.length, -1); const elements = getElementsTypes({ columnType, sourceType }, 1).map((type) => - parseColumnType(type), + parseTupleElementType(type), ); return { type: "Tuple", @@ -551,6 +551,29 @@ export function parseTupleType({ }; } +function parseTupleElementType(sourceType: string): ParsedColumnType { + try { + return parseColumnType(sourceType); + } catch (originalError) { + if (!(originalError instanceof ColumnTypeParseError)) { + throw originalError; + } + const namedElementType = NamedTupleElementPattern.exec(sourceType)?.[1]; + if (namedElementType === undefined) { + throw originalError; + } + + try { + return parseColumnType(namedElementType); + } catch (namedElementError) { + if (!(namedElementError instanceof ColumnTypeParseError)) { + throw namedElementError; + } + throw originalError; + } + } +} + export function parseArrayType({ columnType, sourceType, @@ -795,6 +818,9 @@ const DateTimePrefix = "DateTime" as const; const DateTimeWithTimezonePrefix = "DateTime(" as const; const DateTime64Prefix = "DateTime64(" as const; const FixedStringPrefix = "FixedString(" as const; +// Tuple element names may be bare or quoted with backticks/double quotes. +const NamedTupleElementPattern = + /^(?:`(?:\\.|``|[^`])*`|"(?:\\.|""|[^"])*"|\S+)\s+(.+)$/; const SingleQuoteASCII = 39 as const; const LeftParenASCII = 40 as const; From 1d61fe4c819500b8ed58249e5cb5c4992aba5198 Mon Sep 17 00:00:00 2001 From: sid sri Date: Fri, 24 Jul 2026 19:06:00 +0530 Subject: [PATCH 2/3] docs: add named tuple parser changelog --- packages/client-node/CHANGELOG.md | 2 ++ packages/client-web/CHANGELOG.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/client-node/CHANGELOG.md b/packages/client-node/CHANGELOG.md index 435f9b73f..e95a9a3ea 100644 --- a/packages/client-node/CHANGELOG.md +++ b/packages/client-node/CHANGELOG.md @@ -15,8 +15,10 @@ ## Bug fixes - Fixed `Array(Date)` / `Array(Date32)` query-parameter binding (and other temporal element types nested in arrays, tuples, and maps). A JS `Date` inside a container was serialized as a bare Unix timestamp (e.g. `[1683244800]`), which the server's `Array(Date)` element parser rejects (`CANNOT_PARSE_INPUT_ASSERTION_FAILED`). Container-nested `Date` values are now emitted as a quoted UTC date string (e.g. `['2023-05-05']`), the one encoding every temporal element type accepts. Note: a `Date` used inside `Array(DateTime)` / `Array(DateTime64)` is now bound at day precision (the time-of-day is dropped), since date-only is the only form `Array(Date)` accepts; scalar `Date` / `DateTime` binding is unchanged. ([#947]) +- Fixed the deprecated `parseColumnType` function to parse named Tuple elements (for example, `Tuple(name String, count UInt64)`) instead of throwing `Unsupported column type`. Element names remain omitted from the legacy `ParsedColumnTuple` output; use `@clickhouse/datatype-parser` when names are needed. ([#964]) [#947]: https://github.com/ClickHouse/clickhouse-js/pull/947 +[#964]: https://github.com/ClickHouse/clickhouse-js/pull/964 # 1.23.1 diff --git a/packages/client-web/CHANGELOG.md b/packages/client-web/CHANGELOG.md index 12fb839c6..e4932a93c 100644 --- a/packages/client-web/CHANGELOG.md +++ b/packages/client-web/CHANGELOG.md @@ -15,8 +15,10 @@ ## Bug fixes - Fixed `Array(Date)` / `Array(Date32)` query-parameter binding (and other temporal element types nested in arrays, tuples, and maps). A JS `Date` inside a container was serialized as a bare Unix timestamp (e.g. `[1683244800]`), which the server's `Array(Date)` element parser rejects (`CANNOT_PARSE_INPUT_ASSERTION_FAILED`). Container-nested `Date` values are now emitted as a quoted UTC date string (e.g. `['2023-05-05']`), the one encoding every temporal element type accepts. Note: a `Date` used inside `Array(DateTime)` / `Array(DateTime64)` is now bound at day precision (the time-of-day is dropped), since date-only is the only form `Array(Date)` accepts; scalar `Date` / `DateTime` binding is unchanged. ([#947]) +- Fixed the deprecated `parseColumnType` function to parse named Tuple elements (for example, `Tuple(name String, count UInt64)`) instead of throwing `Unsupported column type`. Element names remain omitted from the legacy `ParsedColumnTuple` output; use `@clickhouse/datatype-parser` when names are needed. ([#964]) [#947]: https://github.com/ClickHouse/clickhouse-js/pull/947 +[#964]: https://github.com/ClickHouse/clickhouse-js/pull/964 # 1.23.1 From e81959efdc3f8c79b12c7e0af38f230095645d3a Mon Sep 17 00:00:00 2001 From: sid sri Date: Sat, 25 Jul 2026 15:28:42 +0530 Subject: [PATCH 3/3] refactor: clarify tuple element parsing --- packages/client-common/src/parse/column_types.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/client-common/src/parse/column_types.ts b/packages/client-common/src/parse/column_types.ts index 321f75242..643b7923a 100644 --- a/packages/client-common/src/parse/column_types.ts +++ b/packages/client-common/src/parse/column_types.ts @@ -551,14 +551,15 @@ export function parseTupleType({ }; } -function parseTupleElementType(sourceType: string): ParsedColumnType { +function parseTupleElementType(elementSourceType: string): ParsedColumnType { try { - return parseColumnType(sourceType); + return parseColumnType(elementSourceType); } catch (originalError) { if (!(originalError instanceof ColumnTypeParseError)) { throw originalError; } - const namedElementType = NamedTupleElementPattern.exec(sourceType)?.[1]; + const namedElementType = + NamedTupleElementPattern.exec(elementSourceType)?.[1]; if (namedElementType === undefined) { throw originalError; }