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..643b7923a 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,30 @@ export function parseTupleType({ }; } +function parseTupleElementType(elementSourceType: string): ParsedColumnType { + try { + return parseColumnType(elementSourceType); + } catch (originalError) { + if (!(originalError instanceof ColumnTypeParseError)) { + throw originalError; + } + const namedElementType = + NamedTupleElementPattern.exec(elementSourceType)?.[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 +819,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; 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