@clickhouse/client 1.22.0 → 1.23.0-head.70ad405.1

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 (191) hide show
  1. package/CHANGELOG.md +1342 -0
  2. package/README.md +20 -7
  3. package/dist/client.d.ts +2 -2
  4. package/dist/client.js +11 -4
  5. package/dist/client.js.map +1 -1
  6. package/dist/common/clickhouse_types.d.ts +98 -0
  7. package/dist/common/clickhouse_types.js +30 -0
  8. package/dist/common/clickhouse_types.js.map +1 -0
  9. package/dist/common/client.d.ts +233 -0
  10. package/dist/common/client.js +414 -0
  11. package/dist/common/client.js.map +1 -0
  12. package/dist/common/config.d.ts +234 -0
  13. package/dist/common/config.js +364 -0
  14. package/dist/common/config.js.map +1 -0
  15. package/dist/common/connection.d.ts +124 -0
  16. package/dist/common/connection.js +3 -0
  17. package/dist/common/connection.js.map +1 -0
  18. package/dist/common/data_formatter/format_query_params.d.ts +11 -0
  19. package/dist/common/data_formatter/format_query_params.js +128 -0
  20. package/dist/common/data_formatter/format_query_params.js.map +1 -0
  21. package/dist/common/data_formatter/format_query_settings.d.ts +2 -0
  22. package/dist/common/data_formatter/format_query_settings.js +20 -0
  23. package/dist/common/data_formatter/format_query_settings.js.map +1 -0
  24. package/dist/common/data_formatter/formatter.d.ts +41 -0
  25. package/dist/common/data_formatter/formatter.js +78 -0
  26. package/dist/common/data_formatter/formatter.js.map +1 -0
  27. package/dist/common/data_formatter/index.d.ts +3 -0
  28. package/dist/common/data_formatter/index.js +24 -0
  29. package/dist/common/data_formatter/index.js.map +1 -0
  30. package/dist/common/error/error.d.ts +20 -0
  31. package/dist/common/error/error.js +73 -0
  32. package/dist/common/error/error.js.map +1 -0
  33. package/dist/common/error/index.d.ts +1 -0
  34. package/dist/common/error/index.js +18 -0
  35. package/dist/common/error/index.js.map +1 -0
  36. package/dist/common/index.d.ts +67 -0
  37. package/dist/common/index.js +97 -0
  38. package/dist/common/index.js.map +1 -0
  39. package/dist/common/logger.d.ts +80 -0
  40. package/dist/common/logger.js +154 -0
  41. package/dist/common/logger.js.map +1 -0
  42. package/dist/common/parse/column_types.d.ts +155 -0
  43. package/dist/common/parse/column_types.js +594 -0
  44. package/dist/common/parse/column_types.js.map +1 -0
  45. package/dist/common/parse/index.d.ts +2 -0
  46. package/dist/common/parse/index.js +19 -0
  47. package/dist/common/parse/index.js.map +1 -0
  48. package/dist/common/parse/json_handling.d.ts +19 -0
  49. package/dist/common/parse/json_handling.js +8 -0
  50. package/dist/common/parse/json_handling.js.map +1 -0
  51. package/dist/common/result.d.ts +90 -0
  52. package/dist/common/result.js +3 -0
  53. package/dist/common/result.js.map +1 -0
  54. package/dist/common/settings.d.ts +2007 -0
  55. package/dist/common/settings.js +19 -0
  56. package/dist/common/settings.js.map +1 -0
  57. package/dist/common/tracing.d.ts +146 -0
  58. package/dist/common/tracing.js +76 -0
  59. package/dist/common/tracing.js.map +1 -0
  60. package/dist/common/ts_utils.d.ts +4 -0
  61. package/dist/common/ts_utils.js +3 -0
  62. package/dist/common/ts_utils.js.map +1 -0
  63. package/dist/common/utils/connection.d.ts +21 -0
  64. package/dist/common/utils/connection.js +43 -0
  65. package/dist/common/utils/connection.js.map +1 -0
  66. package/dist/common/utils/index.d.ts +5 -0
  67. package/dist/common/utils/index.js +22 -0
  68. package/dist/common/utils/index.js.map +1 -0
  69. package/dist/common/utils/multipart.d.ts +34 -0
  70. package/dist/common/utils/multipart.js +81 -0
  71. package/dist/common/utils/multipart.js.map +1 -0
  72. package/dist/common/utils/sleep.d.ts +4 -0
  73. package/dist/common/utils/sleep.js +12 -0
  74. package/dist/common/utils/sleep.js.map +1 -0
  75. package/dist/common/utils/stream.d.ts +15 -0
  76. package/dist/common/utils/stream.js +50 -0
  77. package/dist/common/utils/stream.js.map +1 -0
  78. package/dist/common/utils/url.d.ts +20 -0
  79. package/dist/common/utils/url.js +67 -0
  80. package/dist/common/utils/url.js.map +1 -0
  81. package/dist/common/version.d.ts +2 -0
  82. package/dist/common/version.js +4 -0
  83. package/dist/common/version.js.map +1 -0
  84. package/dist/config.d.ts +22 -2
  85. package/dist/config.js +2 -2
  86. package/dist/config.js.map +1 -1
  87. package/dist/connection/compression.d.ts +2 -2
  88. package/dist/connection/compression.js +4 -4
  89. package/dist/connection/compression.js.map +1 -1
  90. package/dist/connection/create_connection.d.ts +1 -1
  91. package/dist/connection/node_base_connection.d.ts +3 -3
  92. package/dist/connection/node_base_connection.js +22 -22
  93. package/dist/connection/node_base_connection.js.map +1 -1
  94. package/dist/connection/node_custom_agent_connection.js +2 -2
  95. package/dist/connection/node_custom_agent_connection.js.map +1 -1
  96. package/dist/connection/node_http_connection.js +2 -2
  97. package/dist/connection/node_http_connection.js.map +1 -1
  98. package/dist/connection/node_https_connection.d.ts +1 -1
  99. package/dist/connection/node_https_connection.js +3 -3
  100. package/dist/connection/node_https_connection.js.map +1 -1
  101. package/dist/connection/socket_pool.d.ts +1 -1
  102. package/dist/connection/socket_pool.js +30 -30
  103. package/dist/connection/socket_pool.js.map +1 -1
  104. package/dist/connection/stream.d.ts +1 -1
  105. package/dist/connection/stream.js +9 -9
  106. package/dist/connection/stream.js.map +1 -1
  107. package/dist/index.d.ts +9 -7
  108. package/dist/index.js +26 -24
  109. package/dist/index.js.map +1 -1
  110. package/dist/result_set.d.ts +1 -1
  111. package/dist/result_set.js +10 -10
  112. package/dist/result_set.js.map +1 -1
  113. package/dist/utils/encoder.d.ts +1 -1
  114. package/dist/utils/encoder.js +5 -5
  115. package/dist/utils/encoder.js.map +1 -1
  116. package/dist/version.d.ts +1 -1
  117. package/dist/version.js +1 -1
  118. package/dist/version.js.map +1 -1
  119. package/package.json +10 -7
  120. package/skills/AGENTS.md +8 -0
  121. package/skills/clickhouse-js-node-rowbinary/AGENTS.md +44 -0
  122. package/skills/clickhouse-js-node-rowbinary/CHANGELOG.md +49 -0
  123. package/skills/clickhouse-js-node-rowbinary/EXAMPLES.md +48 -0
  124. package/skills/clickhouse-js-node-rowbinary/README.md +319 -0
  125. package/skills/clickhouse-js-node-rowbinary/SKILL.md +111 -0
  126. package/skills/clickhouse-js-node-rowbinary/case-studies/iot-rowbinary-vs-json.md +83 -0
  127. package/skills/clickhouse-js-node-rowbinary/case-studies/ledger-rowbinary-vs-json.md +103 -0
  128. package/skills/clickhouse-js-node-rowbinary/case-studies/logs-json-wins.md +86 -0
  129. package/skills/clickhouse-js-node-rowbinary/case-studies/wasm-vs-js.md +172 -0
  130. package/skills/clickhouse-js-node-rowbinary/reader.md +126 -0
  131. package/skills/clickhouse-js-node-rowbinary/src/examples/carts.ts +75 -0
  132. package/skills/clickhouse-js-node-rowbinary/src/examples/events.ts +51 -0
  133. package/skills/clickhouse-js-node-rowbinary/src/examples/iot.ts +158 -0
  134. package/skills/clickhouse-js-node-rowbinary/src/examples/ledger.ts +98 -0
  135. package/skills/clickhouse-js-node-rowbinary/src/examples/logs.ts +73 -0
  136. package/skills/clickhouse-js-node-rowbinary/src/examples/observability.ts +141 -0
  137. package/skills/clickhouse-js-node-rowbinary/src/examples/orders.ts +66 -0
  138. package/skills/clickhouse-js-node-rowbinary/src/examples/profiles.ts +60 -0
  139. package/skills/clickhouse-js-node-rowbinary/src/examples/telemetry.ts +102 -0
  140. package/skills/clickhouse-js-node-rowbinary/src/readers/aggregateFunction.ts +34 -0
  141. package/skills/clickhouse-js-node-rowbinary/src/readers/bool.ts +10 -0
  142. package/skills/clickhouse-js-node-rowbinary/src/readers/columnar.ts +125 -0
  143. package/skills/clickhouse-js-node-rowbinary/src/readers/compile.ts +328 -0
  144. package/skills/clickhouse-js-node-rowbinary/src/readers/composite.ts +181 -0
  145. package/skills/clickhouse-js-node-rowbinary/src/readers/core.ts +77 -0
  146. package/skills/clickhouse-js-node-rowbinary/src/readers/datetime.ts +113 -0
  147. package/skills/clickhouse-js-node-rowbinary/src/readers/decimals.ts +57 -0
  148. package/skills/clickhouse-js-node-rowbinary/src/readers/dynamic.ts +332 -0
  149. package/skills/clickhouse-js-node-rowbinary/src/readers/enums.ts +40 -0
  150. package/skills/clickhouse-js-node-rowbinary/src/readers/floats.ts +32 -0
  151. package/skills/clickhouse-js-node-rowbinary/src/readers/geo.ts +109 -0
  152. package/skills/clickhouse-js-node-rowbinary/src/readers/header.ts +29 -0
  153. package/skills/clickhouse-js-node-rowbinary/src/readers/integers.ts +95 -0
  154. package/skills/clickhouse-js-node-rowbinary/src/readers/interval.ts +54 -0
  155. package/skills/clickhouse-js-node-rowbinary/src/readers/ip.ts +93 -0
  156. package/skills/clickhouse-js-node-rowbinary/src/readers/json.ts +33 -0
  157. package/skills/clickhouse-js-node-rowbinary/src/readers/lowCardinality.ts +18 -0
  158. package/skills/clickhouse-js-node-rowbinary/src/readers/nested.ts +23 -0
  159. package/skills/clickhouse-js-node-rowbinary/src/readers/nothing.ts +29 -0
  160. package/skills/clickhouse-js-node-rowbinary/src/readers/reader.ts +68 -0
  161. package/skills/clickhouse-js-node-rowbinary/src/readers/rowBinaryWithNamesAndTypes.ts +155 -0
  162. package/skills/clickhouse-js-node-rowbinary/src/readers/rows.ts +58 -0
  163. package/skills/clickhouse-js-node-rowbinary/src/readers/simpleAggregateFunction.ts +20 -0
  164. package/skills/clickhouse-js-node-rowbinary/src/readers/stream.ts +276 -0
  165. package/skills/clickhouse-js-node-rowbinary/src/readers/strings.ts +55 -0
  166. package/skills/clickhouse-js-node-rowbinary/src/readers/time.ts +61 -0
  167. package/skills/clickhouse-js-node-rowbinary/src/readers/uuid.ts +153 -0
  168. package/skills/clickhouse-js-node-rowbinary/src/readers/varint.ts +70 -0
  169. package/skills/clickhouse-js-node-rowbinary/src/writers/aggregateFunction.ts +18 -0
  170. package/skills/clickhouse-js-node-rowbinary/src/writers/bool.ts +10 -0
  171. package/skills/clickhouse-js-node-rowbinary/src/writers/composite.ts +140 -0
  172. package/skills/clickhouse-js-node-rowbinary/src/writers/core.ts +92 -0
  173. package/skills/clickhouse-js-node-rowbinary/src/writers/datetime.ts +123 -0
  174. package/skills/clickhouse-js-node-rowbinary/src/writers/decimals.ts +51 -0
  175. package/skills/clickhouse-js-node-rowbinary/src/writers/enums.ts +18 -0
  176. package/skills/clickhouse-js-node-rowbinary/src/writers/floats.ts +40 -0
  177. package/skills/clickhouse-js-node-rowbinary/src/writers/geo.ts +125 -0
  178. package/skills/clickhouse-js-node-rowbinary/src/writers/integers.ts +90 -0
  179. package/skills/clickhouse-js-node-rowbinary/src/writers/interval.ts +11 -0
  180. package/skills/clickhouse-js-node-rowbinary/src/writers/ip.ts +121 -0
  181. package/skills/clickhouse-js-node-rowbinary/src/writers/lowCardinality.ts +12 -0
  182. package/skills/clickhouse-js-node-rowbinary/src/writers/nested.ts +17 -0
  183. package/skills/clickhouse-js-node-rowbinary/src/writers/nothing.ts +21 -0
  184. package/skills/clickhouse-js-node-rowbinary/src/writers/rows.ts +144 -0
  185. package/skills/clickhouse-js-node-rowbinary/src/writers/simpleAggregateFunction.ts +12 -0
  186. package/skills/clickhouse-js-node-rowbinary/src/writers/strings.ts +77 -0
  187. package/skills/clickhouse-js-node-rowbinary/src/writers/time.ts +54 -0
  188. package/skills/clickhouse-js-node-rowbinary/src/writers/uuid.ts +60 -0
  189. package/skills/clickhouse-js-node-rowbinary/src/writers/varint.ts +64 -0
  190. package/skills/clickhouse-js-node-rowbinary/src/writers/writer.ts +101 -0
  191. package/skills/clickhouse-js-node-rowbinary/writer.md +96 -0
@@ -0,0 +1,332 @@
1
+ import { type Reader, Cursor } from "./core.js";
2
+ import { readUVarint } from "./varint.js";
3
+ import {
4
+ readInt8,
5
+ readInt16,
6
+ readInt32,
7
+ readInt64,
8
+ readInt128,
9
+ readInt256,
10
+ readUInt8,
11
+ readUInt16,
12
+ readUInt32,
13
+ readUInt64,
14
+ readUInt128,
15
+ readUInt256,
16
+ } from "./integers.js";
17
+ import { readBool } from "./bool.js";
18
+ import { readEnum8, readEnum16 } from "./enums.js";
19
+ import { readFloat32, readFloat64 } from "./floats.js";
20
+ import { readString, readFixedString } from "./strings.js";
21
+ import { readUUID } from "./uuid.js";
22
+ import { readIPv4, readIPv6 } from "./ip.js";
23
+ import {
24
+ readDate,
25
+ readDate32,
26
+ readDateTime,
27
+ readDateTime64,
28
+ } from "./datetime.js";
29
+ import {
30
+ readDecimal32,
31
+ readDecimal64,
32
+ readDecimal128,
33
+ readDecimal256,
34
+ } from "./decimals.js";
35
+ import {
36
+ INTERVAL_UNITS,
37
+ type IntervalValue,
38
+ readInterval,
39
+ } from "./interval.js";
40
+ import {
41
+ readArray,
42
+ readMap,
43
+ readNullable,
44
+ readTuple,
45
+ readTupleNamed,
46
+ readVariant,
47
+ } from "./composite.js";
48
+ import { readJSON } from "./json.js";
49
+
50
+ /**
51
+ * Read one `Dynamic` value. A `Dynamic` is SELF-DESCRIBING: every value is a
52
+ * binary TYPE ENCODING followed by the value's RowBinary bytes. So unlike every
53
+ * other reader, the type is not known until runtime — this is the one place a
54
+ * generic runtime dispatch is correct and unavoidable. {@link readDynamicType}
55
+ * parses the type header into a value `Reader`; here we just invoke it.
56
+ *
57
+ * GOTCHA — wrappers are erased. `Dynamic` stores the CONCRETE type of the stored
58
+ * value, never a wrapper: a non-null `Nullable(UInt8)` is stored as plain
59
+ * `UInt8`, an active `Variant(...)` as just its current alternative's type. A
60
+ * NULL (from any source) is stored as `Nothing` (tag 0x00) and decodes to
61
+ * `null`. So you never see Nullable/Variant tags here.
62
+ *
63
+ * For a Dynamic-heavy hot path where the same few types recur, parse the type
64
+ * once and reuse the returned reader across rows instead of re-parsing.
65
+ */
66
+ export function readDynamic(state: Cursor): unknown {
67
+ return readDynamicType(state)(state);
68
+ }
69
+
70
+ /**
71
+ * Parse a binary TYPE ENCODING (the header ClickHouse writes before each
72
+ * `Dynamic` value) and return a `Reader` that reads ONE value of that type. The
73
+ * type bytes are consumed now; the returned reader consumes only value bytes
74
+ * when called. Composites recurse: element/key/field types are parsed eagerly
75
+ * into inner readers, then composed with the existing combinators
76
+ * ({@link readArray}/{@link readTuple}/{@link readMap}/...).
77
+ *
78
+ * The leading byte is a 1-byte tag; parameterized types (FixedString, Enum,
79
+ * Decimal, DateTime64, timezone'd DateTime) carry extra LEB128/varint and string
80
+ * fields in the header, which we consume to reach the value reader.
81
+ *
82
+ * Only the tags ClickHouse actually emits for stored `Dynamic` values are
83
+ * handled (wrappers are erased — see {@link readDynamic}). Unknown tags throw
84
+ * with the tag value so you can extend this switch for the types your data
85
+ * actually contains.
86
+ */
87
+ export function readDynamicType(state: Cursor): Reader<unknown> {
88
+ const tag = readUInt8(state);
89
+ switch (tag) {
90
+ // Nothing — a stored NULL. Zero value bytes.
91
+ case 0x00:
92
+ return () => null;
93
+ // Unsigned integers.
94
+ case 0x01:
95
+ return readUInt8;
96
+ case 0x02:
97
+ return readUInt16;
98
+ case 0x03:
99
+ return readUInt32;
100
+ case 0x04:
101
+ return readUInt64;
102
+ case 0x05:
103
+ return readUInt128;
104
+ case 0x06:
105
+ return readUInt256;
106
+ // Signed integers.
107
+ case 0x07:
108
+ return readInt8;
109
+ case 0x08:
110
+ return readInt16;
111
+ case 0x09:
112
+ return readInt32;
113
+ case 0x0a:
114
+ return readInt64;
115
+ case 0x0b:
116
+ return readInt128;
117
+ case 0x0c:
118
+ return readInt256;
119
+ // Floats.
120
+ case 0x0d:
121
+ return readFloat32;
122
+ case 0x0e:
123
+ return readFloat64;
124
+ // Dates and times. The timezone'd variants carry a tz string in the header
125
+ // (metadata only — identical value wire to the untimezoned form).
126
+ case 0x0f:
127
+ return readDate;
128
+ case 0x10:
129
+ return readDate32;
130
+ case 0x11:
131
+ return readDateTime;
132
+ case 0x12:
133
+ readString(state); // timezone name (metadata)
134
+ return readDateTime;
135
+ case 0x13:
136
+ return readDateTime64(readUVarint(state));
137
+ case 0x14: {
138
+ const precision = readUVarint(state);
139
+ readString(state); // timezone name (metadata)
140
+ return readDateTime64(precision);
141
+ }
142
+ // String / FixedString(N).
143
+ case 0x15:
144
+ return readString;
145
+ case 0x16:
146
+ return readFixedString(readUVarint(state));
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.
150
+ case 0x17: {
151
+ const n = readUVarint(state);
152
+ const map = new Map<number, string>();
153
+ for (let i = 0; i < n; i++) {
154
+ const name = readString(state);
155
+ map.set(readInt8(state), name);
156
+ }
157
+ return readEnum8(map);
158
+ }
159
+ case 0x18: {
160
+ const n = readUVarint(state);
161
+ const map = new Map<number, string>();
162
+ for (let i = 0; i < n; i++) {
163
+ const name = readString(state);
164
+ map.set(readInt16(state), name);
165
+ }
166
+ return readEnum16(map);
167
+ }
168
+ // Decimals: header carries precision P then scale S (both varint). Only S
169
+ // matters for decoding; P is consumed and dropped. Returns [unscaled, S].
170
+ case 0x19: {
171
+ readUVarint(state);
172
+ return readDecimal32(readUVarint(state));
173
+ }
174
+ case 0x1a: {
175
+ readUVarint(state);
176
+ return readDecimal64(readUVarint(state));
177
+ }
178
+ case 0x1b: {
179
+ readUVarint(state);
180
+ return readDecimal128(readUVarint(state));
181
+ }
182
+ case 0x1c: {
183
+ readUVarint(state);
184
+ return readDecimal256(readUVarint(state));
185
+ }
186
+ case 0x1d:
187
+ return readUUID;
188
+ // Array(T): parse the element type once, then read a length-prefixed run.
189
+ case 0x1e:
190
+ return readArray(readDynamicType(state));
191
+ // Tuple(...): a field count, then that many element type encodings.
192
+ case 0x1f: {
193
+ const n = readUVarint(state);
194
+ const fields: Array<Reader<unknown>> = [];
195
+ for (let i = 0; i < n; i++) fields.push(readDynamicType(state));
196
+ return readTuple(fields);
197
+ }
198
+ // Named Tuple: a count, then (name String, type) pairs. Names shape the
199
+ // result object; the value wire is identical to an unnamed tuple.
200
+ case 0x20: {
201
+ const n = readUVarint(state);
202
+ const fields: Record<string, Reader<unknown>> = {};
203
+ for (let i = 0; i < n; i++) {
204
+ const name = readString(state);
205
+ fields[name] = readDynamicType(state);
206
+ }
207
+ return readTupleNamed(fields);
208
+ }
209
+ // Set (0x21): a type used inside IN-expressions, not a stored column value.
210
+ case 0x21:
211
+ throw new RangeError(
212
+ "RowBinary: Dynamic type 0x21 (Set) has no decodable value form",
213
+ );
214
+ // Interval (0x22): the header carries a 1-byte unit kind (0x00 Nanosecond
215
+ // ... 0x0a Year), then the value is a signed Int64 count of that unit. Here
216
+ // — unlike a standalone Interval* column — the unit IS in the wire, so we
217
+ // pair it with the count as an IntervalValue rather than dropping it.
218
+ case 0x22: {
219
+ const kind = readUInt8(state);
220
+ const unit = INTERVAL_UNITS[kind];
221
+ if (unit === undefined) {
222
+ throw new RangeError(
223
+ `RowBinary: unknown Interval kind ${kind} in Dynamic type encoding`,
224
+ );
225
+ }
226
+ return (s): IntervalValue => [readInterval(s), unit];
227
+ }
228
+ // Nullable(T): a NULL flag byte then (if not null) the inner value. At the
229
+ // TOP level Dynamic erases Nullable, but NESTED inside Array/Tuple/Map the
230
+ // element type really is Nullable(T) — e.g. Array(Nullable(UInt8)) — so the
231
+ // tag does appear here.
232
+ case 0x23:
233
+ return readNullable(readDynamicType(state));
234
+ // Function (0x24): a higher-order function type (lambda), header-only with no
235
+ // stored value form.
236
+ case 0x24:
237
+ throw new RangeError(
238
+ "RowBinary: Dynamic type 0x24 (Function) has no decodable value form",
239
+ );
240
+ // AggregateFunction (0x25): an opaque, UNFRAMED aggregation state with a
241
+ // function-specific layout and no length prefix, so it cannot be decoded OR
242
+ // skipped generically. Finalize server-side before putting it in a Dynamic.
243
+ case 0x25:
244
+ throw new RangeError(
245
+ "RowBinary: Dynamic type 0x25 (AggregateFunction) is an opaque unframed state — finalize it server-side",
246
+ );
247
+ // LowCardinality(T): transparent — keep the inner type's reader as-is.
248
+ case 0x26:
249
+ return readDynamicType(state);
250
+ // Map(K, V): parse the key type then the value type.
251
+ case 0x27: {
252
+ const key = readDynamicType(state);
253
+ const value = readDynamicType(state);
254
+ return readMap(key, value);
255
+ }
256
+ case 0x28:
257
+ return readIPv4;
258
+ case 0x29:
259
+ return readIPv6;
260
+ // Variant (0x2a): the header is (count, then each alternative's type
261
+ // encoding). ClickHouse writes the alternatives ALREADY SORTED by type name,
262
+ // so the parsed readers line up with the discriminant directly. The value is
263
+ // a 1-byte discriminant (0xff = NULL) then the chosen value. NOTE: top-level
264
+ // Dynamic erases Variant, so this tag only appears NESTED.
265
+ case 0x2a: {
266
+ const n = readUVarint(state);
267
+ const alternatives: Array<Reader<unknown>> = [];
268
+ for (let i = 0; i < n; i++) alternatives.push(readDynamicType(state));
269
+ return readVariant(alternatives);
270
+ }
271
+ // Dynamic (0x2b): a Dynamic nested inside a Dynamic. The header is a single
272
+ // max_dynamic_types byte; the value is itself a type-encoding + value, so it
273
+ // is just a recursive readDynamic. We skip max_dynamic_types because it does
274
+ // NOT affect value decoding — it is a storage/Native-format overflow
275
+ // threshold; in RowBinary every value is normalized to a plain (tag, value).
276
+ case 0x2b:
277
+ readUInt8(state); // max_dynamic_types — storage threshold, not used to decode
278
+ return readDynamic;
279
+ // Custom type (0x2c): the type name is written as a String and must be
280
+ // re-parsed to learn the real type — we don't have a type-name parser.
281
+ case 0x2c:
282
+ throw new RangeError(
283
+ "RowBinary: Dynamic type 0x2c (custom type, name-encoded) is not supported — requires parsing the type name string",
284
+ );
285
+ case 0x2d:
286
+ return readBool;
287
+ // SimpleAggregateFunction (0x2e): transparent — the value is just its
288
+ // underlying type T. The header is (function_name String, argument types);
289
+ // extend here by consuming those, then returning T's reader.
290
+ case 0x2e:
291
+ throw new RangeError(
292
+ "RowBinary: Dynamic type 0x2e (SimpleAggregateFunction) is not supported yet — consume the header, then read the inner T",
293
+ );
294
+ // Nested(...) (0x2f): on the wire it IS Array(Tuple(...)). The header is
295
+ // identical to a named Tuple's (count, then (name String, type) pairs), and
296
+ // the value is an Array of those tuples, so compose readArray + readTupleNamed.
297
+ case 0x2f: {
298
+ const n = readUVarint(state);
299
+ const fields: Record<string, Reader<unknown>> = {};
300
+ for (let i = 0; i < n; i++) {
301
+ const name = readString(state);
302
+ fields[name] = readDynamicType(state);
303
+ }
304
+ return readArray(readTupleNamed(fields));
305
+ }
306
+ // JSON (0x30): the type-encoding header is a version byte, max_dynamic_paths
307
+ // (varuint), max_dynamic_types (uint8), then the typed-path / skip-path /
308
+ // skip-regexp lists. We consume it to reach the value body. Typed paths are
309
+ // serialized WITHOUT a Dynamic tag, so a schema-less reader can't decode them
310
+ // — bail if any are declared.
311
+ case 0x30: {
312
+ readUInt8(state); // serialization version (observed 0x00)
313
+ readUVarint(state); // max_dynamic_paths
314
+ readUInt8(state); // max_dynamic_types
315
+ const typedPaths = readUVarint(state);
316
+ if (typedPaths !== 0) {
317
+ throw new RangeError(
318
+ "RowBinary: JSON with declared typed paths is not supported — read each typed path with its known type",
319
+ );
320
+ }
321
+ const skipPaths = readUVarint(state);
322
+ for (let i = 0; i < skipPaths; i++) readString(state);
323
+ const skipRegexps = readUVarint(state);
324
+ for (let i = 0; i < skipRegexps; i++) readString(state);
325
+ return readJSON;
326
+ }
327
+ default:
328
+ throw new RangeError(
329
+ `RowBinary: unknown Dynamic type tag 0x${tag.toString(16)} (not in the binary type encoding table)`,
330
+ );
331
+ }
332
+ }
@@ -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,32 @@
1
+ import { Cursor, advance } from "./core.js";
2
+
3
+ /**
4
+ * Scratch view for widening a `BFloat16`: its 16 bits are the top half of an
5
+ * IEEE 754 float32, so we stage them into a 4-byte buffer and read a float32.
6
+ */
7
+ const bf16Scratch = new DataView(new ArrayBuffer(4));
8
+
9
+ /** Read a `Float32`: 4 bytes, little-endian IEEE 754 single precision. */
10
+ export function readFloat32(state: Cursor): number {
11
+ return state.view.getFloat32(advance(state, 4), true);
12
+ }
13
+
14
+ /** Read a `Float64`: 8 bytes, little-endian IEEE 754 double precision. */
15
+ export function readFloat64(state: Cursor): number {
16
+ return state.view.getFloat64(advance(state, 8), true);
17
+ }
18
+
19
+ /**
20
+ * Read a `BFloat16`: 2 bytes, little-endian. BFloat16 is the high 16 bits of a
21
+ * float32 (same 8-bit exponent, 7-bit mantissa), so placing the bits in the top
22
+ * half of a 32-bit float and reading it back is exact.
23
+ *
24
+ * NOTE: `bf16Scratch` is module-level shared state written-then-read in this
25
+ * function. That is safe because the read is synchronous; do NOT introduce an
26
+ * `await`/`yield` between the `setUint32` and the `getFloat32`.
27
+ */
28
+ export function readBFloat16(state: Cursor): number {
29
+ const bits = state.view.getUint16(advance(state, 2), true);
30
+ bf16Scratch.setUint32(0, bits * 0x10000, true);
31
+ return bf16Scratch.getFloat32(0, true);
32
+ }
@@ -0,0 +1,109 @@
1
+ import { Cursor } from "./core.js";
2
+ import { readFloat64 } from "./floats.js";
3
+ import { readUInt8 } from "./integers.js";
4
+ import { readUVarint } from "./varint.js";
5
+
6
+ /** A geo `Point`: `[x, y]`, the base of every ClickHouse geo type. */
7
+ export type Point = [x: number, y: number];
8
+
9
+ // Geo types are concrete compositions of Point = Tuple(Float64, Float64). They
10
+ // are monomorphic (no sub-readers) — the generator can emit them as-is.
11
+
12
+ /** Read a `Point`: `Tuple(Float64, Float64)` -> `[x, y]`. */
13
+ export function readPoint(state: Cursor): Point {
14
+ const x = readFloat64(state);
15
+ const y = readFloat64(state);
16
+ return [x, y];
17
+ }
18
+
19
+ /**
20
+ * Read a `Ring`: `Array(Point)` — a LEB128 point count, then that many points.
21
+ * `LineString` has the identical wire (see {@link readLineString}). `readPoint`
22
+ * is inlined here (two `readFloat64`s) to drop a call per point on this hot path.
23
+ */
24
+ export function readRing(state: Cursor): Point[] {
25
+ const n = readUVarint(state);
26
+ const out: Point[] = [];
27
+ for (let i = 0; i < n; i++) {
28
+ const x = readFloat64(state);
29
+ const y = readFloat64(state);
30
+ out.push([x, y]);
31
+ }
32
+ return out;
33
+ }
34
+
35
+ /**
36
+ * Read a `LineString`: `Array(Point)` (identical wire to a `Ring`). Points are
37
+ * inlined (two `readFloat64`s) to drop a call per point on this hot path.
38
+ */
39
+ export function readLineString(state: Cursor): Point[] {
40
+ const n = readUVarint(state);
41
+ const out: Point[] = [];
42
+ for (let i = 0; i < n; i++) {
43
+ const x = readFloat64(state);
44
+ const y = readFloat64(state);
45
+ out.push([x, y]);
46
+ }
47
+ return out;
48
+ }
49
+
50
+ /** Read a `Polygon`: `Array(Ring)` — the outer ring first, then any holes. */
51
+ export function readPolygon(state: Cursor): Point[][] {
52
+ const n = readUVarint(state);
53
+ const out: Point[][] = [];
54
+ for (let i = 0; i < n; i++) out.push(readRing(state));
55
+ return out;
56
+ }
57
+
58
+ /** Read a `MultiLineString`: `Array(LineString)` (identical wire to a `Polygon`). */
59
+ export function readMultiLineString(state: Cursor): Point[][] {
60
+ const n = readUVarint(state);
61
+ const out: Point[][] = [];
62
+ for (let i = 0; i < n; i++) out.push(readLineString(state));
63
+ return out;
64
+ }
65
+
66
+ /** Read a `MultiPolygon`: `Array(Polygon)`. */
67
+ export function readMultiPolygon(state: Cursor): Point[][][] {
68
+ const n = readUVarint(state);
69
+ const out: Point[][][] = [];
70
+ for (let i = 0; i < n; i++) out.push(readPolygon(state));
71
+ return out;
72
+ }
73
+
74
+ /**
75
+ * Read a `Geometry`: a named `Variant` over the six geo types. This is the
76
+ * MONOMORPHIZED form of `readVariant` for a concrete variant — a switch over the
77
+ * discriminant with each branch inlined, no reader array. The alternatives,
78
+ * sorted by type name (so in discriminant order), are LineString(0),
79
+ * MultiLineString(1), MultiPolygon(2), Point(3), Polygon(4), Ring(5); 0xFF is NULL.
80
+ *
81
+ * NOTE: the value shapes overlap — LineString and Ring are both `Point[]`,
82
+ * MultiLineString and Polygon both `Point[][]` — so the value alone does not say
83
+ * which geo type it was. If you need the kind, branch on the discriminant.
84
+ */
85
+ export function readGeometry(
86
+ state: Cursor,
87
+ ): Point | Point[] | Point[][] | Point[][][] | null {
88
+ const discriminant = readUInt8(state);
89
+ switch (discriminant) {
90
+ case 0:
91
+ return readLineString(state);
92
+ case 1:
93
+ return readMultiLineString(state);
94
+ case 2:
95
+ return readMultiPolygon(state);
96
+ case 3:
97
+ return readPoint(state);
98
+ case 4:
99
+ return readPolygon(state);
100
+ case 5:
101
+ return readRing(state);
102
+ case 0xff:
103
+ return null;
104
+ default:
105
+ throw new RangeError(
106
+ `RowBinary: unknown Geometry discriminant ${discriminant}`,
107
+ );
108
+ }
109
+ }
@@ -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
+ }
@@ -0,0 +1,95 @@
1
+ import { Cursor, advance } from "./core.js";
2
+
3
+ /** Read a single unsigned byte and advance. */
4
+ export function readUInt8(state: Cursor): number {
5
+ return state.buf[advance(state, 1)]!;
6
+ }
7
+
8
+ /** Read an `Int8`: 1 byte, two's-complement signed (-128 .. 127). */
9
+ export function readInt8(state: Cursor): number {
10
+ return state.view.getInt8(advance(state, 1));
11
+ }
12
+
13
+ /** Read a `UInt16`: 2 bytes, little-endian (0 .. 65535). */
14
+ export function readUInt16(state: Cursor): number {
15
+ return state.view.getUint16(advance(state, 2), true);
16
+ }
17
+
18
+ /**
19
+ * Read an `Int16`: 2 bytes, little-endian, two's-complement signed (-32768 ..
20
+ * 32767). `DataView` reads from any offset and decodes explicitly little-endian,
21
+ * so the value never depends on host byte order.
22
+ */
23
+ export function readInt16(state: Cursor): number {
24
+ return state.view.getInt16(advance(state, 2), true);
25
+ }
26
+
27
+ /** Read a `UInt32`: 4 bytes, little-endian (0 .. 4294967295). */
28
+ export function readUInt32(state: Cursor): number {
29
+ return state.view.getUint32(advance(state, 4), true);
30
+ }
31
+
32
+ /** Read an `Int32`: 4 bytes, little-endian, two's-complement signed. */
33
+ export function readInt32(state: Cursor): number {
34
+ return state.view.getInt32(advance(state, 4), true);
35
+ }
36
+
37
+ /**
38
+ * Read a `UInt64`: 8 bytes, little-endian. Returns a `bigint`.
39
+ * SAFE TO TOGGLE: if the values fit in 53 bits, wrap in `Number(...)`.
40
+ */
41
+ export function readUInt64(state: Cursor): bigint {
42
+ return state.view.getBigUint64(advance(state, 8), true);
43
+ }
44
+
45
+ /**
46
+ * Read an `Int64`: 8 bytes, little-endian, two's-complement. Returns a `bigint`
47
+ * (range exceeds `Number.MAX_SAFE_INTEGER`).
48
+ * SAFE TO TOGGLE: if the values fit in 53 bits, wrap in `Number(...)`.
49
+ */
50
+ export function readInt64(state: Cursor): bigint {
51
+ return state.view.getBigInt64(advance(state, 8), true);
52
+ }
53
+
54
+ /** Read a `UInt128`: 16 bytes, little-endian. Always a `bigint`. */
55
+ export function readUInt128(state: Cursor): bigint {
56
+ const start = advance(state, 16);
57
+ const lo = state.view.getBigUint64(start, true);
58
+ const hi = state.view.getBigUint64(start + 8, true);
59
+ return (hi << 64n) + lo;
60
+ }
61
+
62
+ /**
63
+ * Read an `Int128`: 16 bytes, little-endian, two's-complement. Always a
64
+ * `bigint`, composed from the low (unsigned) and high (signed) 64-bit words —
65
+ * reading the high word signed extends the sign across all 128 bits.
66
+ */
67
+ export function readInt128(state: Cursor): bigint {
68
+ const start = advance(state, 16);
69
+ const lo = state.view.getBigUint64(start, true);
70
+ const hi = state.view.getBigInt64(start + 8, true);
71
+ return (hi << 64n) + lo;
72
+ }
73
+
74
+ /** Read a `UInt256`: 32 bytes, little-endian. Always a `bigint`. */
75
+ export function readUInt256(state: Cursor): bigint {
76
+ const start = advance(state, 32);
77
+ const w0 = state.view.getBigUint64(start, true);
78
+ const w1 = state.view.getBigUint64(start + 8, true);
79
+ const w2 = state.view.getBigUint64(start + 16, true);
80
+ const w3 = state.view.getBigUint64(start + 24, true);
81
+ return w0 + (w1 << 64n) + (w2 << 128n) + (w3 << 192n);
82
+ }
83
+
84
+ /**
85
+ * Read an `Int256`: 32 bytes, little-endian, two's-complement. Always a
86
+ * `bigint`. The most-significant 64-bit word is read signed.
87
+ */
88
+ export function readInt256(state: Cursor): bigint {
89
+ const start = advance(state, 32);
90
+ const w0 = state.view.getBigUint64(start, true);
91
+ const w1 = state.view.getBigUint64(start + 8, true);
92
+ const w2 = state.view.getBigUint64(start + 16, true);
93
+ const w3 = state.view.getBigInt64(start + 24, true);
94
+ return (w3 << 192n) + (w2 << 128n) + (w1 << 64n) + w0;
95
+ }
@@ -0,0 +1,54 @@
1
+ import { Cursor } from "./core.js";
2
+ import { readInt64 } from "./integers.js";
3
+
4
+ /** The 11 `Interval` units, in ClickHouse's ascending order. */
5
+ export type IntervalUnit =
6
+ | "Nanosecond"
7
+ | "Microsecond"
8
+ | "Millisecond"
9
+ | "Second"
10
+ | "Minute"
11
+ | "Hour"
12
+ | "Day"
13
+ | "Week"
14
+ | "Month"
15
+ | "Quarter"
16
+ | "Year";
17
+
18
+ /**
19
+ * `Interval` units indexed by the kind byte the binary type encoding writes
20
+ * after the `0x22` tag (`0x00` = Nanosecond ... `0x0a` = Year). Exported because
21
+ * the `Dynamic` reader needs it to decode an `Interval` nested in a `Dynamic`.
22
+ */
23
+ export const INTERVAL_UNITS: readonly IntervalUnit[] = [
24
+ "Nanosecond",
25
+ "Microsecond",
26
+ "Millisecond",
27
+ "Second",
28
+ "Minute",
29
+ "Hour",
30
+ "Day",
31
+ "Week",
32
+ "Month",
33
+ "Quarter",
34
+ "Year",
35
+ ];
36
+
37
+ /**
38
+ * An `Interval` decoded where the unit is carried IN the wire (inside a
39
+ * `Dynamic`): the signed `Int64` count plus its unit. A standalone `Interval*`
40
+ * column has no unit byte — there, use {@link readInterval} and take the unit
41
+ * from the column type instead.
42
+ */
43
+ export type IntervalValue = readonly [count: bigint, unit: IntervalUnit];
44
+
45
+ /**
46
+ * Read an `Interval` — any of `IntervalNanosecond` ... `IntervalYear`: a signed
47
+ * `Int64` count of the unit. The unit is in the type name, not the bytes, and
48
+ * all 11 interval types share this exact wire, so this one reader covers them
49
+ * all; the caller knows the unit from the column type. Returns a `bigint`; wrap
50
+ * in `Number(...)` if the counts are known to fit in 53 bits.
51
+ */
52
+ export function readInterval(state: Cursor): bigint {
53
+ return readInt64(state);
54
+ }