Setting the file. One moment.
Composite · Clickhouse Js Node Rowbinary · ClickHouse/agent-skills · Skills Docs
ContentsBack to the top of the page Previous
Bundled file Compile
src/readers/ composite.ts
TypeScript · 181 lines · 7 KB
* 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 }