@clickhouse/client 1.22.0 → 1.23.0-head.c8dc8d8.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/README.md +2 -1
- package/dist/client.d.ts +2 -2
- package/dist/client.js +3 -3
- 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 +127 -0
- package/dist/common/parse/column_types.js +586 -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 +1990 -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 +2 -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 +7 -7
- package/dist/index.js +24 -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 +7 -5
- package/skills/clickhouse-js-node-rowbinary-parser/EXAMPLES.md +48 -0
- package/skills/clickhouse-js-node-rowbinary-parser/README.md +248 -0
- package/skills/clickhouse-js-node-rowbinary-parser/SKILL.md +190 -0
- package/skills/clickhouse-js-node-rowbinary-parser/case-studies/iot-rowbinary-vs-json.md +83 -0
- package/skills/clickhouse-js-node-rowbinary-parser/case-studies/ledger-rowbinary-vs-json.md +103 -0
- package/skills/clickhouse-js-node-rowbinary-parser/case-studies/logs-json-wins.md +86 -0
- package/skills/clickhouse-js-node-rowbinary-parser/case-studies/wasm-vs-js.md +172 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/aggregateFunction.ts +34 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/bool.ts +10 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/columnar.ts +125 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/composite.ts +181 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/core.ts +77 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/datetime.ts +113 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/decimals.ts +57 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/dynamic.ts +328 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/enums.ts +28 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/examples/carts.ts +71 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/examples/events.ts +51 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/examples/iot.ts +158 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/examples/ledger.ts +98 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/examples/logs.ts +73 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/examples/observability.ts +142 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/examples/orders.ts +65 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/examples/profiles.ts +60 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/examples/telemetry.ts +102 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/floats.ts +32 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/geo.ts +109 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/integers.ts +95 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/interval.ts +54 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/ip.ts +93 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/json.ts +33 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/lowCardinality.ts +18 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/nested.ts +23 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/nothing.ts +29 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/reader.ts +51 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/rows.ts +58 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/simpleAggregateFunction.ts +20 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/stream.ts +276 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/strings.ts +55 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/time.ts +61 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/uuid.ts +153 -0
- package/skills/clickhouse-js-node-rowbinary-parser/src/varint.ts +70 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { type Reader } from "./core.js";
|
|
2
|
+
import { readUInt8 } from "./integers.js";
|
|
3
|
+
import { readUVarint } from "./varint.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read a `Nullable(T)`: a 1-byte null flag (0 = present, non-zero = NULL).
|
|
7
|
+
* Curried: pass the inner reader, get a `Reader<T | null>`.
|
|
8
|
+
*
|
|
9
|
+
* GOTCHA: the inner value bytes follow ONLY when the flag is 0. A NULL is the
|
|
10
|
+
* single `0x01` flag byte with nothing after it — so do NOT read the inner value
|
|
11
|
+
* when the flag is set, or the cursor desyncs.
|
|
12
|
+
*
|
|
13
|
+
* `readValue` decodes the inner `T`. This generic combinator is the reference
|
|
14
|
+
* shape; when generating code, MONOMORPHIZE — emit a dedicated `readNullableX`
|
|
15
|
+
* that inlines the inner read:
|
|
16
|
+
*
|
|
17
|
+
* const readNullableUInt32 = (s) => readUInt8(s) !== 0 ? null : readUInt32(s);
|
|
18
|
+
*/
|
|
19
|
+
export function readNullable<T>(readValue: Reader<T>): Reader<T | null> {
|
|
20
|
+
return (state) => (readUInt8(state) !== 0 ? null : readValue(state));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read an `Array(T)`: a LEB128 element count, then that many `T` values
|
|
25
|
+
* back-to-back. An empty array is just the count byte `0x00`. Curried: pass the
|
|
26
|
+
* element reader, get a `Reader<T[]>`.
|
|
27
|
+
*
|
|
28
|
+
* ARRAY LAYOUT: the count is known up front (the LEB128 prefix), so a generated
|
|
29
|
+
* reader can pre-size. Pick by how the result is used:
|
|
30
|
+
* - small / consumed-as-is (the common case) → DEFAULT to `new Array(n)` +
|
|
31
|
+
* index assignment; it skips `push`'s repeated capacity growth. A clean-room
|
|
32
|
+
* benchmark found this edged out `push` on the small composite arrays here
|
|
33
|
+
* (`baseline/README.md`).
|
|
34
|
+
* - large + iterated/computed-over downstream → `[]` + `push` keeps it a PACKED
|
|
35
|
+
* elements kind (faster to traverse; a pre-sized array is HOLEY), or use a
|
|
36
|
+
* typed array (`Float64Array`…) for numeric elements.
|
|
37
|
+
* This generic combinator uses `push` for simplicity; the monomorphized
|
|
38
|
+
* `readArrayX` below should choose per the rule above.
|
|
39
|
+
*
|
|
40
|
+
* `readElement` decodes one element. This generic combinator is the reference
|
|
41
|
+
* shape; when generating code, MONOMORPHIZE — emit a dedicated `readArrayX` that
|
|
42
|
+
* inlines the element read in the loop (and pre-sizes for the common small case):
|
|
43
|
+
*
|
|
44
|
+
* function readArrayUInt32(s) {
|
|
45
|
+
* const n = readUVarint(s);
|
|
46
|
+
* const out = new Array(n);
|
|
47
|
+
* for (let i = 0; i < n; i++) out[i] = readUInt32(s);
|
|
48
|
+
* return out;
|
|
49
|
+
* }
|
|
50
|
+
*/
|
|
51
|
+
export function readArray<T>(readElement: Reader<T>): Reader<T[]> {
|
|
52
|
+
return (state) => {
|
|
53
|
+
const n = readUVarint(state);
|
|
54
|
+
const out: T[] = [];
|
|
55
|
+
for (let i = 0; i < n; i++) out.push(readElement(state));
|
|
56
|
+
return out;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Read a `QBit(element_type, dimension)` vector. `QBit` is a vector-search type
|
|
62
|
+
* whose ON-DISK layout is quantized and bit-transposed — but that is a STORAGE /
|
|
63
|
+
* Native-format concern. In RowBinary a `QBit` is fully TRANSPARENT: it is the
|
|
64
|
+
* plain vector, encoded byte-for-byte like `Array(element_type)` (a LEB128
|
|
65
|
+
* length, then `dimension` element values). So this is just {@link readArray}.
|
|
66
|
+
*
|
|
67
|
+
* `element_type` is one of `BFloat16` / `Float32` / `Float64`, so `readElement`
|
|
68
|
+
* is the matching float reader. When generating code, MONOMORPHIZE — inline the
|
|
69
|
+
* element read in the loop.
|
|
70
|
+
*/
|
|
71
|
+
export function readQBit<T>(readElement: Reader<T>): Reader<T[]> {
|
|
72
|
+
return readArray(readElement);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Read a `Tuple(...)` into a positional array: each element's value back-to-back,
|
|
77
|
+
* with NO count and NO delimiter. Curried: pass one reader per element (in
|
|
78
|
+
* order), get a `Reader` of the tuple. For a named tuple as an object, use
|
|
79
|
+
* {@link readTupleNamed} (identical wire).
|
|
80
|
+
*
|
|
81
|
+
* Reference shape; when generating code, MONOMORPHIZE — emit the inline sequence
|
|
82
|
+
* with no array-of-readers and no loop:
|
|
83
|
+
*
|
|
84
|
+
* [readUInt32(s), readString(s)]
|
|
85
|
+
*/
|
|
86
|
+
export function readTuple<T extends readonly unknown[]>(readers: {
|
|
87
|
+
[K in keyof T]: Reader<T[K]>;
|
|
88
|
+
}): Reader<T> {
|
|
89
|
+
return (state) => {
|
|
90
|
+
const out: unknown[] = [];
|
|
91
|
+
for (const read of readers as ReadonlyArray<Reader<unknown>>) {
|
|
92
|
+
out.push(read(state));
|
|
93
|
+
}
|
|
94
|
+
return out as unknown as T;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Read a named `Tuple(name1 T1, ...)` into an object. The wire is identical to
|
|
100
|
+
* an unnamed tuple — values back-to-back, no count or delimiter — so the
|
|
101
|
+
* `readers` object's keys MUST be listed in the tuple's declared field order
|
|
102
|
+
* (JS iterates string keys in insertion order), and each reader runs in that
|
|
103
|
+
* order. Curried: pass the readers object, get a `Reader` of the result object.
|
|
104
|
+
*
|
|
105
|
+
* Reference shape; when generating code, MONOMORPHIZE — emit the inline object
|
|
106
|
+
* literal instead of looping over entries:
|
|
107
|
+
*
|
|
108
|
+
* { id: readUInt32(s), name: readString(s) }
|
|
109
|
+
*/
|
|
110
|
+
export function readTupleNamed<T extends Record<string, unknown>>(readers: {
|
|
111
|
+
[K in keyof T]: Reader<T[K]>;
|
|
112
|
+
}): Reader<T> {
|
|
113
|
+
const fns = readers as Record<string, Reader<unknown>>;
|
|
114
|
+
const keys = Object.keys(fns);
|
|
115
|
+
return (state) => {
|
|
116
|
+
const out: Record<string, unknown> = {};
|
|
117
|
+
for (const key of keys) out[key] = fns[key]!(state);
|
|
118
|
+
return out as T;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Read a `Map(K, V)`: a LEB128 pair count, then that many key/value pairs with
|
|
124
|
+
* key and value interleaved (k, v, k, v, ...) — a flattened `Array(Tuple(K, V))`.
|
|
125
|
+
* An empty map is just the count byte `0x00`. Curried: pass the key and value
|
|
126
|
+
* readers, get a `Reader<Map<K, V>>`.
|
|
127
|
+
*
|
|
128
|
+
* The key is read BEFORE the value in each pair. Returns a JS `Map`, which keeps
|
|
129
|
+
* insertion order and accepts any key type.
|
|
130
|
+
*
|
|
131
|
+
* Reference shape; when generating code, MONOMORPHIZE — inline both reads in the
|
|
132
|
+
* loop.
|
|
133
|
+
*/
|
|
134
|
+
export function readMap<K, V>(
|
|
135
|
+
readKey: Reader<K>,
|
|
136
|
+
readValue: Reader<V>,
|
|
137
|
+
): Reader<Map<K, V>> {
|
|
138
|
+
return (state) => {
|
|
139
|
+
const n = readUVarint(state);
|
|
140
|
+
const out = new Map<K, V>();
|
|
141
|
+
for (let i = 0; i < n; i++) {
|
|
142
|
+
const key = readKey(state);
|
|
143
|
+
out.set(key, readValue(state));
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Read a `Variant(T1, ..., Tn)`: a 1-byte discriminant selecting the active
|
|
151
|
+
* alternative, then that alternative's value. Discriminant `0xFF` means NULL.
|
|
152
|
+
* Curried: pass the alternative readers (in sorted-type-name order), get a
|
|
153
|
+
* `Reader`.
|
|
154
|
+
*
|
|
155
|
+
* GOTCHA: the discriminant indexes the alternatives sorted by type NAME
|
|
156
|
+
* (ClickHouse globally sorts them), NOT their declaration order. So `readers`
|
|
157
|
+
* MUST be ordered by sorted type name. E.g. `Variant(UInt8, String)` sorts to
|
|
158
|
+
* ["String", "UInt8"], so discriminant 0 = String and 1 = UInt8.
|
|
159
|
+
*
|
|
160
|
+
* Reference shape; when generating code, MONOMORPHIZE — emit a `switch` over the
|
|
161
|
+
* discriminant with each branch inlined, alternatives in sorted order, `0xFF`
|
|
162
|
+
* -> null.
|
|
163
|
+
*/
|
|
164
|
+
export function readVariant<T extends readonly unknown[]>(readers: {
|
|
165
|
+
[K in keyof T]: Reader<T[K]>;
|
|
166
|
+
}): Reader<T[number] | null> {
|
|
167
|
+
const fns = readers as ReadonlyArray<Reader<T[number]>>;
|
|
168
|
+
return (state) => {
|
|
169
|
+
const discriminant = readUInt8(state);
|
|
170
|
+
if (discriminant === 0xff) return null;
|
|
171
|
+
const fn = fns[discriminant];
|
|
172
|
+
if (fn === undefined) {
|
|
173
|
+
// Out-of-range discriminant (corrupted/truncated input): fail loudly
|
|
174
|
+
// instead of throwing a cryptic "fns[discriminant] is not a function".
|
|
175
|
+
throw new RangeError(
|
|
176
|
+
`RowBinary Variant: discriminant ${discriminant} out of range (${fns.length} alternatives)`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return fn(state);
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by {@link advance} when the buffer lacks the bytes a read needs — the
|
|
3
|
+
* "need more bytes" signal for incremental decoding over a still-filling buffer.
|
|
4
|
+
* A driver catches it (`err === NeedMoreData`), waits for more input, and retries
|
|
5
|
+
* the row from its last committed position.
|
|
6
|
+
*
|
|
7
|
+
* A bare sentinel, NOT an `Error` subclass, on purpose: constructing an Error
|
|
8
|
+
* captures a stack trace — the expensive part of throwing — and on a path that
|
|
9
|
+
* starves once per chunk that cost is pure waste. Throwing a constant skips it,
|
|
10
|
+
* which is why throw + restart beats a generator's yield for realistic chunks
|
|
11
|
+
* (see `streamingRow.bench.ts`).
|
|
12
|
+
*/
|
|
13
|
+
export const NeedMoreData = Symbol("RowBinary.NeedMoreData");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The cursor state every reader threads through: the input `Buffer`, the current
|
|
17
|
+
* position, and a `DataView` over the same bytes.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately STATE only — no read methods. Decoding lives in the free
|
|
20
|
+
* `readX(state, ...)` functions in the sibling modules, so a generated parser
|
|
21
|
+
* pulls in only the per-type readers a result needs. `view`/`buf` are public so
|
|
22
|
+
* those free functions can reach them.
|
|
23
|
+
*/
|
|
24
|
+
export class Cursor {
|
|
25
|
+
pos = 0;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Node-only skill, so the input is a `Buffer`: number reads go through
|
|
29
|
+
* {@link Cursor.view} (DataView), while `String`/`FixedString` use the
|
|
30
|
+
* fast `buf.toString("utf8", ...)`.
|
|
31
|
+
*/
|
|
32
|
+
readonly buf: Buffer;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `DataView` over the same bytes, for fixed-width integer/float reads. Built
|
|
36
|
+
* with the buffer's own `byteOffset`/`byteLength`: a `Buffer` is often a window
|
|
37
|
+
* into a larger pooled `ArrayBuffer`, so `new DataView(buf.buffer)` alone would
|
|
38
|
+
* point at the wrong bytes.
|
|
39
|
+
*/
|
|
40
|
+
readonly view: DataView;
|
|
41
|
+
|
|
42
|
+
constructor(buf: Buffer) {
|
|
43
|
+
this.buf = buf;
|
|
44
|
+
this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A `Reader<T>` decodes one value of type `T` from the cursor, advancing it. Leaf
|
|
50
|
+
* readers (e.g. `readUInt32`) are `Reader`s directly; combinators (e.g.
|
|
51
|
+
* `readArray`) take sub-`Reader`s and return a `Reader`, so types compose with no
|
|
52
|
+
* per-element closures.
|
|
53
|
+
*/
|
|
54
|
+
export type Reader<T> = (state: Cursor) => T;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Reserve `n` bytes for the next read: bounds-check them, advance the cursor past
|
|
58
|
+
* them, and return the offset the read starts at (the value BEFORE advancing).
|
|
59
|
+
* Every fixed-width read goes through this, so the length check and cursor
|
|
60
|
+
* bookkeeping live in one place:
|
|
61
|
+
*
|
|
62
|
+
* function readInt32(s) { return s.view.getInt32(advance(s, 4), true); }
|
|
63
|
+
*
|
|
64
|
+
* Throws {@link NeedMoreData} when fewer than `n` bytes remain, WITHOUT moving the
|
|
65
|
+
* cursor, so a driver can rewind to its last committed row and retry.
|
|
66
|
+
*
|
|
67
|
+
* SAFE TO TOGGLE: for a complete in-memory buffer the check never fires — a parser
|
|
68
|
+
* for that case can drop `advance` and read against `state.pos` directly, trading
|
|
69
|
+
* streaming tolerance for one fewer compare per read.
|
|
70
|
+
*/
|
|
71
|
+
export function advance(state: Cursor, n: number): number {
|
|
72
|
+
const start = state.pos;
|
|
73
|
+
const next = start + n;
|
|
74
|
+
if (next > state.buf.length) throw NeedMoreData;
|
|
75
|
+
state.pos = next;
|
|
76
|
+
return start;
|
|
77
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { type Reader, Cursor } from "./core.js";
|
|
2
|
+
import { readInt32, readInt64, readUInt16, readUInt32 } from "./integers.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Semantic aliases for `number` that mark the unit of a temporal value in a
|
|
6
|
+
* return type. They are plain `number`s (no runtime brand) — purely to make
|
|
7
|
+
* `[Date, Nanoseconds]` etc. self-documenting at the call site.
|
|
8
|
+
*/
|
|
9
|
+
export type Milliseconds = number;
|
|
10
|
+
export type Microseconds = number;
|
|
11
|
+
export type Nanoseconds = number;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Read a `Date`: 2-byte `UInt16` count of days since 1970-01-01 (UTC), returned
|
|
15
|
+
* as a JS `Date` at UTC midnight. A ClickHouse `Date` has no time or timezone;
|
|
16
|
+
* `.toISOString().slice(0, 10)` gives "YYYY-MM-DD".
|
|
17
|
+
*
|
|
18
|
+
* SAFE TO TOGGLE: a `Date` is an object allocation per value. On a hot path that
|
|
19
|
+
* only needs the calendar number, read the raw `UInt16` (days) instead.
|
|
20
|
+
*/
|
|
21
|
+
export function readDate(state: Cursor): Date {
|
|
22
|
+
return new Date(readUInt16(state) * 86_400_000);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Read a `Date32`: 4-byte signed `Int32` count of days since 1970-01-01 (UTC),
|
|
27
|
+
* returned as a JS `Date` at UTC midnight (pre-1970 dates are negative day
|
|
28
|
+
* counts, which `Date` handles).
|
|
29
|
+
*/
|
|
30
|
+
export function readDate32(state: Cursor): Date {
|
|
31
|
+
return new Date(readInt32(state) * 86_400_000);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Read a `DateTime` (and `DateTime(tz)`): 4-byte `UInt32` Unix seconds, returned
|
|
36
|
+
* as a JS `Date` (exact at second resolution). The instant is UTC-based; a
|
|
37
|
+
* column's timezone is display metadata, not in the bytes.
|
|
38
|
+
*/
|
|
39
|
+
export function readDateTime(state: Cursor): Date {
|
|
40
|
+
return new Date(readUInt32(state) * 1000);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read a `DateTime64(P)` (and `DateTime64(P, tz)`): 8-byte signed `Int64` count
|
|
45
|
+
* of `10^-P`-second ticks since the epoch. Curried: `readDateTime64(P)` returns
|
|
46
|
+
* the reader.
|
|
47
|
+
*
|
|
48
|
+
* Returns a pair `[date, nanoseconds]`: `date` is a JS `Date` truncated to whole
|
|
49
|
+
* seconds, and `nanoseconds` is the sub-second remainder in nanoseconds
|
|
50
|
+
* (0..999_999_999). The split keeps full precision a `Date` alone (millisecond
|
|
51
|
+
* resolution) can't hold. `nanoseconds` is always in ns regardless of P. Timezone
|
|
52
|
+
* is metadata.
|
|
53
|
+
*
|
|
54
|
+
* For the typical precisions, prefer the specialized variants
|
|
55
|
+
* {@link readDateTime64P3} (ms — returns a plain `Date`),
|
|
56
|
+
* {@link readDateTime64P6} (µs), and {@link readDateTime64P9} (ns).
|
|
57
|
+
*/
|
|
58
|
+
export function readDateTime64(precision: number): Reader<[Date, Nanoseconds]> {
|
|
59
|
+
return (state) => {
|
|
60
|
+
const ticks = readInt64(state);
|
|
61
|
+
const scale = 10n ** BigInt(precision);
|
|
62
|
+
let sec = ticks / scale;
|
|
63
|
+
let frac = ticks % scale;
|
|
64
|
+
if (frac < 0n) {
|
|
65
|
+
// Floor toward -inf so the fractional remainder stays in [0, scale).
|
|
66
|
+
frac += scale;
|
|
67
|
+
sec -= 1n;
|
|
68
|
+
}
|
|
69
|
+
return [new Date(Number(sec) * 1000), Number(frac) * 10 ** (9 - precision)];
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Read a `DateTime64(3)` ({@link Milliseconds}) — the most common precision — as
|
|
75
|
+
* a plain JS `Date`. P=3 is exactly a `Date`'s own millisecond resolution, so the
|
|
76
|
+
* instant is represented losslessly with no separate fraction. Specialized
|
|
77
|
+
* variant of {@link readDateTime64} with the scale baked in.
|
|
78
|
+
*/
|
|
79
|
+
export function readDateTime64P3(state: Cursor): Date {
|
|
80
|
+
return new Date(Number(readInt64(state)));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Read a `DateTime64(6)` (microseconds) as `[date, microseconds]`: a JS `Date`
|
|
85
|
+
* truncated to whole seconds plus the sub-second remainder in microseconds.
|
|
86
|
+
* Specialized variant of {@link readDateTime64}.
|
|
87
|
+
*/
|
|
88
|
+
export function readDateTime64P6(state: Cursor): [Date, Microseconds] {
|
|
89
|
+
const ticks = readInt64(state);
|
|
90
|
+
let sec = ticks / 1_000_000n;
|
|
91
|
+
let frac = ticks % 1_000_000n; // microseconds within the second
|
|
92
|
+
if (frac < 0n) {
|
|
93
|
+
frac += 1_000_000n;
|
|
94
|
+
sec -= 1n;
|
|
95
|
+
}
|
|
96
|
+
return [new Date(Number(sec) * 1000), Number(frac)];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Read a `DateTime64(9)` (nanoseconds) as `[date, nanoseconds]`: a JS `Date`
|
|
101
|
+
* truncated to whole seconds plus the sub-second remainder in nanoseconds.
|
|
102
|
+
* Specialized variant of {@link readDateTime64} with the scale baked in.
|
|
103
|
+
*/
|
|
104
|
+
export function readDateTime64P9(state: Cursor): [Date, Nanoseconds] {
|
|
105
|
+
const ticks = readInt64(state);
|
|
106
|
+
let sec = ticks / 1_000_000_000n;
|
|
107
|
+
let frac = ticks % 1_000_000_000n; // nanoseconds within the second
|
|
108
|
+
if (frac < 0n) {
|
|
109
|
+
frac += 1_000_000_000n;
|
|
110
|
+
sec -= 1n;
|
|
111
|
+
}
|
|
112
|
+
return [new Date(Number(sec) * 1000), Number(frac)];
|
|
113
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Reader } from "./core.js";
|
|
2
|
+
import { readInt32, readInt64, readInt128, readInt256 } from "./integers.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A decimal kept lossless as its raw parts: `value = unscaled / 10 ** scale`.
|
|
6
|
+
* The `readDecimal*` readers return this so no precision or scale information is
|
|
7
|
+
* thrown away at decode time.
|
|
8
|
+
*/
|
|
9
|
+
export type DecimalValue = readonly [unscaled: bigint, scale: number];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format a {@link DecimalValue} as a fixed-point decimal string with `scale`
|
|
13
|
+
* fractional digits (e.g. `[15000n, 4]` -> `"1.5000"`). Plug in only when you
|
|
14
|
+
* need a string.
|
|
15
|
+
*
|
|
16
|
+
* Trailing zeros are preserved to reflect the declared scale, deliberately unlike
|
|
17
|
+
* ClickHouse's text output, which trims them (`"1.5"`) and drops the point for
|
|
18
|
+
* integers (`"10"`).
|
|
19
|
+
*/
|
|
20
|
+
export function formatDecimal([unscaled, scale]: DecimalValue): string {
|
|
21
|
+
if (scale === 0) return unscaled.toString();
|
|
22
|
+
if (unscaled < 0n) {
|
|
23
|
+
const digits = (-unscaled).toString().padStart(scale + 1, "0");
|
|
24
|
+
const point = digits.length - scale;
|
|
25
|
+
return `-${digits.slice(0, point)}.${digits.slice(point)}`;
|
|
26
|
+
}
|
|
27
|
+
const digits = unscaled.toString().padStart(scale + 1, "0");
|
|
28
|
+
const point = digits.length - scale;
|
|
29
|
+
return `${digits.slice(0, point)}.${digits.slice(point)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Read a `Decimal32(P, S)`: a 4-byte little-endian signed integer (same wire
|
|
34
|
+
* shape as `Int32`) scaled by 10^S. Pass the column's scale `S`; returns a
|
|
35
|
+
* `Reader` of the raw `[unscaled, scale]` pair (see {@link formatDecimal}).
|
|
36
|
+
*
|
|
37
|
+
* `Decimal(P, S)` is an alias: pick the width reader by precision P — P<=9 ->
|
|
38
|
+
* Decimal32, <=18 -> Decimal64, <=38 -> Decimal128, <=76 -> Decimal256.
|
|
39
|
+
*/
|
|
40
|
+
export function readDecimal32(scale: number): Reader<DecimalValue> {
|
|
41
|
+
return (state) => [BigInt(readInt32(state)), scale];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Read a `Decimal64(P, S)`: 8-byte LE signed integer scaled by 10^S. Returns `[unscaled, scale]`; see {@link formatDecimal}. */
|
|
45
|
+
export function readDecimal64(scale: number): Reader<DecimalValue> {
|
|
46
|
+
return (state) => [readInt64(state), scale];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Read a `Decimal128(P, S)`: 16-byte LE signed integer scaled by 10^S. Returns `[unscaled, scale]`; see {@link formatDecimal}. */
|
|
50
|
+
export function readDecimal128(scale: number): Reader<DecimalValue> {
|
|
51
|
+
return (state) => [readInt128(state), scale];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Read a `Decimal256(P, S)`: 32-byte LE signed integer scaled by 10^S. Returns `[unscaled, scale]`; see {@link formatDecimal}. */
|
|
55
|
+
export function readDecimal256(scale: number): Reader<DecimalValue> {
|
|
56
|
+
return (state) => [readInt256(state), scale];
|
|
57
|
+
}
|