@clickhouse/client 1.23.0-head.dbc2960.1 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1342 -0
- package/README.md +18 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +7 -6
- 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-parser → clickhouse-js-node-rowbinary}/README.md +78 -14
- package/skills/clickhouse-js-node-rowbinary/SKILL.md +111 -0
- package/skills/{clickhouse-js-node-rowbinary-parser/SKILL.md → clickhouse-js-node-rowbinary/reader.md} +51 -131
- package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/carts.ts +9 -5
- package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/events.ts +5 -5
- package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/iot.ts +4 -4
- package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/ledger.ts +3 -3
- package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/logs.ts +4 -4
- package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/observability.ts +9 -10
- package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/orders.ts +10 -9
- package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/profiles.ts +5 -5
- package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/src/examples/telemetry.ts +6 -6
- package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/compile.ts +18 -8
- package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/dynamic.ts +12 -8
- package/skills/clickhouse-js-node-rowbinary/src/readers/enums.ts +40 -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
- package/skills/clickhouse-js-node-rowbinary-parser/src/enums.ts +0 -28
- /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/EXAMPLES.md +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/iot-rowbinary-vs-json.md +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/ledger-rowbinary-vs-json.md +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/logs-json-wins.md +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser → clickhouse-js-node-rowbinary}/case-studies/wasm-vs-js.md +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/aggregateFunction.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/bool.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/columnar.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/composite.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/core.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/datetime.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/decimals.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/floats.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/geo.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/header.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/integers.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/interval.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/ip.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/json.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/lowCardinality.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/nested.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/nothing.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/reader.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/rowBinaryWithNamesAndTypes.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/rows.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/simpleAggregateFunction.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/stream.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/strings.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/time.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/uuid.ts +0 -0
- /package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/varint.ts +0 -0
|
@@ -1,46 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# ClickHouse JS RowBinary Parser Generator for Node.js
|
|
12
|
-
|
|
13
|
-
## First: is RowBinary even the right format?
|
|
14
|
-
|
|
15
|
-
RowBinary exists for throughput, but it is **not automatically the fastest
|
|
16
|
-
path** — match the format to the shape of the data before committing to a
|
|
17
|
-
bespoke parser.
|
|
18
|
-
|
|
19
|
-
**Prefer a `JSON*` format (e.g. `JSONEachRow`) when** the result is mostly
|
|
20
|
-
strings / JSON-like values that you consume wholesale — randomly accessing
|
|
21
|
-
essentially every field, running string/regexp methods on them, treating values
|
|
22
|
-
as text. V8's native `JSON.parse` is heavily optimized C++ and builds JS strings
|
|
23
|
-
and objects faster than a JS-level RowBinary decoder can; pair it with HTTP
|
|
24
|
-
response compression (`gzip` / `zstd`, which crushes JSON's repetitive keys) and
|
|
25
|
-
the wire cost shrinks too.
|
|
26
|
-
|
|
27
|
-
**RowBinary clearly wins when** the result is dominated by:
|
|
28
|
-
|
|
29
|
-
- **Wide numerics** — `Int128`/`Int256`/`UInt128`/`UInt256`,
|
|
30
|
-
`Decimal128`/`Decimal256`.
|
|
31
|
-
- **Binary / fixed-width blobs** — `IPv4`, `IPv6`, `UUID`, `FixedString`.
|
|
32
|
-
- **High-volume fixed-width numeric columns** generally, where each value is a
|
|
33
|
-
single `DataView` read.
|
|
34
|
-
|
|
35
|
-
**Prefer the `Native` format when** columnar load and client-side analytics are
|
|
36
|
-
the main goal (fold/scan/filter columns, feed typed arrays to a Worker or WASM).
|
|
37
|
-
`Native` is column-major, so it loads straight into one typed array per column
|
|
38
|
-
with no transpose.
|
|
39
|
-
|
|
40
|
-
For help choosing and consuming a `JSON*` format (or CSV / TSV) instead, use the
|
|
41
|
-
**`clickhouse-js-node-coding`** skill.
|
|
42
|
-
|
|
43
|
-
## Second: complete buffer, or incremental stream?
|
|
1
|
+
# RowBinary reader (decode) for Node.js
|
|
2
|
+
|
|
3
|
+
Decoding a `RowBinary` / `RowBinaryWithNames` / `RowBinaryWithNamesAndTypes`
|
|
4
|
+
response from ClickHouse into JS values. Read [SKILL.md](SKILL.md) first for the
|
|
5
|
+
format gate ("is RowBinary even the right format?") and the principles that
|
|
6
|
+
apply to **both** directions; this file covers the decisions and the per-type
|
|
7
|
+
reference specific to **reading**. Writing? See [writer.md](writer.md).
|
|
8
|
+
|
|
9
|
+
## First: complete buffer, or incremental stream?
|
|
44
10
|
|
|
45
11
|
Decide this before writing the reader — it changes the shape of the code and is
|
|
46
12
|
a real performance fork.
|
|
@@ -56,7 +22,7 @@ a real performance fork.
|
|
|
56
22
|
|
|
57
23
|
The exposed API is streaming by default and requires an optimisation pass.
|
|
58
24
|
|
|
59
|
-
##
|
|
25
|
+
## Second: row objects, or columnar (typed arrays)?
|
|
60
26
|
|
|
61
27
|
The default output is one object per row (array-of-structs). For a **numeric,
|
|
62
28
|
fixed-width result that the consumer reads column-wise**, decode instead into one
|
|
@@ -77,56 +43,39 @@ bandwidth). Measured in `tests/iot.columnar.bench.ts`; rationale in
|
|
|
77
43
|
complete-row count is `(chunk.length / stride) | 0`, and the leftover bytes
|
|
78
44
|
carry to the next chunk. Yield one typed-array batch per chunk, each owning a
|
|
79
45
|
fresh transferable `ArrayBuffer` (see `streamSensorColumns` in
|
|
80
|
-
`src/columnar.ts`).
|
|
46
|
+
`src/readers/columnar.ts`).
|
|
81
47
|
- **Stay row-oriented when** downstream code is row-shaped, the row is
|
|
82
48
|
string-dominated (columnar's win is numeric — a JS string allocates either
|
|
83
49
|
way), or the schema is nested/heterogeneous (`Array`/`Map`/`Tuple`).
|
|
84
50
|
- **Hybrid:** store columnar, expose a lazy `rowAt(i)` accessor that builds an
|
|
85
51
|
object only for rows actually touched (see `iotRowAt` in `src/examples/iot.ts`).
|
|
86
52
|
|
|
87
|
-
##
|
|
53
|
+
## Third: are the column types known ahead of time?
|
|
88
54
|
|
|
89
55
|
- **Known (the default).** Generate a straight-line reader specialized to those
|
|
90
56
|
types — everything below.
|
|
91
57
|
- **Only at runtime** (the schema varies, or you just want to decode an arbitrary
|
|
92
58
|
`RowBinaryWithNamesAndTypes` stream). Call
|
|
93
|
-
`compileRowBinaryWithNamesAndTypes(cursor)` (`src/rowBinaryWithNamesAndTypes.ts`):
|
|
59
|
+
`compileRowBinaryWithNamesAndTypes(cursor)` (`src/readers/rowBinaryWithNamesAndTypes.ts`):
|
|
94
60
|
it reads the header, folds each column type's AST into a `Reader`
|
|
95
|
-
(`astToReader`, `src/compile.ts`; type strings parsed by
|
|
61
|
+
(`astToReader`, `src/readers/compile.ts`; type strings parsed by
|
|
96
62
|
`@clickhouse/datatype-parser`), and returns a `readRows` driver for the rest of
|
|
97
63
|
the stream. Generic and unoptimized (no codegen), so prefer the specialized
|
|
98
64
|
path whenever the types are fixed.
|
|
99
65
|
|
|
100
|
-
##
|
|
101
|
-
|
|
102
|
-
When generating a parser, follow these:
|
|
103
|
-
|
|
104
|
-
- **Little-endian only.** RowBinary is little-endian; target x86/ARM. Read every
|
|
105
|
-
multi-byte number with `DataView` accessors passing a **literal** `true` for
|
|
106
|
-
the `littleEndian` flag.
|
|
66
|
+
## Reader guidance
|
|
107
67
|
|
|
108
|
-
|
|
109
|
-
plain per-type API. Only after it's correct (and tested) specialize it. Don't
|
|
110
|
-
bake performance assumptions in before correctness.
|
|
111
|
-
|
|
112
|
-
- **Monomorphize generic/composite types.** Emit specialized, inlined code per
|
|
113
|
-
type combination instead of passing functions as arguments where the type
|
|
114
|
-
is known ahead of time.
|
|
68
|
+
On top of the shared principles in [SKILL.md](SKILL.md), the read path has its own:
|
|
115
69
|
|
|
116
70
|
- **Streaming: throw + restart, not generators.** To signal "need more bytes",
|
|
117
71
|
a synchronous reader that throws a sentinel (`NeedMoreData`) and restarts the
|
|
118
|
-
row beats generators for realistic chunk sizes
|
|
72
|
+
row beats generators for realistic chunk sizes.
|
|
119
73
|
|
|
120
74
|
- **Keep an eye on chunk sizes.** Partial trailing rows, small chunks are a silent
|
|
121
75
|
throughput killer: `streamRowBatches` warns once when
|
|
122
76
|
rows-per-chunk falls too low, and `coalesceChunks(source, { minSize, timeoutMs })`
|
|
123
77
|
merges small chunks in front of it when the source size isn't yours to raise.
|
|
124
78
|
|
|
125
|
-
- **Shared scratch is not reentrant.** Some hot methods reuse a module-level
|
|
126
|
-
scratch buffer as a write-then-read pair — correct only because reads are fully
|
|
127
|
-
synchronous. An `async`/`yield` boundary between populating and reading it
|
|
128
|
-
corrupts the value.
|
|
129
|
-
|
|
130
79
|
- **Hoist the cursor into locals.** Prefer the working buffer and view declared
|
|
131
80
|
once at the top of the generated reader, and keep the read offset in a **local variable**,
|
|
132
81
|
operating on it directly instead of re-reading from an object.
|
|
@@ -135,72 +84,43 @@ When generating a parser, follow these:
|
|
|
135
84
|
neighbouring fixed-width columns has a known combined size, so bounds-check it
|
|
136
85
|
ONCE.
|
|
137
86
|
|
|
138
|
-
- **Inline the leaf reads.** The per-type `readX` functions are the correct,
|
|
139
|
-
composable reference; the generated parser should INLINE their bodies, not call
|
|
140
|
-
them, so the row reader is straight-line with no per-field indirection (and so
|
|
141
|
-
the two points above can fold the offset arithmetic together).
|
|
142
|
-
|
|
143
|
-
- **Annotate the decoded type per column.** Inlining erases the type structure,
|
|
144
|
-
so put a short comment above each column's decode block naming the ClickHouse
|
|
145
|
-
type it reads.
|
|
146
|
-
|
|
147
87
|
- **Pre-allocate small result arrays.** RowBinary gives every array/map its
|
|
148
88
|
element count up front (the LEB128 prefix), so DEFAULT is to `new Array(n)`.
|
|
149
89
|
NOTE: for **large** arrays the application will iterate or compute over repeatedly,
|
|
150
90
|
prefer `[]` + `push` (faster to traverse in V8) — or a typed array (`Float64Array`…)
|
|
151
91
|
for numeric elements.
|
|
152
92
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
| `
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
184
|
-
|
|
|
185
|
-
| `
|
|
186
|
-
| `
|
|
187
|
-
| `Nested(...)` — no wire of its own; `Array(Tuple(...))` | `src/nested.ts` |
|
|
188
|
-
| `Nothing` — zero-width, never decoded (only wrapped) | `src/nothing.ts` |
|
|
189
|
-
| `AggregateFunction(...)` — opaque state; finalize server-side | `src/aggregateFunction.ts` |
|
|
190
|
-
|
|
191
|
-
## Worked examples
|
|
192
|
-
|
|
193
|
-
Six end-to-end examples with real speedup are catalogued in [EXAMPLES.md](EXAMPLES.md).
|
|
194
|
-
|
|
195
|
-
## Out of scope
|
|
196
|
-
|
|
197
|
-
- **JSON / CSV / TSV / Parquet parsing** → use `clickhouse-js-node-coding`.
|
|
198
|
-
- **Connection errors, hangs, type mismatches** → use
|
|
199
|
-
`clickhouse-js-node-troubleshooting`.
|
|
200
|
-
- **Browser / Web Worker / Edge** → `@clickhouse/client-web`.
|
|
201
|
-
|
|
202
|
-
## Still Stuck?
|
|
203
|
-
|
|
204
|
-
- [ClickHouse RowBinary format](https://clickhouse.com/docs/interfaces/formats#rowbinary)
|
|
205
|
-
- [ClickHouse data types](https://clickhouse.com/docs/sql-reference/data-types)
|
|
206
|
-
- [ClickHouse JS client docs](https://clickhouse.com/docs/integrations/javascript)
|
|
93
|
+
## Reader type family references
|
|
94
|
+
|
|
95
|
+
The readers live as real code under `src/readers/`, split by type family.
|
|
96
|
+
|
|
97
|
+
| Result contains (trigger) | Open |
|
|
98
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
99
|
+
| **Always** — cursor state, `advance()`, `NeedMoreData`, `Reader<T>` | `src/readers/core.ts` |
|
|
100
|
+
| LEB128 length/count prefixes for `String`/`Array`/`Map` (`readUVarint`) | `src/readers/varint.ts` |
|
|
101
|
+
| `Int8`–`Int256`, `UInt8`–`UInt256` | `src/readers/integers.ts` |
|
|
102
|
+
| `Bool` | `src/readers/bool.ts` |
|
|
103
|
+
| `Enum8`, `Enum16` (resolve to the value's name; `readInt8`/`readInt16` for the raw int) | `src/readers/enums.ts` |
|
|
104
|
+
| `Float32`, `Float64`, `BFloat16` | `src/readers/floats.ts` |
|
|
105
|
+
| `Decimal32/64/128/256`, `Decimal(P, S)` | `src/readers/decimals.ts` |
|
|
106
|
+
| `String`, `FixedString(N)` | `src/readers/strings.ts` |
|
|
107
|
+
| `UUID` | `src/readers/uuid.ts` |
|
|
108
|
+
| `IPv4`, `IPv6` | `src/readers/ip.ts` |
|
|
109
|
+
| `Date`, `Date32`, `DateTime`, `DateTime(tz)`, `DateTime64(P[, tz])` | `src/readers/datetime.ts` |
|
|
110
|
+
| `Time`, `Time64(P)` | `src/readers/time.ts` |
|
|
111
|
+
| `IntervalNanosecond` … `IntervalYear` | `src/readers/interval.ts` |
|
|
112
|
+
| `Array(T)`, `Map(K, V)`, `Tuple(...)`, `Nullable(T)`, `Variant(...)`, `QBit(...)` | `src/readers/composite.ts` |
|
|
113
|
+
| `Point`, `Ring`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`, `Geometry` | `src/readers/geo.ts` |
|
|
114
|
+
| `Dynamic` (and `Variant`/`Interval`/`Nested`/`Dynamic` nested inside it) | `src/readers/dynamic.ts` |
|
|
115
|
+
| `JSON` | `src/readers/json.ts` |
|
|
116
|
+
| The whole result — loop rows to EOF (`readRows`) | `src/readers/rows.ts` |
|
|
117
|
+
| A chunked HTTP response — `streamRowBatches`, `coalesceChunks` | `src/readers/stream.ts` |
|
|
118
|
+
| The `RowBinaryWithNamesAndTypes` header — column names + type strings (`readHeader`) | `src/readers/header.ts` |
|
|
119
|
+
| Fold one parsed type AST into a `Reader` (`astToReader`) — AST in, reader out | `src/readers/compile.ts` |
|
|
120
|
+
| **Types known only at runtime** — compile a whole header into a row reader (`compileRowBinaryWithNamesAndTypes`, `typeStringToReader`) | `src/readers/rowBinaryWithNamesAndTypes.ts` |
|
|
121
|
+
| **Numeric/fixed-width result read column-wise** (aggregate/scan/plot, hand to a Worker/WASM) → decode into typed arrays, not row objects (~4x) | `src/readers/columnar.ts` (`streamSensorColumns` — streaming, yields transferable typed-array batches); `decodeIotColumnar` in `src/examples/iot.ts` is the whole-buffer form |
|
|
122
|
+
| `LowCardinality(T)` — transparent, decode as `T` | `src/readers/lowCardinality.ts` |
|
|
123
|
+
| `SimpleAggregateFunction(f, T)` — transparent, decode as `T` | `src/readers/simpleAggregateFunction.ts` |
|
|
124
|
+
| `Nested(...)` — no wire of its own; `Array(Tuple(...))` | `src/readers/nested.ts` |
|
|
125
|
+
| `Nothing` — zero-width, never decoded (only wrapped) | `src/readers/nothing.ts` |
|
|
126
|
+
| `AggregateFunction(...)` — opaque state; finalize server-side | `src/readers/aggregateFunction.ts` |
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
readArray,
|
|
3
|
+
readNullable,
|
|
4
|
+
readTupleNamed,
|
|
5
|
+
} from "../readers/composite.js";
|
|
6
|
+
import { type Reader, advance } from "../readers/core.js";
|
|
7
|
+
import { readInt32, readUInt16, readUInt32 } from "../readers/integers.js";
|
|
8
|
+
import { readString } from "../readers/strings.js";
|
|
9
|
+
import { readUVarint } from "../readers/varint.js";
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Example: a carts table — nested generics.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type Reader, advance } from "../core.js";
|
|
2
|
-
import { readDateTime } from "../datetime.js";
|
|
3
|
-
import { readUInt64 } from "../integers.js";
|
|
4
|
-
import { readString } from "../strings.js";
|
|
5
|
-
import { readUVarint } from "../varint.js";
|
|
1
|
+
import { type Reader, advance } from "../readers/core.js";
|
|
2
|
+
import { readDateTime } from "../readers/datetime.js";
|
|
3
|
+
import { readUInt64 } from "../readers/integers.js";
|
|
4
|
+
import { readString } from "../readers/strings.js";
|
|
5
|
+
import { readUVarint } from "../readers/varint.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Example: a plain events table — the scalar baseline.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type Reader, advance } from "../core.js";
|
|
2
|
-
import { readDateTime64P3 } from "../datetime.js";
|
|
3
|
-
import { readFloat32, readFloat64 } from "../floats.js";
|
|
4
|
-
import { readUInt8, readUInt32 } from "../integers.js";
|
|
1
|
+
import { type Reader, advance } from "../readers/core.js";
|
|
2
|
+
import { readDateTime64P3 } from "../readers/datetime.js";
|
|
3
|
+
import { readFloat32, readFloat64 } from "../readers/floats.js";
|
|
4
|
+
import { readUInt8, readUInt32 } from "../readers/integers.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Example: a table of IoT sensor readings — the dense, fixed-width NUMERIC case
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { type Reader, advance } from "../core.js";
|
|
1
|
+
import { type Reader, advance } from "../readers/core.js";
|
|
2
2
|
import {
|
|
3
3
|
type DecimalValue,
|
|
4
4
|
readDecimal64,
|
|
5
5
|
readDecimal128,
|
|
6
|
-
} from "../decimals.js";
|
|
7
|
-
import { readInt64, readUInt128, readUInt256 } from "../integers.js";
|
|
6
|
+
} from "../readers/decimals.js";
|
|
7
|
+
import { readInt64, readUInt128, readUInt256 } from "../readers/integers.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Example: a financial ledger — the WIDE-NUMERIC case where RowBinary wins on
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type Reader, advance } from "../core.js";
|
|
2
|
-
import { readDateTime } from "../datetime.js";
|
|
3
|
-
import { readString } from "../strings.js";
|
|
4
|
-
import { readUVarint } from "../varint.js";
|
|
1
|
+
import { type Reader, advance } from "../readers/core.js";
|
|
2
|
+
import { readDateTime } from "../readers/datetime.js";
|
|
3
|
+
import { readString } from "../readers/strings.js";
|
|
4
|
+
import { readUVarint } from "../readers/varint.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Example: an application log table — the STRING-HEAVY case where the skill
|
|
@@ -4,15 +4,14 @@ import {
|
|
|
4
4
|
readNullable,
|
|
5
5
|
readTupleNamed,
|
|
6
6
|
readVariant,
|
|
7
|
-
} from "../composite.js";
|
|
8
|
-
import { type Reader, advance } from "../core.js";
|
|
9
|
-
import { readDateTime64P3 } from "../datetime.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { readUVarint } from "../varint.js";
|
|
7
|
+
} from "../readers/composite.js";
|
|
8
|
+
import { type Reader, advance } from "../readers/core.js";
|
|
9
|
+
import { readDateTime64P3 } from "../readers/datetime.js";
|
|
10
|
+
import { readFloat64 } from "../readers/floats.js";
|
|
11
|
+
import { readInt8, readInt64, readUInt64 } from "../readers/integers.js";
|
|
12
|
+
import { readString } from "../readers/strings.js";
|
|
13
|
+
import { formatUUID, formatUUIDTable, readUUID } from "../readers/uuid.js";
|
|
14
|
+
import { readUVarint } from "../readers/varint.js";
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* Example: an observability/events table — the gotcha-heavy one. It packs the
|
|
@@ -60,7 +59,7 @@ export type ObsRow = {
|
|
|
60
59
|
export const readObsRow: Reader<ObsRow> = (s) => ({
|
|
61
60
|
id: readUInt64(s),
|
|
62
61
|
ts: readDateTime64P3(s).toISOString(),
|
|
63
|
-
level:
|
|
62
|
+
level: readInt8(s),
|
|
64
63
|
traceId: formatUUID(readUUID(s)),
|
|
65
64
|
payload: readVariant([readFloat64, readInt64, readString])(s),
|
|
66
65
|
tags: readMap(readString, readString)(s),
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { type Reader, advance } from "../core.js";
|
|
2
|
-
import { type DecimalValue, readDecimal64 } from "../decimals.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { formatUUID, formatUUIDTable, readUUID } from "../uuid.js";
|
|
1
|
+
import { type Reader, advance } from "../readers/core.js";
|
|
2
|
+
import { type DecimalValue, readDecimal64 } from "../readers/decimals.js";
|
|
3
|
+
import { readInt8, readUInt8 } from "../readers/integers.js";
|
|
4
|
+
import { formatUUID, formatUUIDTable, readUUID } from "../readers/uuid.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Example: an orders table — UUID, Decimal, and Enum (awkward-as-JSON types).
|
|
@@ -15,9 +14,11 @@ import { formatUUID, formatUUIDTable, readUUID } from "../uuid.js";
|
|
|
15
14
|
*
|
|
16
15
|
* Shows the parse/format split and faithful values: `uid` is read as raw bytes
|
|
17
16
|
* then formatted with `formatUUID`; `price` stays the exact `[unscaled, scale]`
|
|
18
|
-
* pair (`[1234n, 2]` == 12.34), not a lossy float; `status`
|
|
19
|
-
* underlying `Int8`
|
|
20
|
-
*
|
|
17
|
+
* pair (`[1234n, 2]` == 12.34), not a lossy float; `status` is read as the raw
|
|
18
|
+
* underlying `Int8` (1/2/3) with `readInt8` — a hand-written reader that bakes
|
|
19
|
+
* in the schema can skip name resolution, whereas the generic `readEnum8(map)`
|
|
20
|
+
* (used by the dynamic/header path) resolves the value to its name. The declared
|
|
21
|
+
* scale `2` is baked into `readDecimal64(2)`.
|
|
21
22
|
*/
|
|
22
23
|
export type OrderRow = {
|
|
23
24
|
id: number;
|
|
@@ -30,7 +31,7 @@ export const readOrderRow: Reader<OrderRow> = (s) => ({
|
|
|
30
31
|
id: readUInt8(s),
|
|
31
32
|
uid: formatUUID(readUUID(s)),
|
|
32
33
|
price: readDecimal64(2)(s),
|
|
33
|
-
status:
|
|
34
|
+
status: readInt8(s),
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { readArray, readNullable } from "../composite.js";
|
|
2
|
-
import { type Reader, advance } from "../core.js";
|
|
3
|
-
import { readInt32, readUInt32 } from "../integers.js";
|
|
4
|
-
import { readString } from "../strings.js";
|
|
5
|
-
import { readUVarint } from "../varint.js";
|
|
1
|
+
import { readArray, readNullable } from "../readers/composite.js";
|
|
2
|
+
import { type Reader, advance } from "../readers/core.js";
|
|
3
|
+
import { readInt32, readUInt32 } from "../readers/integers.js";
|
|
4
|
+
import { readString } from "../readers/strings.js";
|
|
5
|
+
import { readUVarint } from "../readers/varint.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Example: a profiles table — Array and Nullable wrappers.
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
readMap,
|
|
4
4
|
readNullable,
|
|
5
5
|
readTupleNamed,
|
|
6
|
-
} from "../composite.js";
|
|
7
|
-
import { type Reader, advance } from "../core.js";
|
|
8
|
-
import { readFloat64 } from "../floats.js";
|
|
9
|
-
import { readUInt16, readUInt32 } from "../integers.js";
|
|
10
|
-
import { readString } from "../strings.js";
|
|
11
|
-
import { readUVarint } from "../varint.js";
|
|
6
|
+
} from "../readers/composite.js";
|
|
7
|
+
import { type Reader, advance } from "../readers/core.js";
|
|
8
|
+
import { readFloat64 } from "../readers/floats.js";
|
|
9
|
+
import { readUInt16, readUInt32 } from "../readers/integers.js";
|
|
10
|
+
import { readString } from "../readers/strings.js";
|
|
11
|
+
import { readUVarint } from "../readers/varint.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Example: a telemetry table — composite readers that nest.
|
|
@@ -115,10 +115,12 @@ export class RowBinaryTypeError extends Error {
|
|
|
115
115
|
*/
|
|
116
116
|
export function astToReader(node: Node): Reader<unknown> {
|
|
117
117
|
switch (node.kind) {
|
|
118
|
-
case NodeKind.EnumDataType:
|
|
119
|
-
// Explicit-value enum: the wire value is the underlying int
|
|
120
|
-
//
|
|
121
|
-
|
|
118
|
+
case NodeKind.EnumDataType: {
|
|
119
|
+
// Explicit-value enum: the wire value is the underlying int, which we
|
|
120
|
+
// resolve to its NAME via the `'name' = value` pairs the type carries.
|
|
121
|
+
const map = enumNameMap(node);
|
|
122
|
+
return node.name === "Enum16" ? readEnum16(map) : readEnum8(map);
|
|
123
|
+
}
|
|
122
124
|
case NodeKind.TupleDataType:
|
|
123
125
|
return tupleReader(node);
|
|
124
126
|
case NodeKind.DataType:
|
|
@@ -185,12 +187,13 @@ function dataTypeReader(node: Node): Reader<unknown> {
|
|
|
185
187
|
return readDecimal128(literalInt(requireArg(node, 0)));
|
|
186
188
|
case "Decimal256":
|
|
187
189
|
return readDecimal256(literalInt(requireArg(node, 0)));
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
+
// A bare `Enum8`/`Enum16` (no explicit values — only reachable from a
|
|
191
|
+
// hand-written type string) has no names to resolve, so the reader falls
|
|
192
|
+
// back to the stringified underlying int.
|
|
190
193
|
case "Enum8":
|
|
191
|
-
return readEnum8;
|
|
194
|
+
return readEnum8(new Map());
|
|
192
195
|
case "Enum16":
|
|
193
|
-
return readEnum16;
|
|
196
|
+
return readEnum16(new Map());
|
|
194
197
|
|
|
195
198
|
default: {
|
|
196
199
|
// Interval<Unit> (IntervalSecond, IntervalDay, …): all decode to the
|
|
@@ -247,6 +250,13 @@ function nestedReader(node: Node): Reader<unknown> {
|
|
|
247
250
|
return readNested(fields);
|
|
248
251
|
}
|
|
249
252
|
|
|
253
|
+
/** Builds an enum's underlying-int -> name lookup from its `'name' = value` pairs. */
|
|
254
|
+
function enumNameMap(node: Node): ReadonlyMap<number, string> {
|
|
255
|
+
const map = new Map<number, string>();
|
|
256
|
+
for (const ev of node.values) map.set(Number(ev.value), ev.name);
|
|
257
|
+
return map;
|
|
258
|
+
}
|
|
259
|
+
|
|
250
260
|
/** Folds `Decimal(P, S)` to the right width by precision P; scale S drives decoding. */
|
|
251
261
|
function decimalReader(node: Node): Reader<unknown> {
|
|
252
262
|
const precision = literalInt(requireArg(node, 0));
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
readUInt256,
|
|
16
16
|
} from "./integers.js";
|
|
17
17
|
import { readBool } from "./bool.js";
|
|
18
|
+
import { readEnum8, readEnum16 } from "./enums.js";
|
|
18
19
|
import { readFloat32, readFloat64 } from "./floats.js";
|
|
19
20
|
import { readString, readFixedString } from "./strings.js";
|
|
20
21
|
import { readUUID } from "./uuid.js";
|
|
@@ -143,23 +144,26 @@ export function readDynamicType(state: Cursor): Reader<unknown> {
|
|
|
143
144
|
return readString;
|
|
144
145
|
case 0x16:
|
|
145
146
|
return readFixedString(readUVarint(state));
|
|
146
|
-
// Enum8 / Enum16: a count then (name String, value Int8/Int16) pairs.
|
|
147
|
-
// name<->value map
|
|
147
|
+
// Enum8 / Enum16: a count then (name String, value Int8/Int16) pairs. We
|
|
148
|
+
// collect them into the name<->value map so the value resolves to its name,
|
|
149
|
+
// matching the textual-type path in compile.ts.
|
|
148
150
|
case 0x17: {
|
|
149
151
|
const n = readUVarint(state);
|
|
152
|
+
const map = new Map<number, string>();
|
|
150
153
|
for (let i = 0; i < n; i++) {
|
|
151
|
-
readString(state);
|
|
152
|
-
readInt8(state);
|
|
154
|
+
const name = readString(state);
|
|
155
|
+
map.set(readInt8(state), name);
|
|
153
156
|
}
|
|
154
|
-
return
|
|
157
|
+
return readEnum8(map);
|
|
155
158
|
}
|
|
156
159
|
case 0x18: {
|
|
157
160
|
const n = readUVarint(state);
|
|
161
|
+
const map = new Map<number, string>();
|
|
158
162
|
for (let i = 0; i < n; i++) {
|
|
159
|
-
readString(state);
|
|
160
|
-
readInt16(state);
|
|
163
|
+
const name = readString(state);
|
|
164
|
+
map.set(readInt16(state), name);
|
|
161
165
|
}
|
|
162
|
-
return
|
|
166
|
+
return readEnum16(map);
|
|
163
167
|
}
|
|
164
168
|
// Decimals: header carries precision P then scale S (both varint). Only S
|
|
165
169
|
// matters for decoding; P is consumed and dropped. Returns [unscaled, S].
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Reader, advance } from "./core.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maps an enum's underlying integer to its name. Built from the type's
|
|
5
|
+
* `'name' = value` pairs (which live in the column type, not the wire) by the
|
|
6
|
+
* compile step / dynamic decoder and handed to the readers below.
|
|
7
|
+
*/
|
|
8
|
+
export type EnumNameMap = ReadonlyMap<number, string>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Read an `Enum8` and resolve it to its NAME — the ergonomic default, since the
|
|
12
|
+
* point of an enum is usually the label, not the raw `Int8`.
|
|
13
|
+
*
|
|
14
|
+
* This is a factory: the name<->value map is metadata carried by the type, not
|
|
15
|
+
* the bytes, so it is supplied once (per column) and closed over. A value with
|
|
16
|
+
* no matching name falls back to its stringified integer rather than throwing,
|
|
17
|
+
* so a decode never blows up on an unexpected wire value.
|
|
18
|
+
*
|
|
19
|
+
* Need the raw underlying integer instead (e.g. a monomorphized hot path that
|
|
20
|
+
* has the schema baked in)? Read it directly with {@link readInt8} — that is the
|
|
21
|
+
* fast, allocation-free path the enum's name resolution sits on top of.
|
|
22
|
+
*/
|
|
23
|
+
export function readEnum8(valueToName: EnumNameMap): Reader<string> {
|
|
24
|
+
return (state) =>
|
|
25
|
+
resolveName(valueToName, state.view.getInt8(advance(state, 1)));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read an `Enum16` (2-byte underlying `Int16`) and resolve it to its NAME. See
|
|
30
|
+
* {@link readEnum8}; use {@link readInt16} for the raw underlying integer.
|
|
31
|
+
*/
|
|
32
|
+
export function readEnum16(valueToName: EnumNameMap): Reader<string> {
|
|
33
|
+
return (state) =>
|
|
34
|
+
resolveName(valueToName, state.view.getInt16(advance(state, 2), true));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveName(valueToName: EnumNameMap, value: number): string {
|
|
38
|
+
const name = valueToName.get(value);
|
|
39
|
+
return name !== undefined ? name : String(value);
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Writer } from "./core.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Inverse of `readAggregateFunction`: an `AggregateFunction(func, T…)` column is
|
|
5
|
+
* OPAQUE, unframed aggregation state with a layout specific to `func` and the
|
|
6
|
+
* server version, so it cannot be produced generically from a value. Build the
|
|
7
|
+
* state server-side (the `-State` combinators) rather than encoding it on the
|
|
8
|
+
* client.
|
|
9
|
+
*
|
|
10
|
+
* This writer throws to stop a generic encoder from emitting a misaligned row.
|
|
11
|
+
*/
|
|
12
|
+
export const writeAggregateFunction: Writer<never> = () => {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"RowBinary: AggregateFunction is opaque, unframed aggregation state with no " +
|
|
15
|
+
"length prefix — not generically encodable. Produce the state server-side " +
|
|
16
|
+
"(the -State combinators) instead of encoding it on the client.",
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Sink } from "./core.js";
|
|
2
|
+
import { writeUInt8 } from "./integers.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Write a `Bool`: 1 byte, stored as `UInt8` (`false` -> 0, `true` -> 1). Mirror
|
|
6
|
+
* of `readBool`.
|
|
7
|
+
*/
|
|
8
|
+
export function writeBool(sink: Sink, value: boolean): void {
|
|
9
|
+
writeUInt8(sink, value ? 1 : 0);
|
|
10
|
+
}
|