@clickhouse/client 1.23.0-head.fae5998.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/common/index.d.ts +2 -2
- package/dist/common/index.js +2 -2
- package/dist/common/index.js.map +1 -1
- package/dist/common/parse/column_types.d.ts +30 -2
- package/dist/common/parse/column_types.js +8 -0
- package/dist/common/parse/column_types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.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 -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 +85 -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} +59 -123
- 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/src/readers/compile.ts +328 -0
- 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/readers/header.ts +29 -0
- package/skills/{clickhouse-js-node-rowbinary-parser/src → clickhouse-js-node-rowbinary/src/readers}/reader.ts +17 -0
- package/skills/clickhouse-js-node-rowbinary/src/readers/rowBinaryWithNamesAndTypes.ts +155 -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}/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}/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,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.
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fold: turn a parsed ClickHouse data-type AST (from
|
|
3
|
+
* `@clickhouse/datatype-parser`) into a RowBinary value {@link Reader}.
|
|
4
|
+
*
|
|
5
|
+
* AST in, reader out — nothing else. Reading the `RowBinaryWithNamesAndTypes`
|
|
6
|
+
* header, parsing type strings, and assembling a row reader live in
|
|
7
|
+
* `rowBinaryWithNamesAndTypes.ts`; this module is just the type-to-reader
|
|
8
|
+
* mapping.
|
|
9
|
+
*
|
|
10
|
+
* It composes the existing GENERIC curried combinators at runtime, exactly as a
|
|
11
|
+
* hand-written reader would — no code generation and no monomorphization (those
|
|
12
|
+
* are the deliberate next step; see the `MONOMORPHIZE` notes throughout the
|
|
13
|
+
* combinator modules). The shape mirrors {@link readDynamicType} in
|
|
14
|
+
* `dynamic.ts`, which performs the same "type → Reader" mapping but driven by
|
|
15
|
+
* ClickHouse's BINARY type encoding rather than the textual type's AST.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { NodeKind, type Node } from "@clickhouse/datatype-parser";
|
|
19
|
+
|
|
20
|
+
import type { Reader } from "./core.js";
|
|
21
|
+
import {
|
|
22
|
+
readInt8,
|
|
23
|
+
readInt16,
|
|
24
|
+
readInt32,
|
|
25
|
+
readInt64,
|
|
26
|
+
readInt128,
|
|
27
|
+
readInt256,
|
|
28
|
+
readUInt8,
|
|
29
|
+
readUInt16,
|
|
30
|
+
readUInt32,
|
|
31
|
+
readUInt64,
|
|
32
|
+
readUInt128,
|
|
33
|
+
readUInt256,
|
|
34
|
+
} from "./integers.js";
|
|
35
|
+
import { readFloat32, readFloat64, readBFloat16 } from "./floats.js";
|
|
36
|
+
import { readBool } from "./bool.js";
|
|
37
|
+
import { readString, readFixedString } from "./strings.js";
|
|
38
|
+
import { readUUID } from "./uuid.js";
|
|
39
|
+
import { readIPv4, readIPv6 } from "./ip.js";
|
|
40
|
+
import {
|
|
41
|
+
readDate,
|
|
42
|
+
readDate32,
|
|
43
|
+
readDateTime,
|
|
44
|
+
readDateTime64,
|
|
45
|
+
} from "./datetime.js";
|
|
46
|
+
import { readTime, readTime64 } from "./time.js";
|
|
47
|
+
import { readInterval } from "./interval.js";
|
|
48
|
+
import {
|
|
49
|
+
readDecimal32,
|
|
50
|
+
readDecimal64,
|
|
51
|
+
readDecimal128,
|
|
52
|
+
readDecimal256,
|
|
53
|
+
} from "./decimals.js";
|
|
54
|
+
import { readEnum8, readEnum16 } from "./enums.js";
|
|
55
|
+
import {
|
|
56
|
+
readArray,
|
|
57
|
+
readMap,
|
|
58
|
+
readNullable,
|
|
59
|
+
readTuple,
|
|
60
|
+
readTupleNamed,
|
|
61
|
+
readVariant,
|
|
62
|
+
readQBit,
|
|
63
|
+
} from "./composite.js";
|
|
64
|
+
import { readLowCardinality } from "./lowCardinality.js";
|
|
65
|
+
import { readNested } from "./nested.js";
|
|
66
|
+
import { readNothing } from "./nothing.js";
|
|
67
|
+
import {
|
|
68
|
+
readPoint,
|
|
69
|
+
readRing,
|
|
70
|
+
readLineString,
|
|
71
|
+
readPolygon,
|
|
72
|
+
readMultiLineString,
|
|
73
|
+
readMultiPolygon,
|
|
74
|
+
readGeometry,
|
|
75
|
+
} from "./geo.js";
|
|
76
|
+
import { readJSON } from "./json.js";
|
|
77
|
+
import { readDynamic } from "./dynamic.js";
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Thrown when a ClickHouse type cannot be turned into a RowBinary reader —
|
|
81
|
+
* either the type string did not parse, or the parsed type is unsupported or
|
|
82
|
+
* malformed for RowBinary decoding (`AggregateFunction`, a missing argument, a
|
|
83
|
+
* malformed `Nested(...)`, …).
|
|
84
|
+
*
|
|
85
|
+
* A dedicated class so callers can `catch (e) { if (e instanceof
|
|
86
|
+
* RowBinaryTypeError) … }` and branch on a bad-type error specifically rather
|
|
87
|
+
* than string-matching a generic `Error`. Note the standalone
|
|
88
|
+
* `@clickhouse/datatype-parser` is itself NON-throwing (it returns a
|
|
89
|
+
* `ParseResult`); this is the error the compile layer raises on top of it.
|
|
90
|
+
*/
|
|
91
|
+
export class RowBinaryTypeError extends Error {
|
|
92
|
+
/** The full ClickHouse type string being compiled, when the throw site knows it. */
|
|
93
|
+
readonly typeString?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Byte offset into `typeString` where parsing stopped — set for PARSE
|
|
96
|
+
* failures (from the underlying parser), undefined for fold-time errors.
|
|
97
|
+
*/
|
|
98
|
+
readonly position?: number;
|
|
99
|
+
|
|
100
|
+
constructor(
|
|
101
|
+
message: string,
|
|
102
|
+
options: { typeString?: string; position?: number } = {},
|
|
103
|
+
) {
|
|
104
|
+
super(message);
|
|
105
|
+
this.name = "RowBinaryTypeError";
|
|
106
|
+
this.typeString = options.typeString;
|
|
107
|
+
this.position = options.position;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The fold itself: turn a parsed type-AST node into a value {@link Reader},
|
|
113
|
+
* recursing into element / key / field types for composites. The shape mirrors
|
|
114
|
+
* the server's `EXPLAIN AST` data-type subtree (see the parser's `ast.ts`).
|
|
115
|
+
*/
|
|
116
|
+
export function astToReader(node: Node): Reader<unknown> {
|
|
117
|
+
switch (node.kind) {
|
|
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
|
+
}
|
|
124
|
+
case NodeKind.TupleDataType:
|
|
125
|
+
return tupleReader(node);
|
|
126
|
+
case NodeKind.DataType:
|
|
127
|
+
return dataTypeReader(node);
|
|
128
|
+
default:
|
|
129
|
+
// Literal / Function / Identifier / NameTypePair are argument nodes,
|
|
130
|
+
// consumed by their parent — never a standalone column type.
|
|
131
|
+
throw new RowBinaryTypeError(
|
|
132
|
+
`cannot build a column reader for a ${node.kind} node (${node.name || "?"})`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Fold a generic `DataType` node (`name` + optional `arguments`) — the bulk of
|
|
139
|
+
* the type system: scalars, the parameterized types, and the
|
|
140
|
+
* wrappers/composites that recurse through {@link astToReader}.
|
|
141
|
+
*/
|
|
142
|
+
function dataTypeReader(node: Node): Reader<unknown> {
|
|
143
|
+
switch (node.name) {
|
|
144
|
+
// --- wrappers & composites (recurse into argument types) ---
|
|
145
|
+
case "Nullable":
|
|
146
|
+
return readNullable(astToReader(requireArg(node, 0)));
|
|
147
|
+
case "LowCardinality":
|
|
148
|
+
// Transparent in RowBinary: just the inner type's reader.
|
|
149
|
+
return readLowCardinality(astToReader(requireArg(node, 0)));
|
|
150
|
+
case "Array":
|
|
151
|
+
return readArray(astToReader(requireArg(node, 0)));
|
|
152
|
+
case "QBit":
|
|
153
|
+
// QBit(element_type, dimension): wire-identical to Array(element_type);
|
|
154
|
+
// the dimension (arg 1) is metadata, not in the value stream.
|
|
155
|
+
return readQBit(astToReader(requireArg(node, 0)));
|
|
156
|
+
case "Map":
|
|
157
|
+
return readMap(
|
|
158
|
+
astToReader(requireArg(node, 0)),
|
|
159
|
+
astToReader(requireArg(node, 1)),
|
|
160
|
+
);
|
|
161
|
+
case "Variant":
|
|
162
|
+
return variantReader(node);
|
|
163
|
+
case "Nested":
|
|
164
|
+
return nestedReader(node);
|
|
165
|
+
|
|
166
|
+
// --- parameterized scalars ---
|
|
167
|
+
case "FixedString":
|
|
168
|
+
return readFixedString(literalInt(requireArg(node, 0)));
|
|
169
|
+
case "DateTime":
|
|
170
|
+
case "DateTime32":
|
|
171
|
+
// An optional timezone argument is metadata; the value wire is the same.
|
|
172
|
+
return readDateTime;
|
|
173
|
+
case "DateTime64":
|
|
174
|
+
// DateTime64(P [, 'tz']); default precision 3 if somehow omitted.
|
|
175
|
+
return readDateTime64(
|
|
176
|
+
node.arguments.length > 0 ? literalInt(node.arguments[0]!) : 3,
|
|
177
|
+
);
|
|
178
|
+
case "Time64":
|
|
179
|
+
return readTime64(literalInt(requireArg(node, 0)));
|
|
180
|
+
case "Decimal":
|
|
181
|
+
return decimalReader(node);
|
|
182
|
+
case "Decimal32":
|
|
183
|
+
return readDecimal32(literalInt(requireArg(node, 0)));
|
|
184
|
+
case "Decimal64":
|
|
185
|
+
return readDecimal64(literalInt(requireArg(node, 0)));
|
|
186
|
+
case "Decimal128":
|
|
187
|
+
return readDecimal128(literalInt(requireArg(node, 0)));
|
|
188
|
+
case "Decimal256":
|
|
189
|
+
return readDecimal256(literalInt(requireArg(node, 0)));
|
|
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.
|
|
193
|
+
case "Enum8":
|
|
194
|
+
return readEnum8(new Map());
|
|
195
|
+
case "Enum16":
|
|
196
|
+
return readEnum16(new Map());
|
|
197
|
+
|
|
198
|
+
default: {
|
|
199
|
+
// Interval<Unit> (IntervalSecond, IntervalDay, …): all decode to the
|
|
200
|
+
// signed Int64 count; the unit lives in the type name, not the wire.
|
|
201
|
+
if (node.name.startsWith("Interval")) return readInterval;
|
|
202
|
+
const leaf = NULLARY[node.name];
|
|
203
|
+
if (leaf !== undefined) return leaf;
|
|
204
|
+
throw new RowBinaryTypeError(`unsupported RowBinary type: ${node.name}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Folds a `Tuple(...)` — named (object) when every element is named, else positional (array). */
|
|
210
|
+
function tupleReader(node: Node): Reader<unknown> {
|
|
211
|
+
const readers = node.arguments.map(astToReader);
|
|
212
|
+
const names = node.element_names;
|
|
213
|
+
const named =
|
|
214
|
+
names.length === readers.length && names.every((n) => n.length > 0);
|
|
215
|
+
if (named) {
|
|
216
|
+
const fields: Record<string, Reader<unknown>> = {};
|
|
217
|
+
for (let i = 0; i < names.length; i++) fields[names[i]!] = readers[i]!;
|
|
218
|
+
return readTupleNamed(fields);
|
|
219
|
+
}
|
|
220
|
+
return readTuple(readers);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Folds a `Variant(...)`. The 1-byte discriminant indexes the alternatives
|
|
225
|
+
* sorted by type NAME (ClickHouse's global ordering), and the server writes the
|
|
226
|
+
* type string with alternatives ALREADY in that sorted order — so for a
|
|
227
|
+
* header-sourced type the AST argument order is the discriminant order and we
|
|
228
|
+
* pass them straight through. (A hand-written, non-normalized `Variant(...)`
|
|
229
|
+
* string would need sorting first.)
|
|
230
|
+
*/
|
|
231
|
+
function variantReader(node: Node): Reader<unknown> {
|
|
232
|
+
return readVariant(node.arguments.map(astToReader));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Folds a `Nested(name Type, …)`: on the wire it IS `Array(Tuple(...))` with
|
|
237
|
+
* the field names, so we compose {@link readNested} (= `readArray(readTupleNamed)`)
|
|
238
|
+
* over the `NameTypePair` children.
|
|
239
|
+
*/
|
|
240
|
+
function nestedReader(node: Node): Reader<unknown> {
|
|
241
|
+
const fields: Record<string, Reader<unknown>> = {};
|
|
242
|
+
for (const child of node.arguments) {
|
|
243
|
+
if (child.kind !== NodeKind.NameTypePair || child.data_type === null) {
|
|
244
|
+
throw new RowBinaryTypeError(
|
|
245
|
+
"malformed Nested(...): expected name/type pairs",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
fields[child.name] = astToReader(child.data_type);
|
|
249
|
+
}
|
|
250
|
+
return readNested(fields);
|
|
251
|
+
}
|
|
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
|
+
|
|
260
|
+
/** Folds `Decimal(P, S)` to the right width by precision P; scale S drives decoding. */
|
|
261
|
+
function decimalReader(node: Node): Reader<unknown> {
|
|
262
|
+
const precision = literalInt(requireArg(node, 0));
|
|
263
|
+
const scale = literalInt(requireArg(node, 1));
|
|
264
|
+
if (precision <= 9) return readDecimal32(scale);
|
|
265
|
+
if (precision <= 18) return readDecimal64(scale);
|
|
266
|
+
if (precision <= 38) return readDecimal128(scale);
|
|
267
|
+
return readDecimal256(scale);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The nullary (no-argument) scalar types, mapped to their leaf readers. */
|
|
271
|
+
const NULLARY: Record<string, Reader<unknown>> = {
|
|
272
|
+
UInt8: readUInt8,
|
|
273
|
+
UInt16: readUInt16,
|
|
274
|
+
UInt32: readUInt32,
|
|
275
|
+
UInt64: readUInt64,
|
|
276
|
+
UInt128: readUInt128,
|
|
277
|
+
UInt256: readUInt256,
|
|
278
|
+
Int8: readInt8,
|
|
279
|
+
Int16: readInt16,
|
|
280
|
+
Int32: readInt32,
|
|
281
|
+
Int64: readInt64,
|
|
282
|
+
Int128: readInt128,
|
|
283
|
+
Int256: readInt256,
|
|
284
|
+
Float32: readFloat32,
|
|
285
|
+
Float64: readFloat64,
|
|
286
|
+
BFloat16: readBFloat16,
|
|
287
|
+
Bool: readBool,
|
|
288
|
+
String: readString,
|
|
289
|
+
UUID: readUUID,
|
|
290
|
+
IPv4: readIPv4,
|
|
291
|
+
IPv6: readIPv6,
|
|
292
|
+
Date: readDate,
|
|
293
|
+
Date32: readDate32,
|
|
294
|
+
Time: readTime,
|
|
295
|
+
Nothing: readNothing,
|
|
296
|
+
// Geo types.
|
|
297
|
+
Point: readPoint,
|
|
298
|
+
Ring: readRing,
|
|
299
|
+
LineString: readLineString,
|
|
300
|
+
Polygon: readPolygon,
|
|
301
|
+
MultiLineString: readMultiLineString,
|
|
302
|
+
MultiPolygon: readMultiPolygon,
|
|
303
|
+
Geometry: readGeometry as Reader<unknown>,
|
|
304
|
+
// Self-describing types: bare `JSON` / `Dynamic` (any args are metadata).
|
|
305
|
+
JSON: readJSON,
|
|
306
|
+
Dynamic: readDynamic,
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/** Argument accessor that fails loudly instead of returning `undefined`. */
|
|
310
|
+
function requireArg(node: Node, index: number): Node {
|
|
311
|
+
const arg = node.arguments[index];
|
|
312
|
+
if (arg === undefined) {
|
|
313
|
+
throw new RowBinaryTypeError(
|
|
314
|
+
`type ${node.name} is missing argument ${index}`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return arg;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Reads an integer out of a `Literal` argument node (e.g. the N in FixedString(N)). */
|
|
321
|
+
function literalInt(node: Node): number {
|
|
322
|
+
if (node.kind !== NodeKind.Literal) {
|
|
323
|
+
throw new RowBinaryTypeError(
|
|
324
|
+
`expected a literal argument, got a ${node.kind} node`,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
return Number(node.value);
|
|
328
|
+
}
|
|
@@ -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,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `RowBinaryWithNamesAndTypes` HEADER: the column names and their type
|
|
3
|
+
* strings the server writes before the row data. Parsing the type strings into
|
|
4
|
+
* readers lives in `compile.ts`; this module is just the wire read.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Cursor } from "./core.js";
|
|
8
|
+
import { readUVarint } from "./varint.js";
|
|
9
|
+
import { readString } from "./strings.js";
|
|
10
|
+
|
|
11
|
+
/** Column names and their type strings, in stream order. */
|
|
12
|
+
export interface RowBinaryHeader {
|
|
13
|
+
names: string[];
|
|
14
|
+
types: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Read the `RowBinaryWithNamesAndTypes` header off the cursor: a LEB128 column
|
|
19
|
+
* count, then that many column-name `String`s, then that many type-string
|
|
20
|
+
* `String`s. Leaves the cursor at the first row's bytes.
|
|
21
|
+
*/
|
|
22
|
+
export function readHeader(state: Cursor): RowBinaryHeader {
|
|
23
|
+
const count = readUVarint(state);
|
|
24
|
+
const names: string[] = new Array(count);
|
|
25
|
+
for (let i = 0; i < count; i++) names[i] = readString(state);
|
|
26
|
+
const types: string[] = new Array(count);
|
|
27
|
+
for (let i = 0; i < count; i++) types[i] = readString(state);
|
|
28
|
+
return { names, types };
|
|
29
|
+
}
|
|
@@ -24,6 +24,20 @@
|
|
|
24
24
|
* lowCardinality (readLowCardinality), simpleAggregateFunction
|
|
25
25
|
* (readSimpleAggregateFunction), nested (readNested), nothing (readNothing),
|
|
26
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
|
|
27
41
|
*/
|
|
28
42
|
export * from "./core.js";
|
|
29
43
|
export * from "./varint.js";
|
|
@@ -49,3 +63,6 @@ export * from "./simpleAggregateFunction.js";
|
|
|
49
63
|
export * from "./nested.js";
|
|
50
64
|
export * from "./nothing.js";
|
|
51
65
|
export * from "./aggregateFunction.js";
|
|
66
|
+
export * from "./header.js";
|
|
67
|
+
export * from "./compile.js";
|
|
68
|
+
export * from "./rowBinaryWithNamesAndTypes.js";
|