@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.
- package/CHANGELOG.md +1342 -0
- package/README.md +20 -7
- package/dist/client.d.ts +2 -2
- package/dist/client.js +11 -4
- package/dist/client.js.map +1 -1
- package/dist/common/clickhouse_types.d.ts +98 -0
- package/dist/common/clickhouse_types.js +30 -0
- package/dist/common/clickhouse_types.js.map +1 -0
- package/dist/common/client.d.ts +233 -0
- package/dist/common/client.js +414 -0
- package/dist/common/client.js.map +1 -0
- package/dist/common/config.d.ts +234 -0
- package/dist/common/config.js +364 -0
- package/dist/common/config.js.map +1 -0
- package/dist/common/connection.d.ts +124 -0
- package/dist/common/connection.js +3 -0
- package/dist/common/connection.js.map +1 -0
- package/dist/common/data_formatter/format_query_params.d.ts +11 -0
- package/dist/common/data_formatter/format_query_params.js +128 -0
- package/dist/common/data_formatter/format_query_params.js.map +1 -0
- package/dist/common/data_formatter/format_query_settings.d.ts +2 -0
- package/dist/common/data_formatter/format_query_settings.js +20 -0
- package/dist/common/data_formatter/format_query_settings.js.map +1 -0
- package/dist/common/data_formatter/formatter.d.ts +41 -0
- package/dist/common/data_formatter/formatter.js +78 -0
- package/dist/common/data_formatter/formatter.js.map +1 -0
- package/dist/common/data_formatter/index.d.ts +3 -0
- package/dist/common/data_formatter/index.js +24 -0
- package/dist/common/data_formatter/index.js.map +1 -0
- package/dist/common/error/error.d.ts +20 -0
- package/dist/common/error/error.js +73 -0
- package/dist/common/error/error.js.map +1 -0
- package/dist/common/error/index.d.ts +1 -0
- package/dist/common/error/index.js +18 -0
- package/dist/common/error/index.js.map +1 -0
- package/dist/common/index.d.ts +67 -0
- package/dist/common/index.js +97 -0
- package/dist/common/index.js.map +1 -0
- package/dist/common/logger.d.ts +80 -0
- package/dist/common/logger.js +154 -0
- package/dist/common/logger.js.map +1 -0
- package/dist/common/parse/column_types.d.ts +155 -0
- package/dist/common/parse/column_types.js +594 -0
- package/dist/common/parse/column_types.js.map +1 -0
- package/dist/common/parse/index.d.ts +2 -0
- package/dist/common/parse/index.js +19 -0
- package/dist/common/parse/index.js.map +1 -0
- package/dist/common/parse/json_handling.d.ts +19 -0
- package/dist/common/parse/json_handling.js +8 -0
- package/dist/common/parse/json_handling.js.map +1 -0
- package/dist/common/result.d.ts +90 -0
- package/dist/common/result.js +3 -0
- package/dist/common/result.js.map +1 -0
- package/dist/common/settings.d.ts +2007 -0
- package/dist/common/settings.js +19 -0
- package/dist/common/settings.js.map +1 -0
- package/dist/common/tracing.d.ts +146 -0
- package/dist/common/tracing.js +76 -0
- package/dist/common/tracing.js.map +1 -0
- package/dist/common/ts_utils.d.ts +4 -0
- package/dist/common/ts_utils.js +3 -0
- package/dist/common/ts_utils.js.map +1 -0
- package/dist/common/utils/connection.d.ts +21 -0
- package/dist/common/utils/connection.js +43 -0
- package/dist/common/utils/connection.js.map +1 -0
- package/dist/common/utils/index.d.ts +5 -0
- package/dist/common/utils/index.js +22 -0
- package/dist/common/utils/index.js.map +1 -0
- package/dist/common/utils/multipart.d.ts +34 -0
- package/dist/common/utils/multipart.js +81 -0
- package/dist/common/utils/multipart.js.map +1 -0
- package/dist/common/utils/sleep.d.ts +4 -0
- package/dist/common/utils/sleep.js +12 -0
- package/dist/common/utils/sleep.js.map +1 -0
- package/dist/common/utils/stream.d.ts +15 -0
- package/dist/common/utils/stream.js +50 -0
- package/dist/common/utils/stream.js.map +1 -0
- package/dist/common/utils/url.d.ts +20 -0
- package/dist/common/utils/url.js +67 -0
- package/dist/common/utils/url.js.map +1 -0
- package/dist/common/version.d.ts +2 -0
- package/dist/common/version.js +4 -0
- package/dist/common/version.js.map +1 -0
- package/dist/config.d.ts +22 -2
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/connection/compression.d.ts +2 -2
- package/dist/connection/compression.js +4 -4
- package/dist/connection/compression.js.map +1 -1
- package/dist/connection/create_connection.d.ts +1 -1
- package/dist/connection/node_base_connection.d.ts +3 -3
- package/dist/connection/node_base_connection.js +22 -22
- package/dist/connection/node_base_connection.js.map +1 -1
- package/dist/connection/node_custom_agent_connection.js +2 -2
- package/dist/connection/node_custom_agent_connection.js.map +1 -1
- package/dist/connection/node_http_connection.js +2 -2
- package/dist/connection/node_http_connection.js.map +1 -1
- package/dist/connection/node_https_connection.d.ts +1 -1
- package/dist/connection/node_https_connection.js +3 -3
- package/dist/connection/node_https_connection.js.map +1 -1
- package/dist/connection/socket_pool.d.ts +1 -1
- package/dist/connection/socket_pool.js +30 -30
- package/dist/connection/socket_pool.js.map +1 -1
- package/dist/connection/stream.d.ts +1 -1
- package/dist/connection/stream.js +9 -9
- package/dist/connection/stream.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.js +26 -24
- package/dist/index.js.map +1 -1
- package/dist/result_set.d.ts +1 -1
- package/dist/result_set.js +10 -10
- package/dist/result_set.js.map +1 -1
- package/dist/utils/encoder.d.ts +1 -1
- package/dist/utils/encoder.js +5 -5
- package/dist/utils/encoder.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +10 -7
- package/skills/AGENTS.md +8 -0
- package/skills/clickhouse-js-node-rowbinary/AGENTS.md +44 -0
- package/skills/clickhouse-js-node-rowbinary/CHANGELOG.md +49 -0
- package/skills/clickhouse-js-node-rowbinary/EXAMPLES.md +48 -0
- package/skills/clickhouse-js-node-rowbinary/README.md +319 -0
- package/skills/clickhouse-js-node-rowbinary/SKILL.md +111 -0
- package/skills/clickhouse-js-node-rowbinary/case-studies/iot-rowbinary-vs-json.md +83 -0
- package/skills/clickhouse-js-node-rowbinary/case-studies/ledger-rowbinary-vs-json.md +103 -0
- package/skills/clickhouse-js-node-rowbinary/case-studies/logs-json-wins.md +86 -0
- package/skills/clickhouse-js-node-rowbinary/case-studies/wasm-vs-js.md +172 -0
- package/skills/clickhouse-js-node-rowbinary/reader.md +126 -0
- package/skills/clickhouse-js-node-rowbinary/src/examples/carts.ts +75 -0
- package/skills/clickhouse-js-node-rowbinary/src/examples/events.ts +51 -0
- package/skills/clickhouse-js-node-rowbinary/src/examples/iot.ts +158 -0
- package/skills/clickhouse-js-node-rowbinary/src/examples/ledger.ts +98 -0
- package/skills/clickhouse-js-node-rowbinary/src/examples/logs.ts +73 -0
- package/skills/clickhouse-js-node-rowbinary/src/examples/observability.ts +141 -0
- package/skills/clickhouse-js-node-rowbinary/src/examples/orders.ts +66 -0
- package/skills/clickhouse-js-node-rowbinary/src/examples/profiles.ts +60 -0
- package/skills/clickhouse-js-node-rowbinary/src/examples/telemetry.ts +102 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/aggregateFunction.ts +34 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/bool.ts +10 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/columnar.ts +125 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/compile.ts +328 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/composite.ts +181 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/core.ts +77 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/datetime.ts +113 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/decimals.ts +57 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/dynamic.ts +332 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/enums.ts +40 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/floats.ts +32 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/geo.ts +109 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/header.ts +29 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/integers.ts +95 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/interval.ts +54 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/ip.ts +93 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/json.ts +33 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/lowCardinality.ts +18 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/nested.ts +23 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/nothing.ts +29 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/reader.ts +68 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/rowBinaryWithNamesAndTypes.ts +155 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/rows.ts +58 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/simpleAggregateFunction.ts +20 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/stream.ts +276 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/strings.ts +55 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/time.ts +61 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/uuid.ts +153 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/varint.ts +70 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/aggregateFunction.ts +18 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/bool.ts +10 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/composite.ts +140 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/core.ts +92 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/datetime.ts +123 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/decimals.ts +51 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/enums.ts +18 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/floats.ts +40 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/geo.ts +125 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/integers.ts +90 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/interval.ts +11 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/ip.ts +121 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/lowCardinality.ts +12 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/nested.ts +17 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/nothing.ts +21 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/rows.ts +144 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/simpleAggregateFunction.ts +12 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/strings.ts +77 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/time.ts +54 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/uuid.ts +60 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/varint.ts +64 -0
- package/skills/clickhouse-js-node-rowbinary/src/writers/writer.ts +101 -0
- package/skills/clickhouse-js-node-rowbinary/writer.md +96 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Cursor, advance } from "./core.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Read an `IPv4`: stored as a 4-byte little-endian `UInt32`. Returns the raw
|
|
5
|
+
* 32-bit value (the little-endian load already orders the octets); pass it to
|
|
6
|
+
* {@link formatIPv4} for the dotted-quad string.
|
|
7
|
+
*/
|
|
8
|
+
export function readIPv4(state: Cursor): number {
|
|
9
|
+
return state.view.getUint32(advance(state, 4), true);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read an `IPv6`: 16 bytes in network (big-endian) order. Returns the raw bytes
|
|
14
|
+
* as a zero-copy view; pass them to {@link formatIPv6} for the canonical string.
|
|
15
|
+
*
|
|
16
|
+
* The view shares memory with the response buffer, so keeping it alive pins the
|
|
17
|
+
* whole response chunk in memory. If the value must outlive the row/response,
|
|
18
|
+
* copy it with `Buffer.from(...)`.
|
|
19
|
+
*/
|
|
20
|
+
export function readIPv6(state: Cursor): Buffer {
|
|
21
|
+
const start = advance(state, 16);
|
|
22
|
+
return state.buf.subarray(start, start + 16);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Format an `IPv4` (the raw 32-bit value from {@link readIPv4}) as a dotted-quad
|
|
27
|
+
* string. Kept aside so the hot read path can skip building a string when the
|
|
28
|
+
* numeric value is all the caller needs.
|
|
29
|
+
*/
|
|
30
|
+
export function formatIPv4(value: number): string {
|
|
31
|
+
return `${(value >>> 24) & 0xff}.${(value >>> 16) & 0xff}.${(value >>> 8) & 0xff}.${value & 0xff}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Join groups `[from, to)` as colon-separated lowercase hex, by concatenating
|
|
36
|
+
* into a string in a loop. Benchmarks faster than `slice().map().join(":")`,
|
|
37
|
+
* which allocates an intermediate array. Returns `""` for an empty range.
|
|
38
|
+
*/
|
|
39
|
+
function joinGroupsHex(g: number[], from: number, to: number): string {
|
|
40
|
+
if (from >= to) return "";
|
|
41
|
+
let s = g[from]!.toString(16);
|
|
42
|
+
for (let i = from + 1; i < to; i++) s += ":" + g[i]!.toString(16);
|
|
43
|
+
return s;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Format an `IPv6` (the raw 16 bytes from {@link readIPv6}) as the canonical
|
|
48
|
+
* RFC 5952 string: lowercase, no leading zeros, the longest run of zero groups
|
|
49
|
+
* (>= 2) collapsed to `::` (leftmost on a tie), and the `::ffff:a.b.c.d` form
|
|
50
|
+
* for IPv4-mapped addresses (matching ClickHouse).
|
|
51
|
+
*
|
|
52
|
+
* Kept aside from the read so the hot path only formats when a string is
|
|
53
|
+
* actually needed.
|
|
54
|
+
*/
|
|
55
|
+
export function formatIPv6(b: Buffer): string {
|
|
56
|
+
// IPv4-mapped (::ffff:a.b.c.d): first 10 bytes zero, then 0xffff.
|
|
57
|
+
let mapped = b[10] === 0xff && b[11] === 0xff;
|
|
58
|
+
for (let i = 0; mapped && i < 10; i++) {
|
|
59
|
+
if (b[i] !== 0) mapped = false;
|
|
60
|
+
}
|
|
61
|
+
if (mapped) {
|
|
62
|
+
return `::ffff:${b[12]}.${b[13]}.${b[14]}.${b[15]}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Eight 16-bit groups, big-endian.
|
|
66
|
+
const g: number[] = [];
|
|
67
|
+
for (let i = 0; i < 8; i++) {
|
|
68
|
+
g.push((b[2 * i]! << 8) | b[2 * i + 1]!);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Longest run of >= 2 zero groups becomes "::" (leftmost wins on a tie).
|
|
72
|
+
let bestStart = -1;
|
|
73
|
+
let bestLen = 0;
|
|
74
|
+
let curStart = -1;
|
|
75
|
+
let curLen = 0;
|
|
76
|
+
for (let i = 0; i < 8; i++) {
|
|
77
|
+
if (g[i] === 0) {
|
|
78
|
+
if (curStart < 0) curStart = i;
|
|
79
|
+
curLen++;
|
|
80
|
+
if (curLen > bestLen) {
|
|
81
|
+
bestLen = curLen;
|
|
82
|
+
bestStart = curStart;
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
curStart = -1;
|
|
86
|
+
curLen = 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (bestLen < 2) {
|
|
90
|
+
return joinGroupsHex(g, 0, 8);
|
|
91
|
+
}
|
|
92
|
+
return `${joinGroupsHex(g, 0, bestStart)}::${joinGroupsHex(g, bestStart + bestLen, 8)}`;
|
|
93
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Cursor } from "./core.js";
|
|
2
|
+
import { readUVarint } from "./varint.js";
|
|
3
|
+
import { readString } from "./strings.js";
|
|
4
|
+
import { readDynamic } from "./dynamic.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Read a `JSON` value. ClickHouse's `JSON` is NOT JSON text and NOT BSON — it is
|
|
8
|
+
* a list of (path, value) pairs built on the same machinery as `Dynamic`:
|
|
9
|
+
*
|
|
10
|
+
* <varuint pathCount> then pathCount x ( <String path> <Dynamic value> )
|
|
11
|
+
*
|
|
12
|
+
* Nested objects are FLATTENED to dotted paths (`{a:{b:2}}` -> path `"a.b"`), and
|
|
13
|
+
* each leaf value is a self-describing `Dynamic`, so this just loops
|
|
14
|
+
* {@link readString} + {@link readDynamic}. Returns a `Map` keyed by the flat
|
|
15
|
+
* dotted path. Path order on the wire is not significant.
|
|
16
|
+
*
|
|
17
|
+
* GOTCHA: a null-valued path is NOT stored at all — `{"a":null}` serializes as
|
|
18
|
+
* zero paths, identical to `{}`. JSON arrays come back as `Array(Nullable(T))`.
|
|
19
|
+
*
|
|
20
|
+
* LIMITATION — typed paths only. This reads a plain `JSON` column, where every
|
|
21
|
+
* path is dynamic (tagged). A `JSON(a T, ...)` with DECLARED typed paths
|
|
22
|
+
* serializes those paths' values WITHOUT a type tag, so they cannot be decoded
|
|
23
|
+
* without the schema; read each typed path with its known `T` reader instead.
|
|
24
|
+
*/
|
|
25
|
+
export function readJSON(state: Cursor): Map<string, unknown> {
|
|
26
|
+
const n = readUVarint(state);
|
|
27
|
+
const out = new Map<string, unknown>();
|
|
28
|
+
for (let i = 0; i < n; i++) {
|
|
29
|
+
const path = readString(state);
|
|
30
|
+
out.set(path, readDynamic(state));
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Reader } from "./core.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `LowCardinality(T)` is TRANSPARENT in RowBinary: it is encoded byte-for-byte
|
|
5
|
+
* the same as `T`, with NO dictionary/index layer. (The dictionary encoding
|
|
6
|
+
* exists only in the Native format — do not look for it here.) So there is
|
|
7
|
+
* nothing to decode at this level: use `T`'s own reader directly.
|
|
8
|
+
*
|
|
9
|
+
* This identity combinator exists only to document that, and to let a generated
|
|
10
|
+
* parser name the wrapper at the call site if it wants the type to read
|
|
11
|
+
* literally — it returns the inner reader unchanged:
|
|
12
|
+
*
|
|
13
|
+
* readLowCardinality(readString) === readString
|
|
14
|
+
*
|
|
15
|
+
* Prefer just calling the inner reader.
|
|
16
|
+
*/
|
|
17
|
+
export const readLowCardinality = <T>(readValue: Reader<T>): Reader<T> =>
|
|
18
|
+
readValue;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readArray, readTupleNamed } from "./composite.js";
|
|
2
|
+
import { type Reader } from "./core.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `Nested(a T1, b T2, …)` has NO wire format of its own:
|
|
6
|
+
* - `flatten_nested = 1` (the default): the column expands into separate
|
|
7
|
+
* columns `a Array(T1)`, `b Array(T2)`, … — decode each with `readArray`.
|
|
8
|
+
* - `flatten_nested = 0`: the column is `Array(Tuple(a T1, b T2, …))` — decode
|
|
9
|
+
* with `readArray` + `readTupleNamed`.
|
|
10
|
+
*
|
|
11
|
+
* Either way it reuses existing readers; there is no dedicated Nested wire. This
|
|
12
|
+
* thin alias just composes the two for the `flatten_nested = 0` shape, as
|
|
13
|
+
* documentation that "Nested === Array(Tuple(...))":
|
|
14
|
+
*
|
|
15
|
+
* readNested({ a: readUInt8, b: readString })
|
|
16
|
+
* === readArray(readTupleNamed({ a: readUInt8, b: readString }))
|
|
17
|
+
*
|
|
18
|
+
* When generating code, prefer inlining (monomorphize the array + tuple) over
|
|
19
|
+
* this generic composition.
|
|
20
|
+
*/
|
|
21
|
+
export const readNested = <T extends Record<string, unknown>>(fields: {
|
|
22
|
+
[K in keyof T]: Reader<T[K]>;
|
|
23
|
+
}): Reader<T[]> => readArray(readTupleNamed(fields));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Reader } from "./core.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `Nothing` is the empty type: it has NO values and occupies ZERO bytes. It is
|
|
5
|
+
* never a column on its own (you cannot materialize a value of it) — it only
|
|
6
|
+
* appears wrapped, as the inferred element of an untyped literal:
|
|
7
|
+
*
|
|
8
|
+
* [] -> Array(Nothing) -> always the empty array (varint length 0x00)
|
|
9
|
+
* NULL -> Nullable(Nothing) -> always NULL (lone flag byte 0x01)
|
|
10
|
+
*
|
|
11
|
+
* So a `Nothing` value is NEVER read: `readArray`'s element reader and
|
|
12
|
+
* `readNullable`'s inner reader are not called in those cases (the array is
|
|
13
|
+
* empty / the value is NULL). There is nothing to decode.
|
|
14
|
+
*
|
|
15
|
+
* Wire this in as the inner reader to make that invariant loud: it throws if it
|
|
16
|
+
* is ever actually invoked, which would mean a `Nothing` reader was placed where
|
|
17
|
+
* a real element/inner type was expected.
|
|
18
|
+
*
|
|
19
|
+
* readArray(readNothing) // [] — readNothing never runs
|
|
20
|
+
* readNullable(readNothing) // null — readNothing never runs
|
|
21
|
+
*/
|
|
22
|
+
export const readNothing: Reader<never> = () => {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"RowBinary: Nothing is zero-width and is never decoded — it only appears as " +
|
|
25
|
+
"an empty Array(Nothing) or a NULL Nullable(Nothing), where the inner reader " +
|
|
26
|
+
"is not called. Reaching here means a Nothing reader was wired where a real " +
|
|
27
|
+
"element/inner type was expected.",
|
|
28
|
+
);
|
|
29
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel re-export of the RowBinary reader, split by type family into the
|
|
3
|
+
* sibling modules. Import from here for everything in one place, or from a
|
|
4
|
+
* specific module (e.g. `./integers.js`, `./strings.js`) to pull in only the
|
|
5
|
+
* sub-parsers a given result actually needs — the latter is what a generated
|
|
6
|
+
* parser should do, copying just the modules its column types require.
|
|
7
|
+
*
|
|
8
|
+
* - core — Cursor, Reader<T>, advance, NeedMoreData
|
|
9
|
+
* - varint — readUVarint
|
|
10
|
+
* - integers — readUInt8..readUInt256, readInt8..readInt256
|
|
11
|
+
* - bool / enums / floats
|
|
12
|
+
* - decimals — DecimalValue, formatDecimal, readDecimal32..256
|
|
13
|
+
* - strings — readString, readFixedString, readFixedStringBytes
|
|
14
|
+
* - uuid — readUUID(+BigInt/HiLo), formatUUID(+Table)
|
|
15
|
+
* - ip — readIPv4/6, formatIPv4/6
|
|
16
|
+
* - datetime / time / interval
|
|
17
|
+
* - composite — readArray/Map/Tuple/TupleNamed/Nullable/Variant/QBit
|
|
18
|
+
* - rows — readRows
|
|
19
|
+
* - geo — Point, readPoint/Ring/LineString/Polygon/MultiLineString/MultiPolygon/Geometry
|
|
20
|
+
* - dynamic — readDynamic, readDynamicType
|
|
21
|
+
* - json — readJSON
|
|
22
|
+
* - stream — streamRowBatches, coalesceChunks
|
|
23
|
+
* - transparent / special wrappers (mostly documentation; see each file):
|
|
24
|
+
* lowCardinality (readLowCardinality), simpleAggregateFunction
|
|
25
|
+
* (readSimpleAggregateFunction), nested (readNested), nothing (readNothing),
|
|
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
|
|
41
|
+
*/
|
|
42
|
+
export * from "./core.js";
|
|
43
|
+
export * from "./varint.js";
|
|
44
|
+
export * from "./integers.js";
|
|
45
|
+
export * from "./bool.js";
|
|
46
|
+
export * from "./enums.js";
|
|
47
|
+
export * from "./floats.js";
|
|
48
|
+
export * from "./decimals.js";
|
|
49
|
+
export * from "./strings.js";
|
|
50
|
+
export * from "./uuid.js";
|
|
51
|
+
export * from "./ip.js";
|
|
52
|
+
export * from "./datetime.js";
|
|
53
|
+
export * from "./time.js";
|
|
54
|
+
export * from "./interval.js";
|
|
55
|
+
export * from "./composite.js";
|
|
56
|
+
export * from "./rows.js";
|
|
57
|
+
export * from "./geo.js";
|
|
58
|
+
export * from "./dynamic.js";
|
|
59
|
+
export * from "./json.js";
|
|
60
|
+
export * from "./stream.js";
|
|
61
|
+
export * from "./lowCardinality.js";
|
|
62
|
+
export * from "./simpleAggregateFunction.js";
|
|
63
|
+
export * from "./nested.js";
|
|
64
|
+
export * from "./nothing.js";
|
|
65
|
+
export * from "./aggregateFunction.js";
|
|
66
|
+
export * from "./header.js";
|
|
67
|
+
export * from "./compile.js";
|
|
68
|
+
export * from "./rowBinaryWithNamesAndTypes.js";
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `RowBinaryWithNamesAndTypes` entry point: read the header off a cursor,
|
|
3
|
+
* compile each column's type string into a reader, and hand back a driver that
|
|
4
|
+
* decodes the rest of the stream.
|
|
5
|
+
*
|
|
6
|
+
* This ties together the pieces: {@link readHeader} (wire), the parser
|
|
7
|
+
* (`@clickhouse/datatype-parser`), and {@link astToReader} (the AST → reader
|
|
8
|
+
* fold in `compile.ts`), then assembles a named-tuple row reader over the
|
|
9
|
+
* columns and a {@link readRows} driver for the row data.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { parseDataType } from "@clickhouse/datatype-parser";
|
|
13
|
+
|
|
14
|
+
import type { Reader, Cursor } from "./core.js";
|
|
15
|
+
import { readHeader } from "./header.js";
|
|
16
|
+
import { readRows } from "./rows.js";
|
|
17
|
+
import { astToReader, RowBinaryTypeError } from "./compile.js";
|
|
18
|
+
|
|
19
|
+
/** One decoded row, keyed by column name. */
|
|
20
|
+
export type Row = Record<string, unknown>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The product of compiling a `RowBinaryWithNamesAndTypes` header: the column
|
|
24
|
+
* metadata, the per-column readers, and — the headline — `readRows`, the
|
|
25
|
+
* {@link Reader} that decodes every remaining row of the stream.
|
|
26
|
+
*/
|
|
27
|
+
export interface CompiledStream {
|
|
28
|
+
/** Column names, in stream order (from the header). */
|
|
29
|
+
names: string[];
|
|
30
|
+
/** Column type strings, in stream order (from the header). */
|
|
31
|
+
types: string[];
|
|
32
|
+
/** One folded reader per column, in stream order. */
|
|
33
|
+
columnReaders: Reader<unknown>[];
|
|
34
|
+
/** Reads exactly one row into a `{ [name]: value }` object. */
|
|
35
|
+
readRow: Reader<Row>;
|
|
36
|
+
/**
|
|
37
|
+
* Reads the REST of the stream (all rows after the header) into an array.
|
|
38
|
+
* Streaming-aware via {@link readRows}: on a partial trailing row it rewinds
|
|
39
|
+
* to the last complete row and returns what it has.
|
|
40
|
+
*/
|
|
41
|
+
readRows: Reader<Row[]>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse one ClickHouse type string and fold it into a {@link Reader}. Throws a
|
|
46
|
+
* {@link RowBinaryTypeError} if the parser rejects the string (e.g. the
|
|
47
|
+
* deliberately unsupported `AggregateFunction` / `SimpleAggregateFunction`) —
|
|
48
|
+
* carrying the `typeString` and the parse `position`.
|
|
49
|
+
*/
|
|
50
|
+
export function typeStringToReader(typeStr: string): Reader<unknown> {
|
|
51
|
+
const result = parseDataType(typeStr);
|
|
52
|
+
if (!result.ok()) {
|
|
53
|
+
const err = result.error!;
|
|
54
|
+
throw new RowBinaryTypeError(
|
|
55
|
+
`cannot compile type ${JSON.stringify(typeStr)}: ${err.message}`,
|
|
56
|
+
{ typeString: typeStr, position: err.position },
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return astToReader(result.ast!);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Resolves a ClickHouse type string to a reader — `typeStringToReader` or a cache wrapping it. */
|
|
63
|
+
export type TypeReaderResolver = (typeStr: string) => Reader<unknown>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Build an LRU-cached {@link typeStringToReader}. The full ClickHouse type
|
|
67
|
+
* STRING is a perfect cache key: two columns of the same type compile to the
|
|
68
|
+
* same reader, and a reader is stateless (it only ever touches the cursor it is
|
|
69
|
+
* handed), so one instance is safe to share across columns and across streams —
|
|
70
|
+
* a cache hit skips the parse + AST fold entirely.
|
|
71
|
+
*
|
|
72
|
+
* Worth it when you decode many `RowBinaryWithNamesAndTypes` responses whose
|
|
73
|
+
* schemas overlap (e.g. the same query run repeatedly): keep one cache and pass
|
|
74
|
+
* it to {@link compileRowBinaryWithNamesAndTypes}, so a recurring type is
|
|
75
|
+
* compiled once rather than once per response. A single response rarely repeats
|
|
76
|
+
* a type across its own columns, so the win is across calls, not within one.
|
|
77
|
+
*
|
|
78
|
+
* Classic Map-based LRU: a `Map` iterates in insertion order, so on a HIT we
|
|
79
|
+
* delete + re-set the entry to move it to the most-recently-used end, and on
|
|
80
|
+
* overflow we evict the oldest key (the first the `Map` yields). `maxSize` caps
|
|
81
|
+
* memory. A parse FAILURE is never cached — {@link typeStringToReader} throws
|
|
82
|
+
* before anything is stored — so fixing a bad type is not shadowed by a cached
|
|
83
|
+
* error.
|
|
84
|
+
*/
|
|
85
|
+
export function createTypeReaderCache(maxSize = 256): TypeReaderResolver {
|
|
86
|
+
const cache = new Map<string, Reader<unknown>>();
|
|
87
|
+
return (typeStr) => {
|
|
88
|
+
const cached = cache.get(typeStr);
|
|
89
|
+
if (cached !== undefined) {
|
|
90
|
+
// Touch on hit. A Map iterates in INSERTION order, not usage order — so on
|
|
91
|
+
// its own `keys().next()` would give the oldest-added key, not the
|
|
92
|
+
// least-recently-USED one. Deleting and re-inserting moves this key to the
|
|
93
|
+
// tail, which is what turns insertion order INTO recency order: every
|
|
94
|
+
// access (hit here, or miss below) lands the key at the tail, leaving the
|
|
95
|
+
// head as the genuine least-recently-used entry.
|
|
96
|
+
cache.delete(typeStr);
|
|
97
|
+
cache.set(typeStr, cached);
|
|
98
|
+
return cached;
|
|
99
|
+
}
|
|
100
|
+
const reader = typeStringToReader(typeStr); // may throw — then nothing is cached
|
|
101
|
+
cache.set(typeStr, reader);
|
|
102
|
+
if (cache.size > maxSize) {
|
|
103
|
+
// The head is the least-recently-used key (see touch-on-hit above), so it
|
|
104
|
+
// is the correct one to evict.
|
|
105
|
+
const lru = cache.keys().next().value;
|
|
106
|
+
if (lru !== undefined) cache.delete(lru);
|
|
107
|
+
}
|
|
108
|
+
return reader;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The headline entry point. Reads the `RowBinaryWithNamesAndTypes` header off
|
|
114
|
+
* `state`, compiles each column type into a combinator reader, and returns the
|
|
115
|
+
* column metadata plus the readers — including `readRows`, the reader for the
|
|
116
|
+
* REST of the stream. After this call the cursor sits at the first row, so:
|
|
117
|
+
*
|
|
118
|
+
* const s = new Cursor(buf);
|
|
119
|
+
* const { names, readRows } = compileRowBinaryWithNamesAndTypes(s);
|
|
120
|
+
* const rows = readRows(s); // decode every remaining row
|
|
121
|
+
*
|
|
122
|
+
* Pass `resolveType` to reuse readers across calls — e.g. a shared
|
|
123
|
+
* {@link createTypeReaderCache}. It defaults to {@link typeStringToReader}
|
|
124
|
+
* (compile every column afresh).
|
|
125
|
+
*/
|
|
126
|
+
export function compileRowBinaryWithNamesAndTypes(
|
|
127
|
+
state: Cursor,
|
|
128
|
+
resolveType: TypeReaderResolver = typeStringToReader,
|
|
129
|
+
): CompiledStream {
|
|
130
|
+
const { names, types } = readHeader(state);
|
|
131
|
+
const columnReaders = types.map((t) => resolveType(t));
|
|
132
|
+
|
|
133
|
+
// Build the row reader POSITIONALLY — by column index, NOT by keying the
|
|
134
|
+
// readers on column name and handing them to `readTupleNamed`. The header is
|
|
135
|
+
// an ordered list and RowBinary has no row delimiter, so every row MUST read
|
|
136
|
+
// exactly these readers, in exactly this order. Keying readers by name first
|
|
137
|
+
// would corrupt the stream on legal-but-awkward headers:
|
|
138
|
+
// - duplicate column names (e.g. two `SELECT 1 AS x, 2 AS x`) collapse to a
|
|
139
|
+
// single entry in a `Record`, so fewer readers run than there are columns;
|
|
140
|
+
// - integer-like names (`0`, `1`, …) are reordered ahead of string keys by
|
|
141
|
+
// `Object.keys()`, so the readers would run out of header order.
|
|
142
|
+
// Either desyncs the cursor and misreads every subsequent row. Reading by
|
|
143
|
+
// index sidesteps both. The row OBJECT is still keyed by name; on a duplicate
|
|
144
|
+
// name the last column with that name wins in the object, but every column is
|
|
145
|
+
// still consumed off the wire in order, so the cursor stays in sync.
|
|
146
|
+
const readRow: Reader<Row> = (s) => {
|
|
147
|
+
const row: Row = {};
|
|
148
|
+
for (let i = 0; i < columnReaders.length; i++) {
|
|
149
|
+
row[names[i]!] = columnReaders[i]!(s);
|
|
150
|
+
}
|
|
151
|
+
return row;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return { names, types, columnReaders, readRow, readRows: readRows(readRow) };
|
|
155
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { NeedMoreData, type Reader } from "./core.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Drive `readRow` over every row of a plain `RowBinary` result into an array.
|
|
5
|
+
* Curried: `readRows(readRow)` returns a `Reader<T[]>`. Rows are concatenated on
|
|
6
|
+
* the wire with no count, length prefix, or delimiter, so the result is exhausted
|
|
7
|
+
* only when the cursor reaches the buffer end.
|
|
8
|
+
*
|
|
9
|
+
* `readRow` must consume EXACTLY one row's bytes — a byte short or long compounds
|
|
10
|
+
* across rows and the cursor overshoots or never lands on `buf.length`. Returns
|
|
11
|
+
* `[]` for an empty buffer. When generating code, inline the per-column reads
|
|
12
|
+
* into the loop body:
|
|
13
|
+
*
|
|
14
|
+
* function readRowsUser(s) {
|
|
15
|
+
* const out = [];
|
|
16
|
+
* while (s.pos < s.buf.length) {
|
|
17
|
+
* out.push({ id: readUInt64(s), name: readString(s) });
|
|
18
|
+
* }
|
|
19
|
+
* return out;
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* STREAMING (partial trailing row): a chunk of a still-arriving response may end
|
|
23
|
+
* mid-row. `pos` is committed only AFTER a row reads cleanly, so when a row
|
|
24
|
+
* starves and `readRow` throws {@link NeedMoreData}, this catches it, rewinds
|
|
25
|
+
* `pos` to the last complete row boundary, and returns the rows so far — never a
|
|
26
|
+
* half-built row. The cursor is left at the straddling row, a commit point the
|
|
27
|
+
* driver carries forward:
|
|
28
|
+
*
|
|
29
|
+
* const drive = readRows(readRow);
|
|
30
|
+
* let committed = 0;
|
|
31
|
+
* for (const chunk of chunks) { // chunk = growing prefix
|
|
32
|
+
* const s = new Cursor(chunk);
|
|
33
|
+
* s.pos = committed;
|
|
34
|
+
* emit(drive(s)); // complete rows in this chunk
|
|
35
|
+
* committed = s.pos; // start of the straddling row
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* On a complete buffer no read starves, so the catch never runs. Errors other
|
|
39
|
+
* than {@link NeedMoreData} are real decode faults and propagate. See also
|
|
40
|
+
* `streamRowBatches`, the async driver built on this.
|
|
41
|
+
*/
|
|
42
|
+
export function readRows<T>(readRow: Reader<T>): Reader<T[]> {
|
|
43
|
+
return (state) => {
|
|
44
|
+
const out: T[] = [];
|
|
45
|
+
let committed = state.pos;
|
|
46
|
+
try {
|
|
47
|
+
while (state.pos < state.buf.length) {
|
|
48
|
+
const row = readRow(state);
|
|
49
|
+
committed = state.pos; // row read cleanly — advance the commit point
|
|
50
|
+
out.push(row);
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
if (e !== NeedMoreData) throw e;
|
|
54
|
+
state.pos = committed; // drop the partial trailing row; resume next chunk
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Reader } from "./core.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `SimpleAggregateFunction(func, T)` is TRANSPARENT in RowBinary: the column
|
|
5
|
+
* already holds a finished value of the underlying type `T` (the partial
|
|
6
|
+
* aggregate of a "simple" function — sum / min / max / groupArrayArray / … — is
|
|
7
|
+
* just a value of `T`), so it is encoded byte-for-byte the same as `T`. Decode
|
|
8
|
+
* the inner `T` directly.
|
|
9
|
+
*
|
|
10
|
+
* Do NOT confuse it with `AggregateFunction(func, T)`, whose value is an opaque
|
|
11
|
+
* serialized aggregation STATE with a function-specific binary layout — see
|
|
12
|
+
* `./aggregateFunction.js`.
|
|
13
|
+
*
|
|
14
|
+
* Identity combinator, documentation only:
|
|
15
|
+
*
|
|
16
|
+
* readSimpleAggregateFunction(readUInt64) === readUInt64
|
|
17
|
+
*/
|
|
18
|
+
export const readSimpleAggregateFunction = <T>(
|
|
19
|
+
readValue: Reader<T>,
|
|
20
|
+
): Reader<T> => readValue;
|