@clickhouse/client 1.23.0-head.fae5998.1 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +1342 -0
  2. package/README.md +18 -6
  3. package/dist/common/index.d.ts +2 -2
  4. package/dist/common/index.js +2 -2
  5. package/dist/common/index.js.map +1 -1
  6. package/dist/common/parse/column_types.d.ts +30 -2
  7. package/dist/common/parse/column_types.js +8 -0
  8. package/dist/common/parse/column_types.js.map +1 -1
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +2 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/version.d.ts +1 -1
  13. package/dist/version.js +1 -1
  14. package/dist/version.js.map +1 -1
  15. package/package.json +7 -6
  16. package/skills/AGENTS.md +8 -0
  17. package/skills/clickhouse-js-node-rowbinary/AGENTS.md +44 -0
  18. package/skills/clickhouse-js-node-rowbinary/CHANGELOG.md +49 -0
  19. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/README.md +85 -14
  20. package/skills/clickhouse-js-node-rowbinary/SKILL.md +111 -0
  21. package/skills/{clickhouse-js-node-rowbinary-parser/SKILL.md → clickhouse-js-node-rowbinary/reader.md} +59 -123
  22. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/carts.ts +9 -5
  23. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/events.ts +5 -5
  24. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/iot.ts +4 -4
  25. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/ledger.ts +3 -3
  26. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/logs.ts +4 -4
  27. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/observability.ts +9 -10
  28. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/orders.ts +10 -9
  29. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/profiles.ts +5 -5
  30. package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/telemetry.ts +6 -6
  31. package/skills/clickhouse-js-node-rowbinary/src/readers/compile.ts +328 -0
  32. package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/dynamic.ts +12 -8
  33. package/skills/clickhouse-js-node-rowbinary/src/readers/enums.ts +40 -0
  34. package/skills/clickhouse-js-node-rowbinary/src/readers/header.ts +29 -0
  35. package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/reader.ts +17 -0
  36. package/skills/clickhouse-js-node-rowbinary/src/readers/rowBinaryWithNamesAndTypes.ts +155 -0
  37. package/skills/clickhouse-js-node-rowbinary/src/writers/aggregateFunction.ts +18 -0
  38. package/skills/clickhouse-js-node-rowbinary/src/writers/bool.ts +10 -0
  39. package/skills/clickhouse-js-node-rowbinary/src/writers/composite.ts +140 -0
  40. package/skills/clickhouse-js-node-rowbinary/src/writers/core.ts +92 -0
  41. package/skills/clickhouse-js-node-rowbinary/src/writers/datetime.ts +123 -0
  42. package/skills/clickhouse-js-node-rowbinary/src/writers/decimals.ts +51 -0
  43. package/skills/clickhouse-js-node-rowbinary/src/writers/enums.ts +18 -0
  44. package/skills/clickhouse-js-node-rowbinary/src/writers/floats.ts +40 -0
  45. package/skills/clickhouse-js-node-rowbinary/src/writers/geo.ts +125 -0
  46. package/skills/clickhouse-js-node-rowbinary/src/writers/integers.ts +90 -0
  47. package/skills/clickhouse-js-node-rowbinary/src/writers/interval.ts +11 -0
  48. package/skills/clickhouse-js-node-rowbinary/src/writers/ip.ts +121 -0
  49. package/skills/clickhouse-js-node-rowbinary/src/writers/lowCardinality.ts +12 -0
  50. package/skills/clickhouse-js-node-rowbinary/src/writers/nested.ts +17 -0
  51. package/skills/clickhouse-js-node-rowbinary/src/writers/nothing.ts +21 -0
  52. package/skills/clickhouse-js-node-rowbinary/src/writers/rows.ts +144 -0
  53. package/skills/clickhouse-js-node-rowbinary/src/writers/simpleAggregateFunction.ts +12 -0
  54. package/skills/clickhouse-js-node-rowbinary/src/writers/strings.ts +77 -0
  55. package/skills/clickhouse-js-node-rowbinary/src/writers/time.ts +54 -0
  56. package/skills/clickhouse-js-node-rowbinary/src/writers/uuid.ts +60 -0
  57. package/skills/clickhouse-js-node-rowbinary/src/writers/varint.ts +64 -0
  58. package/skills/clickhouse-js-node-rowbinary/src/writers/writer.ts +101 -0
  59. package/skills/clickhouse-js-node-rowbinary/writer.md +96 -0
  60. package/skills/clickhouse-js-node-rowbinary-parser/src/enums.ts +0 -28
  61. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/EXAMPLES.md +0 -0
  62. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/iot-rowbinary-vs-json.md +0 -0
  63. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/ledger-rowbinary-vs-json.md +0 -0
  64. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/logs-json-wins.md +0 -0
  65. /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/wasm-vs-js.md +0 -0
  66. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/aggregateFunction.ts +0 -0
  67. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/bool.ts +0 -0
  68. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/columnar.ts +0 -0
  69. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/composite.ts +0 -0
  70. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/core.ts +0 -0
  71. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/datetime.ts +0 -0
  72. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/decimals.ts +0 -0
  73. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/floats.ts +0 -0
  74. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/geo.ts +0 -0
  75. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/integers.ts +0 -0
  76. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/interval.ts +0 -0
  77. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/ip.ts +0 -0
  78. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/json.ts +0 -0
  79. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/lowCardinality.ts +0 -0
  80. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/nested.ts +0 -0
  81. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/nothing.ts +0 -0
  82. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/rows.ts +0 -0
  83. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/simpleAggregateFunction.ts +0 -0
  84. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/stream.ts +0 -0
  85. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/strings.ts +0 -0
  86. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/time.ts +0 -0
  87. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/uuid.ts +0 -0
  88. /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/varint.ts +0 -0
@@ -1,8 +1,7 @@
1
- import { type Reader, advance } from "../core.js";
2
- import { type DecimalValue, readDecimal64 } from "../decimals.js";
3
- import { readEnum8 } from "../enums.js";
4
- import { readUInt8 } from "../integers.js";
5
- import { formatUUID, formatUUIDTable, readUUID } from "../uuid.js";
1
+ import { type Reader, advance } from "../readers/core.js";
2
+ import { type DecimalValue, readDecimal64 } from "../readers/decimals.js";
3
+ import { readInt8, readUInt8 } from "../readers/integers.js";
4
+ import { formatUUID, formatUUIDTable, readUUID } from "../readers/uuid.js";
6
5
 
7
6
  /**
8
7
  * Example: an orders table — UUID, Decimal, and Enum (awkward-as-JSON types).
@@ -15,9 +14,11 @@ import { formatUUID, formatUUIDTable, readUUID } from "../uuid.js";
15
14
  *
16
15
  * Shows the parse/format split and faithful values: `uid` is read as raw bytes
17
16
  * then formatted with `formatUUID`; `price` stays the exact `[unscaled, scale]`
18
- * pair (`[1234n, 2]` == 12.34), not a lossy float; `status` decodes to the
19
- * underlying `Int8` value (1/2/3), the name<->value map being type metadata, not
20
- * on the wire. The declared scale `2` is baked into `readDecimal64(2)`.
17
+ * pair (`[1234n, 2]` == 12.34), not a lossy float; `status` is read as the raw
18
+ * underlying `Int8` (1/2/3) with `readInt8` a hand-written reader that bakes
19
+ * in the schema can skip name resolution, whereas the generic `readEnum8(map)`
20
+ * (used by the dynamic/header path) resolves the value to its name. The declared
21
+ * scale `2` is baked into `readDecimal64(2)`.
21
22
  */
22
23
  export type OrderRow = {
23
24
  id: number;
@@ -30,7 +31,7 @@ export const readOrderRow: Reader<OrderRow> = (s) => ({
30
31
  id: readUInt8(s),
31
32
  uid: formatUUID(readUUID(s)),
32
33
  price: readDecimal64(2)(s),
33
- status: readEnum8(s),
34
+ status: readInt8(s),
34
35
  });
35
36
 
36
37
  /**
@@ -1,8 +1,8 @@
1
- import { readArray, readNullable } from "../composite.js";
2
- import { type Reader, advance } from "../core.js";
3
- import { readInt32, readUInt32 } from "../integers.js";
4
- import { readString } from "../strings.js";
5
- import { readUVarint } from "../varint.js";
1
+ import { readArray, readNullable } from "../readers/composite.js";
2
+ import { type Reader, advance } from "../readers/core.js";
3
+ import { readInt32, readUInt32 } from "../readers/integers.js";
4
+ import { readString } from "../readers/strings.js";
5
+ import { readUVarint } from "../readers/varint.js";
6
6
 
7
7
  /**
8
8
  * Example: a profiles table — Array and Nullable wrappers.
@@ -3,12 +3,12 @@ import {
3
3
  readMap,
4
4
  readNullable,
5
5
  readTupleNamed,
6
- } from "../composite.js";
7
- import { type Reader, advance } from "../core.js";
8
- import { readFloat64 } from "../floats.js";
9
- import { readUInt16, readUInt32 } from "../integers.js";
10
- import { readString } from "../strings.js";
11
- import { readUVarint } from "../varint.js";
6
+ } from "../readers/composite.js";
7
+ import { type Reader, advance } from "../readers/core.js";
8
+ import { readFloat64 } from "../readers/floats.js";
9
+ import { readUInt16, readUInt32 } from "../readers/integers.js";
10
+ import { readString } from "../readers/strings.js";
11
+ import { readUVarint } from "../readers/varint.js";
12
12
 
13
13
  /**
14
14
  * Example: a telemetry table — composite readers that nest.
@@ -0,0 +1,328 @@
1
+ /**
2
+ * The fold: turn a parsed ClickHouse data-type AST (from
3
+ * `@clickhouse/datatype-parser`) into a RowBinary value {@link Reader}.
4
+ *
5
+ * AST in, reader out — nothing else. Reading the `RowBinaryWithNamesAndTypes`
6
+ * header, parsing type strings, and assembling a row reader live in
7
+ * `rowBinaryWithNamesAndTypes.ts`; this module is just the type-to-reader
8
+ * mapping.
9
+ *
10
+ * It composes the existing GENERIC curried combinators at runtime, exactly as a
11
+ * hand-written reader would — no code generation and no monomorphization (those
12
+ * are the deliberate next step; see the `MONOMORPHIZE` notes throughout the
13
+ * combinator modules). The shape mirrors {@link readDynamicType} in
14
+ * `dynamic.ts`, which performs the same "type → Reader" mapping but driven by
15
+ * ClickHouse's BINARY type encoding rather than the textual type's AST.
16
+ */
17
+
18
+ import { NodeKind, type Node } from "@clickhouse/datatype-parser";
19
+
20
+ import type { Reader } from "./core.js";
21
+ import {
22
+ readInt8,
23
+ readInt16,
24
+ readInt32,
25
+ readInt64,
26
+ readInt128,
27
+ readInt256,
28
+ readUInt8,
29
+ readUInt16,
30
+ readUInt32,
31
+ readUInt64,
32
+ readUInt128,
33
+ readUInt256,
34
+ } from "./integers.js";
35
+ import { readFloat32, readFloat64, readBFloat16 } from "./floats.js";
36
+ import { readBool } from "./bool.js";
37
+ import { readString, readFixedString } from "./strings.js";
38
+ import { readUUID } from "./uuid.js";
39
+ import { readIPv4, readIPv6 } from "./ip.js";
40
+ import {
41
+ readDate,
42
+ readDate32,
43
+ readDateTime,
44
+ readDateTime64,
45
+ } from "./datetime.js";
46
+ import { readTime, readTime64 } from "./time.js";
47
+ import { readInterval } from "./interval.js";
48
+ import {
49
+ readDecimal32,
50
+ readDecimal64,
51
+ readDecimal128,
52
+ readDecimal256,
53
+ } from "./decimals.js";
54
+ import { readEnum8, readEnum16 } from "./enums.js";
55
+ import {
56
+ readArray,
57
+ readMap,
58
+ readNullable,
59
+ readTuple,
60
+ readTupleNamed,
61
+ readVariant,
62
+ readQBit,
63
+ } from "./composite.js";
64
+ import { readLowCardinality } from "./lowCardinality.js";
65
+ import { readNested } from "./nested.js";
66
+ import { readNothing } from "./nothing.js";
67
+ import {
68
+ readPoint,
69
+ readRing,
70
+ readLineString,
71
+ readPolygon,
72
+ readMultiLineString,
73
+ readMultiPolygon,
74
+ readGeometry,
75
+ } from "./geo.js";
76
+ import { readJSON } from "./json.js";
77
+ import { readDynamic } from "./dynamic.js";
78
+
79
+ /**
80
+ * Thrown when a ClickHouse type cannot be turned into a RowBinary reader —
81
+ * either the type string did not parse, or the parsed type is unsupported or
82
+ * malformed for RowBinary decoding (`AggregateFunction`, a missing argument, a
83
+ * malformed `Nested(...)`, …).
84
+ *
85
+ * A dedicated class so callers can `catch (e) { if (e instanceof
86
+ * RowBinaryTypeError) … }` and branch on a bad-type error specifically rather
87
+ * than string-matching a generic `Error`. Note the standalone
88
+ * `@clickhouse/datatype-parser` is itself NON-throwing (it returns a
89
+ * `ParseResult`); this is the error the compile layer raises on top of it.
90
+ */
91
+ export class RowBinaryTypeError extends Error {
92
+ /** The full ClickHouse type string being compiled, when the throw site knows it. */
93
+ readonly typeString?: string;
94
+ /**
95
+ * Byte offset into `typeString` where parsing stopped — set for PARSE
96
+ * failures (from the underlying parser), undefined for fold-time errors.
97
+ */
98
+ readonly position?: number;
99
+
100
+ constructor(
101
+ message: string,
102
+ options: { typeString?: string; position?: number } = {},
103
+ ) {
104
+ super(message);
105
+ this.name = "RowBinaryTypeError";
106
+ this.typeString = options.typeString;
107
+ this.position = options.position;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * The fold itself: turn a parsed type-AST node into a value {@link Reader},
113
+ * recursing into element / key / field types for composites. The shape mirrors
114
+ * the server's `EXPLAIN AST` data-type subtree (see the parser's `ast.ts`).
115
+ */
116
+ export function astToReader(node: Node): Reader<unknown> {
117
+ switch (node.kind) {
118
+ case NodeKind.EnumDataType: {
119
+ // Explicit-value enum: the wire value is the underlying int, which we
120
+ // resolve to its NAME via the `'name' = value` pairs the type carries.
121
+ const map = enumNameMap(node);
122
+ return node.name === "Enum16" ? readEnum16(map) : readEnum8(map);
123
+ }
124
+ case NodeKind.TupleDataType:
125
+ return tupleReader(node);
126
+ case NodeKind.DataType:
127
+ return dataTypeReader(node);
128
+ default:
129
+ // Literal / Function / Identifier / NameTypePair are argument nodes,
130
+ // consumed by their parent — never a standalone column type.
131
+ throw new RowBinaryTypeError(
132
+ `cannot build a column reader for a ${node.kind} node (${node.name || "?"})`,
133
+ );
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Fold a generic `DataType` node (`name` + optional `arguments`) — the bulk of
139
+ * the type system: scalars, the parameterized types, and the
140
+ * wrappers/composites that recurse through {@link astToReader}.
141
+ */
142
+ function dataTypeReader(node: Node): Reader<unknown> {
143
+ switch (node.name) {
144
+ // --- wrappers & composites (recurse into argument types) ---
145
+ case "Nullable":
146
+ return readNullable(astToReader(requireArg(node, 0)));
147
+ case "LowCardinality":
148
+ // Transparent in RowBinary: just the inner type's reader.
149
+ return readLowCardinality(astToReader(requireArg(node, 0)));
150
+ case "Array":
151
+ return readArray(astToReader(requireArg(node, 0)));
152
+ case "QBit":
153
+ // QBit(element_type, dimension): wire-identical to Array(element_type);
154
+ // the dimension (arg 1) is metadata, not in the value stream.
155
+ return readQBit(astToReader(requireArg(node, 0)));
156
+ case "Map":
157
+ return readMap(
158
+ astToReader(requireArg(node, 0)),
159
+ astToReader(requireArg(node, 1)),
160
+ );
161
+ case "Variant":
162
+ return variantReader(node);
163
+ case "Nested":
164
+ return nestedReader(node);
165
+
166
+ // --- parameterized scalars ---
167
+ case "FixedString":
168
+ return readFixedString(literalInt(requireArg(node, 0)));
169
+ case "DateTime":
170
+ case "DateTime32":
171
+ // An optional timezone argument is metadata; the value wire is the same.
172
+ return readDateTime;
173
+ case "DateTime64":
174
+ // DateTime64(P [, 'tz']); default precision 3 if somehow omitted.
175
+ return readDateTime64(
176
+ node.arguments.length > 0 ? literalInt(node.arguments[0]!) : 3,
177
+ );
178
+ case "Time64":
179
+ return readTime64(literalInt(requireArg(node, 0)));
180
+ case "Decimal":
181
+ return decimalReader(node);
182
+ case "Decimal32":
183
+ return readDecimal32(literalInt(requireArg(node, 0)));
184
+ case "Decimal64":
185
+ return readDecimal64(literalInt(requireArg(node, 0)));
186
+ case "Decimal128":
187
+ return readDecimal128(literalInt(requireArg(node, 0)));
188
+ case "Decimal256":
189
+ return readDecimal256(literalInt(requireArg(node, 0)));
190
+ // A bare `Enum8`/`Enum16` (no explicit values — only reachable from a
191
+ // hand-written type string) has no names to resolve, so the reader falls
192
+ // back to the stringified underlying int.
193
+ case "Enum8":
194
+ return readEnum8(new Map());
195
+ case "Enum16":
196
+ return readEnum16(new Map());
197
+
198
+ default: {
199
+ // Interval<Unit> (IntervalSecond, IntervalDay, …): all decode to the
200
+ // signed Int64 count; the unit lives in the type name, not the wire.
201
+ if (node.name.startsWith("Interval")) return readInterval;
202
+ const leaf = NULLARY[node.name];
203
+ if (leaf !== undefined) return leaf;
204
+ throw new RowBinaryTypeError(`unsupported RowBinary type: ${node.name}`);
205
+ }
206
+ }
207
+ }
208
+
209
+ /** Folds a `Tuple(...)` — named (object) when every element is named, else positional (array). */
210
+ function tupleReader(node: Node): Reader<unknown> {
211
+ const readers = node.arguments.map(astToReader);
212
+ const names = node.element_names;
213
+ const named =
214
+ names.length === readers.length && names.every((n) => n.length > 0);
215
+ if (named) {
216
+ const fields: Record<string, Reader<unknown>> = {};
217
+ for (let i = 0; i < names.length; i++) fields[names[i]!] = readers[i]!;
218
+ return readTupleNamed(fields);
219
+ }
220
+ return readTuple(readers);
221
+ }
222
+
223
+ /**
224
+ * Folds a `Variant(...)`. The 1-byte discriminant indexes the alternatives
225
+ * sorted by type NAME (ClickHouse's global ordering), and the server writes the
226
+ * type string with alternatives ALREADY in that sorted order — so for a
227
+ * header-sourced type the AST argument order is the discriminant order and we
228
+ * pass them straight through. (A hand-written, non-normalized `Variant(...)`
229
+ * string would need sorting first.)
230
+ */
231
+ function variantReader(node: Node): Reader<unknown> {
232
+ return readVariant(node.arguments.map(astToReader));
233
+ }
234
+
235
+ /**
236
+ * Folds a `Nested(name Type, …)`: on the wire it IS `Array(Tuple(...))` with
237
+ * the field names, so we compose {@link readNested} (= `readArray(readTupleNamed)`)
238
+ * over the `NameTypePair` children.
239
+ */
240
+ function nestedReader(node: Node): Reader<unknown> {
241
+ const fields: Record<string, Reader<unknown>> = {};
242
+ for (const child of node.arguments) {
243
+ if (child.kind !== NodeKind.NameTypePair || child.data_type === null) {
244
+ throw new RowBinaryTypeError(
245
+ "malformed Nested(...): expected name/type pairs",
246
+ );
247
+ }
248
+ fields[child.name] = astToReader(child.data_type);
249
+ }
250
+ return readNested(fields);
251
+ }
252
+
253
+ /** Builds an enum's underlying-int -> name lookup from its `'name' = value` pairs. */
254
+ function enumNameMap(node: Node): ReadonlyMap<number, string> {
255
+ const map = new Map<number, string>();
256
+ for (const ev of node.values) map.set(Number(ev.value), ev.name);
257
+ return map;
258
+ }
259
+
260
+ /** Folds `Decimal(P, S)` to the right width by precision P; scale S drives decoding. */
261
+ function decimalReader(node: Node): Reader<unknown> {
262
+ const precision = literalInt(requireArg(node, 0));
263
+ const scale = literalInt(requireArg(node, 1));
264
+ if (precision <= 9) return readDecimal32(scale);
265
+ if (precision <= 18) return readDecimal64(scale);
266
+ if (precision <= 38) return readDecimal128(scale);
267
+ return readDecimal256(scale);
268
+ }
269
+
270
+ /** The nullary (no-argument) scalar types, mapped to their leaf readers. */
271
+ const NULLARY: Record<string, Reader<unknown>> = {
272
+ UInt8: readUInt8,
273
+ UInt16: readUInt16,
274
+ UInt32: readUInt32,
275
+ UInt64: readUInt64,
276
+ UInt128: readUInt128,
277
+ UInt256: readUInt256,
278
+ Int8: readInt8,
279
+ Int16: readInt16,
280
+ Int32: readInt32,
281
+ Int64: readInt64,
282
+ Int128: readInt128,
283
+ Int256: readInt256,
284
+ Float32: readFloat32,
285
+ Float64: readFloat64,
286
+ BFloat16: readBFloat16,
287
+ Bool: readBool,
288
+ String: readString,
289
+ UUID: readUUID,
290
+ IPv4: readIPv4,
291
+ IPv6: readIPv6,
292
+ Date: readDate,
293
+ Date32: readDate32,
294
+ Time: readTime,
295
+ Nothing: readNothing,
296
+ // Geo types.
297
+ Point: readPoint,
298
+ Ring: readRing,
299
+ LineString: readLineString,
300
+ Polygon: readPolygon,
301
+ MultiLineString: readMultiLineString,
302
+ MultiPolygon: readMultiPolygon,
303
+ Geometry: readGeometry as Reader<unknown>,
304
+ // Self-describing types: bare `JSON` / `Dynamic` (any args are metadata).
305
+ JSON: readJSON,
306
+ Dynamic: readDynamic,
307
+ };
308
+
309
+ /** Argument accessor that fails loudly instead of returning `undefined`. */
310
+ function requireArg(node: Node, index: number): Node {
311
+ const arg = node.arguments[index];
312
+ if (arg === undefined) {
313
+ throw new RowBinaryTypeError(
314
+ `type ${node.name} is missing argument ${index}`,
315
+ );
316
+ }
317
+ return arg;
318
+ }
319
+
320
+ /** Reads an integer out of a `Literal` argument node (e.g. the N in FixedString(N)). */
321
+ function literalInt(node: Node): number {
322
+ if (node.kind !== NodeKind.Literal) {
323
+ throw new RowBinaryTypeError(
324
+ `expected a literal argument, got a ${node.kind} node`,
325
+ );
326
+ }
327
+ return Number(node.value);
328
+ }
@@ -15,6 +15,7 @@ import {
15
15
  readUInt256,
16
16
  } from "./integers.js";
17
17
  import { readBool } from "./bool.js";
18
+ import { readEnum8, readEnum16 } from "./enums.js";
18
19
  import { readFloat32, readFloat64 } from "./floats.js";
19
20
  import { readString, readFixedString } from "./strings.js";
20
21
  import { readUUID } from "./uuid.js";
@@ -143,23 +144,26 @@ export function readDynamicType(state: Cursor): Reader<unknown> {
143
144
  return readString;
144
145
  case 0x16:
145
146
  return readFixedString(readUVarint(state));
146
- // Enum8 / Enum16: a count then (name String, value Int8/Int16) pairs. The
147
- // name<->value map is metadata; the stored value is the underlying int.
147
+ // Enum8 / Enum16: a count then (name String, value Int8/Int16) pairs. We
148
+ // collect them into the name<->value map so the value resolves to its name,
149
+ // matching the textual-type path in compile.ts.
148
150
  case 0x17: {
149
151
  const n = readUVarint(state);
152
+ const map = new Map<number, string>();
150
153
  for (let i = 0; i < n; i++) {
151
- readString(state);
152
- readInt8(state);
154
+ const name = readString(state);
155
+ map.set(readInt8(state), name);
153
156
  }
154
- return readInt8;
157
+ return readEnum8(map);
155
158
  }
156
159
  case 0x18: {
157
160
  const n = readUVarint(state);
161
+ const map = new Map<number, string>();
158
162
  for (let i = 0; i < n; i++) {
159
- readString(state);
160
- readInt16(state);
163
+ const name = readString(state);
164
+ map.set(readInt16(state), name);
161
165
  }
162
- return readInt16;
166
+ return readEnum16(map);
163
167
  }
164
168
  // Decimals: header carries precision P then scale S (both varint). Only S
165
169
  // matters for decoding; P is consumed and dropped. Returns [unscaled, S].
@@ -0,0 +1,40 @@
1
+ import { type Reader, advance } from "./core.js";
2
+
3
+ /**
4
+ * Maps an enum's underlying integer to its name. Built from the type's
5
+ * `'name' = value` pairs (which live in the column type, not the wire) by the
6
+ * compile step / dynamic decoder and handed to the readers below.
7
+ */
8
+ export type EnumNameMap = ReadonlyMap<number, string>;
9
+
10
+ /**
11
+ * Read an `Enum8` and resolve it to its NAME — the ergonomic default, since the
12
+ * point of an enum is usually the label, not the raw `Int8`.
13
+ *
14
+ * This is a factory: the name<->value map is metadata carried by the type, not
15
+ * the bytes, so it is supplied once (per column) and closed over. A value with
16
+ * no matching name falls back to its stringified integer rather than throwing,
17
+ * so a decode never blows up on an unexpected wire value.
18
+ *
19
+ * Need the raw underlying integer instead (e.g. a monomorphized hot path that
20
+ * has the schema baked in)? Read it directly with {@link readInt8} — that is the
21
+ * fast, allocation-free path the enum's name resolution sits on top of.
22
+ */
23
+ export function readEnum8(valueToName: EnumNameMap): Reader<string> {
24
+ return (state) =>
25
+ resolveName(valueToName, state.view.getInt8(advance(state, 1)));
26
+ }
27
+
28
+ /**
29
+ * Read an `Enum16` (2-byte underlying `Int16`) and resolve it to its NAME. See
30
+ * {@link readEnum8}; use {@link readInt16} for the raw underlying integer.
31
+ */
32
+ export function readEnum16(valueToName: EnumNameMap): Reader<string> {
33
+ return (state) =>
34
+ resolveName(valueToName, state.view.getInt16(advance(state, 2), true));
35
+ }
36
+
37
+ function resolveName(valueToName: EnumNameMap, value: number): string {
38
+ const name = valueToName.get(value);
39
+ return name !== undefined ? name : String(value);
40
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The `RowBinaryWithNamesAndTypes` HEADER: the column names and their type
3
+ * strings the server writes before the row data. Parsing the type strings into
4
+ * readers lives in `compile.ts`; this module is just the wire read.
5
+ */
6
+
7
+ import type { Cursor } from "./core.js";
8
+ import { readUVarint } from "./varint.js";
9
+ import { readString } from "./strings.js";
10
+
11
+ /** Column names and their type strings, in stream order. */
12
+ export interface RowBinaryHeader {
13
+ names: string[];
14
+ types: string[];
15
+ }
16
+
17
+ /**
18
+ * Read the `RowBinaryWithNamesAndTypes` header off the cursor: a LEB128 column
19
+ * count, then that many column-name `String`s, then that many type-string
20
+ * `String`s. Leaves the cursor at the first row's bytes.
21
+ */
22
+ export function readHeader(state: Cursor): RowBinaryHeader {
23
+ const count = readUVarint(state);
24
+ const names: string[] = new Array(count);
25
+ for (let i = 0; i < count; i++) names[i] = readString(state);
26
+ const types: string[] = new Array(count);
27
+ for (let i = 0; i < count; i++) types[i] = readString(state);
28
+ return { names, types };
29
+ }
@@ -24,6 +24,20 @@
24
24
  * lowCardinality (readLowCardinality), simpleAggregateFunction
25
25
  * (readSimpleAggregateFunction), nested (readNested), nothing (readNothing),
26
26
  * aggregateFunction (readAggregateFunction)
27
+ *
28
+ * Runtime schema path — compile a reader from the type STRINGS rather than
29
+ * hand-/code-generating one. Use this when the column types are not known until
30
+ * the response arrives (or you just want a generic decoder); for a fixed,
31
+ * known schema the specialized straight-line reader is faster.
32
+ * - header — readHeader: the RowBinaryWithNamesAndTypes preamble (column
33
+ * names + type strings) off the cursor
34
+ * - compile — astToReader: fold one parsed type AST (from
35
+ * `@clickhouse/datatype-parser`) into a value Reader. AST in,
36
+ * reader out — the type-to-combinator mapping, nothing else
37
+ * - rowBinaryWithNamesAndTypes — typeStringToReader (parse a type string +
38
+ * fold) and compileRowBinaryWithNamesAndTypes (read the header,
39
+ * compile every column, return a `readRows` driver for the rest of
40
+ * the stream): the end-to-end runtime entry point
27
41
  */
28
42
  export * from "./core.js";
29
43
  export * from "./varint.js";
@@ -49,3 +63,6 @@ export * from "./simpleAggregateFunction.js";
49
63
  export * from "./nested.js";
50
64
  export * from "./nothing.js";
51
65
  export * from "./aggregateFunction.js";
66
+ export * from "./header.js";
67
+ export * from "./compile.js";
68
+ export * from "./rowBinaryWithNamesAndTypes.js";