@clickhouse/client 1.22.0 → 1.23.0-head.b25cda1.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 +11 -4
- package/dist/client.js.map +1 -1
- package/dist/common/clickhouse_types.d.ts +98 -0
- package/dist/common/clickhouse_types.js +30 -0
- package/dist/common/clickhouse_types.js.map +1 -0
- package/dist/common/client.d.ts +233 -0
- package/dist/common/client.js +414 -0
- package/dist/common/client.js.map +1 -0
- package/dist/common/config.d.ts +234 -0
- package/dist/common/config.js +364 -0
- package/dist/common/config.js.map +1 -0
- package/dist/common/connection.d.ts +124 -0
- package/dist/common/connection.js +3 -0
- package/dist/common/connection.js.map +1 -0
- package/dist/common/data_formatter/format_query_params.d.ts +11 -0
- package/dist/common/data_formatter/format_query_params.js +128 -0
- package/dist/common/data_formatter/format_query_params.js.map +1 -0
- package/dist/common/data_formatter/format_query_settings.d.ts +2 -0
- package/dist/common/data_formatter/format_query_settings.js +20 -0
- package/dist/common/data_formatter/format_query_settings.js.map +1 -0
- package/dist/common/data_formatter/formatter.d.ts +41 -0
- package/dist/common/data_formatter/formatter.js +78 -0
- package/dist/common/data_formatter/formatter.js.map +1 -0
- package/dist/common/data_formatter/index.d.ts +3 -0
- package/dist/common/data_formatter/index.js +24 -0
- package/dist/common/data_formatter/index.js.map +1 -0
- package/dist/common/error/error.d.ts +20 -0
- package/dist/common/error/error.js +73 -0
- package/dist/common/error/error.js.map +1 -0
- package/dist/common/error/index.d.ts +1 -0
- package/dist/common/error/index.js +18 -0
- package/dist/common/error/index.js.map +1 -0
- package/dist/common/index.d.ts +67 -0
- package/dist/common/index.js +97 -0
- package/dist/common/index.js.map +1 -0
- package/dist/common/logger.d.ts +80 -0
- package/dist/common/logger.js +154 -0
- package/dist/common/logger.js.map +1 -0
- package/dist/common/parse/column_types.d.ts +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 +22 -2
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/connection/compression.d.ts +2 -2
- package/dist/connection/compression.js +4 -4
- package/dist/connection/compression.js.map +1 -1
- package/dist/connection/create_connection.d.ts +1 -1
- package/dist/connection/node_base_connection.d.ts +3 -3
- package/dist/connection/node_base_connection.js +22 -22
- package/dist/connection/node_base_connection.js.map +1 -1
- package/dist/connection/node_custom_agent_connection.js +2 -2
- package/dist/connection/node_custom_agent_connection.js.map +1 -1
- package/dist/connection/node_http_connection.js +2 -2
- package/dist/connection/node_http_connection.js.map +1 -1
- package/dist/connection/node_https_connection.d.ts +1 -1
- package/dist/connection/node_https_connection.js +3 -3
- package/dist/connection/node_https_connection.js.map +1 -1
- package/dist/connection/socket_pool.d.ts +1 -1
- package/dist/connection/socket_pool.js +30 -30
- package/dist/connection/socket_pool.js.map +1 -1
- package/dist/connection/stream.d.ts +1 -1
- package/dist/connection/stream.js +9 -9
- package/dist/connection/stream.js.map +1 -1
- package/dist/index.d.ts +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,98 @@
|
|
|
1
|
+
import { type Reader, advance } from "../core.js";
|
|
2
|
+
import {
|
|
3
|
+
type DecimalValue,
|
|
4
|
+
readDecimal64,
|
|
5
|
+
readDecimal128,
|
|
6
|
+
} from "../decimals.js";
|
|
7
|
+
import { readInt64, readUInt128, readUInt256 } from "../integers.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Example: a financial ledger — the WIDE-NUMERIC case where RowBinary wins on
|
|
11
|
+
* *correctness*, not merely speed, and the headline of the wide-int/decimal
|
|
12
|
+
* comparison in `ledger.bench.ts`.
|
|
13
|
+
*
|
|
14
|
+
* Columns (the trigger — generate this reader when a result has these types):
|
|
15
|
+
* txn_id UInt128
|
|
16
|
+
* account Int64
|
|
17
|
+
* amount Decimal128(18)
|
|
18
|
+
* balance Decimal128(18)
|
|
19
|
+
* fee Decimal64(4)
|
|
20
|
+
* volume UInt256
|
|
21
|
+
*
|
|
22
|
+
* Every value here exceeds what a JS `number` (IEEE-754 double, 53-bit mantissa)
|
|
23
|
+
* can hold exactly, and ClickHouse emits them as **bare JSON numbers**. So
|
|
24
|
+
* `JSON.parse` silently rounds every field — the only correct JSON path is to
|
|
25
|
+
* quote the values server-side and re-parse each string into a `bigint` /
|
|
26
|
+
* decimal pair by hand. RowBinary reads each as an exact `bigint` straight off
|
|
27
|
+
* the wire. The whole row is fixed-width (16+8+16+16+8+32 = 96 bytes).
|
|
28
|
+
*/
|
|
29
|
+
export type LedgerRow = {
|
|
30
|
+
txn_id: bigint;
|
|
31
|
+
account: bigint;
|
|
32
|
+
amount: DecimalValue;
|
|
33
|
+
balance: DecimalValue;
|
|
34
|
+
fee: DecimalValue;
|
|
35
|
+
volume: bigint;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* API-combinator reader: correct and clear, one leaf reader per column. A fine
|
|
40
|
+
* default; `readLedgerRowFast` is the monomorphized form `ledger.bench.ts`
|
|
41
|
+
* measures. Both return identical values.
|
|
42
|
+
*/
|
|
43
|
+
export const readLedgerRow: Reader<LedgerRow> = (s) => ({
|
|
44
|
+
txn_id: readUInt128(s),
|
|
45
|
+
account: readInt64(s),
|
|
46
|
+
amount: readDecimal128(18)(s),
|
|
47
|
+
balance: readDecimal128(18)(s),
|
|
48
|
+
fee: readDecimal64(4)(s),
|
|
49
|
+
volume: readUInt256(s),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Optimized, monomorphized reader: the six column bounds checks coalesce into
|
|
54
|
+
* one `advance(s, 96)`; each wide value is composed from 64-bit words read at
|
|
55
|
+
* constant offsets off that base, with the high word read **signed** for the
|
|
56
|
+
* signed types (`Int64`, the `Decimal128` unscaled value, no high word needed
|
|
57
|
+
* for the unsigned `UInt128`/`UInt256`). Stays streaming-safe (one `advance`).
|
|
58
|
+
*
|
|
59
|
+
* txn_id UInt128 @ o+0 lo + (hi<<64) unsigned
|
|
60
|
+
* account Int64 @ o+16 getBigInt64
|
|
61
|
+
* amount Decimal128(18) @ o+24 lo + (hiSigned<<64) -> [v, 18]
|
|
62
|
+
* balance Decimal128(18) @ o+40 lo + (hiSigned<<64) -> [v, 18]
|
|
63
|
+
* fee Decimal64(4) @ o+56 getBigInt64 -> [v, 4]
|
|
64
|
+
* volume UInt256 @ o+64 w0 + w1<<64 + w2<<128 + w3<<192 unsigned
|
|
65
|
+
*/
|
|
66
|
+
export const readLedgerRowFast: Reader<LedgerRow> = (s) => {
|
|
67
|
+
const { view } = s;
|
|
68
|
+
const o = advance(s, 96); // one bounds check for the whole 96-byte row
|
|
69
|
+
|
|
70
|
+
// UInt128 — unsigned, both words unsigned.
|
|
71
|
+
const txn_id =
|
|
72
|
+
view.getBigUint64(o, true) + (view.getBigUint64(o + 8, true) << 64n);
|
|
73
|
+
|
|
74
|
+
// Int64 — signed.
|
|
75
|
+
const account = view.getBigInt64(o + 16, true);
|
|
76
|
+
|
|
77
|
+
// Decimal128(18) — Int128 unscaled (low word unsigned, high word signed), scale 18.
|
|
78
|
+
const amount: DecimalValue = [
|
|
79
|
+
view.getBigUint64(o + 24, true) + (view.getBigInt64(o + 32, true) << 64n),
|
|
80
|
+
18,
|
|
81
|
+
];
|
|
82
|
+
const balance: DecimalValue = [
|
|
83
|
+
view.getBigUint64(o + 40, true) + (view.getBigInt64(o + 48, true) << 64n),
|
|
84
|
+
18,
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
// Decimal64(4) — Int64 unscaled, scale 4.
|
|
88
|
+
const fee: DecimalValue = [view.getBigInt64(o + 56, true), 4];
|
|
89
|
+
|
|
90
|
+
// UInt256 — unsigned, four unsigned words.
|
|
91
|
+
const volume =
|
|
92
|
+
view.getBigUint64(o + 64, true) +
|
|
93
|
+
(view.getBigUint64(o + 72, true) << 64n) +
|
|
94
|
+
(view.getBigUint64(o + 80, true) << 128n) +
|
|
95
|
+
(view.getBigUint64(o + 88, true) << 192n);
|
|
96
|
+
|
|
97
|
+
return { txn_id, account, amount, balance, fee, volume };
|
|
98
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
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";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Example: an application log table — the STRING-HEAVY case where the skill
|
|
8
|
+
* steers you AWAY from RowBinary and toward a `JSON*` format. The honest
|
|
9
|
+
* counter-case to the IoT and ledger studies; see `logs.bench.ts`.
|
|
10
|
+
*
|
|
11
|
+
* Columns (the trigger — generate this reader when a result has these types):
|
|
12
|
+
* ts DateTime
|
|
13
|
+
* level LowCardinality(String)
|
|
14
|
+
* service LowCardinality(String)
|
|
15
|
+
* message String
|
|
16
|
+
* trace_id String
|
|
17
|
+
*
|
|
18
|
+
* Four of the five columns are text consumed wholesale, and `LowCardinality(T)`
|
|
19
|
+
* is transparent in RowBinary (decodes as the inner `String`, no dictionary on
|
|
20
|
+
* the wire). A RowBinary string read is a varint length + `buf.toString("utf8",
|
|
21
|
+
* …)` per field in JS; V8's native `JSON.parse` builds the same JS strings in
|
|
22
|
+
* optimized C++ and tends to WIN here. This reader exists so the comparison is
|
|
23
|
+
* apples-to-apples — not because RowBinary is the right call for this shape.
|
|
24
|
+
*/
|
|
25
|
+
export type LogRow = {
|
|
26
|
+
ts: Date;
|
|
27
|
+
level: string;
|
|
28
|
+
service: string;
|
|
29
|
+
message: string;
|
|
30
|
+
trace_id: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* API-combinator reader: one leaf reader per column, the clear default. There is
|
|
35
|
+
* little to monomorphize on a mostly-string row — the only fixed-width field is
|
|
36
|
+
* `ts` — so `readLogRowFast` below is barely different and barely faster; the
|
|
37
|
+
* real lesson (see `logs.bench.ts`) is that JSON beats both.
|
|
38
|
+
*/
|
|
39
|
+
export const readLogRow: Reader<LogRow> = (s) => ({
|
|
40
|
+
ts: readDateTime(s),
|
|
41
|
+
level: readString(s), // LowCardinality(String) — transparent, decode as String
|
|
42
|
+
service: readString(s), // LowCardinality(String) — transparent, decode as String
|
|
43
|
+
message: readString(s),
|
|
44
|
+
trace_id: readString(s),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Optimized {@link readLogRow}: the four string reads inlined (varint length +
|
|
49
|
+
* `buf.toString` in place) and the `DateTime` read written out. Note how little
|
|
50
|
+
* monomorphization can do when the row is dominated by variable-length strings —
|
|
51
|
+
* there are no adjacent fixed-width columns to coalesce, so this stays close to
|
|
52
|
+
* the API version. Included to make `logs.bench.ts` a fair fight; the takeaway
|
|
53
|
+
* is to pick `JSONEachRow` for this shape, not to tune this reader.
|
|
54
|
+
*/
|
|
55
|
+
export const readLogRowFast: Reader<LogRow> = (s) => {
|
|
56
|
+
const { buf } = s;
|
|
57
|
+
// DateTime: 4-byte LE Unix seconds.
|
|
58
|
+
const ts = new Date(s.view.getUint32(advance(s, 4), true) * 1000);
|
|
59
|
+
// Four UTF-8 Strings (the two LowCardinality columns are plain String on the wire).
|
|
60
|
+
let len = readUVarint(s);
|
|
61
|
+
let o = advance(s, len);
|
|
62
|
+
const level = buf.toString("utf8", o, o + len);
|
|
63
|
+
len = readUVarint(s);
|
|
64
|
+
o = advance(s, len);
|
|
65
|
+
const service = buf.toString("utf8", o, o + len);
|
|
66
|
+
len = readUVarint(s);
|
|
67
|
+
o = advance(s, len);
|
|
68
|
+
const message = buf.toString("utf8", o, o + len);
|
|
69
|
+
len = readUVarint(s);
|
|
70
|
+
o = advance(s, len);
|
|
71
|
+
const trace_id = buf.toString("utf8", o, o + len);
|
|
72
|
+
return { ts, level, service, message, trace_id };
|
|
73
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readArray,
|
|
3
|
+
readMap,
|
|
4
|
+
readNullable,
|
|
5
|
+
readTupleNamed,
|
|
6
|
+
readVariant,
|
|
7
|
+
} from "../composite.js";
|
|
8
|
+
import { type Reader, advance } from "../core.js";
|
|
9
|
+
import { readDateTime64P3 } from "../datetime.js";
|
|
10
|
+
import { readEnum8 } from "../enums.js";
|
|
11
|
+
import { readFloat64 } from "../floats.js";
|
|
12
|
+
import { readInt64, readUInt64 } from "../integers.js";
|
|
13
|
+
import { readString } from "../strings.js";
|
|
14
|
+
import { formatUUID, formatUUIDTable, readUUID } from "../uuid.js";
|
|
15
|
+
import { readUVarint } from "../varint.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Example: an observability/events table — the gotcha-heavy one. It packs the
|
|
19
|
+
* traps that trip a from-scratch decoder; the skill's job is getting them right.
|
|
20
|
+
*
|
|
21
|
+
* Columns (the trigger):
|
|
22
|
+
* id UInt64
|
|
23
|
+
* ts DateTime64(3, 'UTC')
|
|
24
|
+
* level Enum8('debug'=1, 'info'=2, 'warn'=3, 'error'=4)
|
|
25
|
+
* trace_id UUID
|
|
26
|
+
* payload Variant(String, Int64, Float64)
|
|
27
|
+
* tags Map(LowCardinality(String), String)
|
|
28
|
+
* metrics Array(Tuple(name LowCardinality(String), value Float64))
|
|
29
|
+
* attrs Array(Nullable(Int64))
|
|
30
|
+
*
|
|
31
|
+
* Gotchas exercised, all in one row:
|
|
32
|
+
* - `Variant(String, Int64, Float64)`: the discriminant indexes the alternatives
|
|
33
|
+
* SORTED BY TYPE NAME — ["Float64", "Int64", "String"] → 0=Float64, 1=Int64,
|
|
34
|
+
* 2=String (NOT declaration order); `0xFF` = NULL. `readVariant` takes the
|
|
35
|
+
* readers in that sorted order.
|
|
36
|
+
* - `DateTime64(3)`: 8-byte Int64 of millisecond ticks; P=3 is exactly a `Date`'s
|
|
37
|
+
* resolution, so `readDateTime64P3` returns a plain `Date` (here ISO-stringed).
|
|
38
|
+
* - `LowCardinality(String)` (in the Map key and the Tuple field) is TRANSPARENT
|
|
39
|
+
* in RowBinary — decode as plain `String`, no dictionary layer.
|
|
40
|
+
* - `UUID` is two little-endian `UInt64` halves, byte-reversed vs the text form.
|
|
41
|
+
* - `Array(Nullable(Int64))`: per element a null flag then (if present) an Int64,
|
|
42
|
+
* kept as `bigint`.
|
|
43
|
+
*/
|
|
44
|
+
export type ObsRow = {
|
|
45
|
+
id: bigint;
|
|
46
|
+
ts: string;
|
|
47
|
+
level: number;
|
|
48
|
+
traceId: string;
|
|
49
|
+
payload: number | bigint | string | null;
|
|
50
|
+
tags: Map<string, string>;
|
|
51
|
+
metrics: { name: string; value: number }[];
|
|
52
|
+
attrs: (bigint | null)[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* API-combinator reader. Note the `Variant` readers are in sorted-type-name
|
|
57
|
+
* order (Float64, Int64, String), and `LowCardinality` columns just use the
|
|
58
|
+
* inner `String` reader.
|
|
59
|
+
*/
|
|
60
|
+
export const readObsRow: Reader<ObsRow> = (s) => ({
|
|
61
|
+
id: readUInt64(s),
|
|
62
|
+
ts: readDateTime64P3(s).toISOString(),
|
|
63
|
+
level: readEnum8(s),
|
|
64
|
+
traceId: formatUUID(readUUID(s)),
|
|
65
|
+
payload: readVariant([readFloat64, readInt64, readString])(s),
|
|
66
|
+
tags: readMap(readString, readString)(s),
|
|
67
|
+
metrics: readArray(readTupleNamed({ name: readString, value: readFloat64 }))(
|
|
68
|
+
s,
|
|
69
|
+
),
|
|
70
|
+
attrs: readArray(readNullable(readInt64))(s),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Optimized {@link readObsRow}, flattened per the SKILL.md guidance:
|
|
75
|
+
* - `buf`/`view` hoisted to locals.
|
|
76
|
+
* - the leading run of FIXED-WIDTH columns — `id` UInt64 (8) + `ts` DateTime64 (8)
|
|
77
|
+
* + `level` Enum8 (1) + `trace_id` UUID (16) = 33 bytes — is bounds-checked ONCE
|
|
78
|
+
* (`advance(s, 33)`) and read at constant offsets, instead of four `advance`s.
|
|
79
|
+
* - the `Variant` is an inlined `switch` over the discriminant (sorted order).
|
|
80
|
+
* - leaf reads inlined, `formatUUIDTable` for the UUID, pre-sized arrays.
|
|
81
|
+
* The variable-width columns (`payload`/`tags`/`metrics`/`attrs`) each start a new
|
|
82
|
+
* `advance` run because their size isn't known until decoded.
|
|
83
|
+
*/
|
|
84
|
+
export const readObsRowFast: Reader<ObsRow> = (s) => {
|
|
85
|
+
const { buf, view } = s;
|
|
86
|
+
|
|
87
|
+
// One bounds check for the 33-byte fixed-width head.
|
|
88
|
+
const o = advance(s, 33);
|
|
89
|
+
const id = view.getBigUint64(o, true);
|
|
90
|
+
const ts = new Date(Number(view.getBigInt64(o + 8, true))).toISOString();
|
|
91
|
+
const level = view.getInt8(o + 16);
|
|
92
|
+
const traceId = formatUUIDTable(buf.subarray(o + 17, o + 33));
|
|
93
|
+
|
|
94
|
+
// payload Variant(String, Int64, Float64): 1-byte discriminant (sorted names:
|
|
95
|
+
// 0=Float64, 1=Int64, 2=String), 0xFF = NULL.
|
|
96
|
+
let payload: number | bigint | string | null;
|
|
97
|
+
const disc = buf[advance(s, 1)]!;
|
|
98
|
+
if (disc === 0xff) {
|
|
99
|
+
payload = null;
|
|
100
|
+
} else if (disc === 0) {
|
|
101
|
+
payload = view.getFloat64(advance(s, 8), true);
|
|
102
|
+
} else if (disc === 1) {
|
|
103
|
+
payload = view.getBigInt64(advance(s, 8), true);
|
|
104
|
+
} else {
|
|
105
|
+
const len = readUVarint(s);
|
|
106
|
+
const st = advance(s, len);
|
|
107
|
+
payload = buf.toString("utf8", st, st + len);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// tags Map(LowCardinality(String) -> String): count, then key/value strings.
|
|
111
|
+
const tagN = readUVarint(s);
|
|
112
|
+
const tags = new Map<string, string>();
|
|
113
|
+
for (let i = 0; i < tagN; i++) {
|
|
114
|
+
let len = readUVarint(s);
|
|
115
|
+
let st = advance(s, len);
|
|
116
|
+
const k = buf.toString("utf8", st, st + len);
|
|
117
|
+
len = readUVarint(s);
|
|
118
|
+
st = advance(s, len);
|
|
119
|
+
tags.set(k, buf.toString("utf8", st, st + len));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// metrics Array(Tuple(name LowCardinality(String), value Float64)).
|
|
123
|
+
const mN = readUVarint(s);
|
|
124
|
+
const metrics = new Array<{ name: string; value: number }>(mN);
|
|
125
|
+
for (let i = 0; i < mN; i++) {
|
|
126
|
+
const len = readUVarint(s);
|
|
127
|
+
const st = advance(s, len);
|
|
128
|
+
const name = buf.toString("utf8", st, st + len);
|
|
129
|
+
const value = view.getFloat64(advance(s, 8), true);
|
|
130
|
+
metrics[i] = { name, value };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// attrs Array(Nullable(Int64)).
|
|
134
|
+
const aN = readUVarint(s);
|
|
135
|
+
const attrs = new Array<bigint | null>(aN);
|
|
136
|
+
for (let i = 0; i < aN; i++) {
|
|
137
|
+
attrs[i] =
|
|
138
|
+
buf[advance(s, 1)]! !== 0 ? null : view.getBigInt64(advance(s, 8), true);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { id, ts, level, traceId, payload, tags, metrics, attrs };
|
|
142
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type Reader, advance } from "../core.js";
|
|
2
|
+
import { type DecimalValue, readDecimal64 } from "../decimals.js";
|
|
3
|
+
import { readEnum8 } from "../enums.js";
|
|
4
|
+
import { readUInt8 } from "../integers.js";
|
|
5
|
+
import { formatUUID, formatUUIDTable, readUUID } from "../uuid.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Example: an orders table — UUID, Decimal, and Enum (awkward-as-JSON types).
|
|
9
|
+
*
|
|
10
|
+
* Columns (the trigger):
|
|
11
|
+
* id UInt8
|
|
12
|
+
* uid UUID
|
|
13
|
+
* price Decimal64(2)
|
|
14
|
+
* status Enum8('new' = 1, 'shipped' = 2, 'done' = 3)
|
|
15
|
+
*
|
|
16
|
+
* Shows the parse/format split and faithful values: `uid` is read as raw bytes
|
|
17
|
+
* then formatted with `formatUUID`; `price` stays the exact `[unscaled, scale]`
|
|
18
|
+
* pair (`[1234n, 2]` == 12.34), not a lossy float; `status` decodes to the
|
|
19
|
+
* underlying `Int8` value (1/2/3), the name<->value map being type metadata, not
|
|
20
|
+
* on the wire. The declared scale `2` is baked into `readDecimal64(2)`.
|
|
21
|
+
*/
|
|
22
|
+
export type OrderRow = {
|
|
23
|
+
id: number;
|
|
24
|
+
uid: string;
|
|
25
|
+
price: DecimalValue;
|
|
26
|
+
status: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const readOrderRow: Reader<OrderRow> = (s) => ({
|
|
30
|
+
id: readUInt8(s),
|
|
31
|
+
uid: formatUUID(readUUID(s)),
|
|
32
|
+
price: readDecimal64(2)(s),
|
|
33
|
+
status: readEnum8(s),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Optimized {@link readOrderRow}, flattened per the SKILL.md guidance:
|
|
38
|
+
* - `buf`/`view` hoisted to locals (one property load, not one per field).
|
|
39
|
+
* - every column here is FIXED-WIDTH, so the whole row — `id` UInt8 (1) + `uid`
|
|
40
|
+
* UUID (16) + `price` Decimal64 (8) + `status` Enum8 (1) = 26 bytes — is
|
|
41
|
+
* bounds-checked ONCE (`advance(s, 26)`) and read at constant offsets, instead
|
|
42
|
+
* of four separate `advance`s. (This is the exact worked example in SKILL.md.)
|
|
43
|
+
* - the four leaf reads are inlined, and the BigInt `formatUUID` is swapped for
|
|
44
|
+
* the lookup-table `formatUUIDTable` (~1.6x on its own; see `readUUID.bench.ts`).
|
|
45
|
+
* Since this example formats every UUID to a string, that swap is the dominant
|
|
46
|
+
* win — the `readDecimal64(2)` closure (rebuilt per row above) is inlined too.
|
|
47
|
+
*
|
|
48
|
+
* MEASURED (Node 24 / V8, `orders.bench.ts`): ~2.6x faster — the largest win of
|
|
49
|
+
* the examples, dominated by the `formatUUIDTable` swap (every row stringifies a
|
|
50
|
+
* UUID; the table formatter is ~1.7x on its own and the row is otherwise cheap).
|
|
51
|
+
*
|
|
52
|
+
* `formatUUIDTable` uses a shared scratch buffer, so it is non-reentrant — fine
|
|
53
|
+
* here because the bytes are copied into the returned string synchronously before
|
|
54
|
+
* the next call.
|
|
55
|
+
*/
|
|
56
|
+
export const readOrderRowFast: Reader<OrderRow> = (s) => {
|
|
57
|
+
const { buf, view } = s;
|
|
58
|
+
// One bounds check for the whole 26-byte fixed-width row.
|
|
59
|
+
const o = advance(s, 26);
|
|
60
|
+
const id = buf[o]!;
|
|
61
|
+
const uid = formatUUIDTable(buf.subarray(o + 1, o + 17));
|
|
62
|
+
const price: DecimalValue = [view.getBigInt64(o + 17, true), 2];
|
|
63
|
+
const status = view.getInt8(o + 25);
|
|
64
|
+
return { id, uid, price, status };
|
|
65
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
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";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Example: a profiles table — Array and Nullable wrappers.
|
|
9
|
+
*
|
|
10
|
+
* Columns (the trigger):
|
|
11
|
+
* id UInt32
|
|
12
|
+
* tags Array(String)
|
|
13
|
+
* score Nullable(Int32)
|
|
14
|
+
*
|
|
15
|
+
* `readArray(elem)` reads a LEB128 length then that many elements; `readNullable`
|
|
16
|
+
* reads a 1-byte present/NULL flag then the value. Both are combinators: pass the
|
|
17
|
+
* inner reader and they return a `Reader`. Empty array and NULL are the sharp
|
|
18
|
+
* cases (a single byte each).
|
|
19
|
+
*/
|
|
20
|
+
export type ProfileRow = { id: number; tags: string[]; score: number | null };
|
|
21
|
+
|
|
22
|
+
export const readProfileRow: Reader<ProfileRow> = (s) => ({
|
|
23
|
+
id: readUInt32(s),
|
|
24
|
+
tags: readArray(readString)(s),
|
|
25
|
+
score: readNullable(readInt32)(s),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Optimized {@link readProfileRow}, monomorphized: `readArray(readString)` and
|
|
30
|
+
* `readNullable(readInt32)` each allocate a fresh combinator closure on EVERY
|
|
31
|
+
* row in the version above; here the array loop and the null-flag branch are
|
|
32
|
+
* inlined, so no per-row closures are created and the element/inner reads are
|
|
33
|
+
* straight-line. This is the kind of win the SKILL's "monomorphize" step targets;
|
|
34
|
+
* see `profiles.bench.ts`.
|
|
35
|
+
*
|
|
36
|
+
* MEASURED (Node 24 / V8, `profiles.bench.ts`): ~1.3x faster — removing the two
|
|
37
|
+
* per-row combinator closures (`readArray(readString)`, `readNullable(readInt32)`)
|
|
38
|
+
* is the win.
|
|
39
|
+
*/
|
|
40
|
+
export const readProfileRowFast: Reader<ProfileRow> = (s) => {
|
|
41
|
+
const { buf, view } = s;
|
|
42
|
+
|
|
43
|
+
// id UInt32.
|
|
44
|
+
const id = view.getUint32(advance(s, 4), true);
|
|
45
|
+
|
|
46
|
+
// tags Array(String): count, then each a length-prefixed UTF-8 string.
|
|
47
|
+
const n = readUVarint(s);
|
|
48
|
+
const tags = new Array<string>(n);
|
|
49
|
+
for (let i = 0; i < n; i++) {
|
|
50
|
+
const len = readUVarint(s);
|
|
51
|
+
const start = advance(s, len);
|
|
52
|
+
tags[i] = buf.toString("utf8", start, start + len);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// score Nullable(Int32): null-flag byte, then if non-null a 4-byte int.
|
|
56
|
+
const score =
|
|
57
|
+
buf[advance(s, 1)]! !== 0 ? null : view.getInt32(advance(s, 4), true);
|
|
58
|
+
|
|
59
|
+
return { id, tags, score };
|
|
60
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readArray,
|
|
3
|
+
readMap,
|
|
4
|
+
readNullable,
|
|
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";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Example: a telemetry table — composite readers that nest.
|
|
15
|
+
*
|
|
16
|
+
* Columns (the trigger):
|
|
17
|
+
* host String
|
|
18
|
+
* tags Map(String, String)
|
|
19
|
+
* cpu Array(Float64)
|
|
20
|
+
* region Nullable(String)
|
|
21
|
+
* window Tuple(start UInt32, count UInt16)
|
|
22
|
+
*
|
|
23
|
+
* The combinators compose exactly the way the column type nests:
|
|
24
|
+
* `readMap(k, v)`, `readArray(elem)`, `readNullable(inner)`, and
|
|
25
|
+
* `readTupleNamed({...})` each take sub-readers and return a `Reader`. This is the
|
|
26
|
+
* generic (closure-per-element) API; a generated parser would monomorphize these
|
|
27
|
+
* into inlined per-type loops, but the result shape is exactly this.
|
|
28
|
+
*/
|
|
29
|
+
export type TelemetryRow = {
|
|
30
|
+
host: string;
|
|
31
|
+
tags: Map<string, string>;
|
|
32
|
+
cpu: number[];
|
|
33
|
+
region: string | null;
|
|
34
|
+
window: { start: number; count: number };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const readTelemetryRow: Reader<TelemetryRow> = (s) => ({
|
|
38
|
+
host: readString(s),
|
|
39
|
+
tags: readMap(readString, readString)(s),
|
|
40
|
+
cpu: readArray(readFloat64)(s),
|
|
41
|
+
region: readNullable(readString)(s),
|
|
42
|
+
window: readTupleNamed({ start: readUInt32, count: readUInt16 })(s),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Optimized {@link readTelemetryRow}, fully monomorphized: the API version above
|
|
47
|
+
* builds FOUR combinator closures per row (`readMap(...)`, `readArray(...)`,
|
|
48
|
+
* `readNullable(...)`, `readTupleNamed(...)`) and, for the named tuple, iterates
|
|
49
|
+
* a keys array building an object field by field. Here every loop and branch is
|
|
50
|
+
* inlined and the `window` object is a flat literal — no per-row closures, no
|
|
51
|
+
* key iteration. The most composite-heavy example.
|
|
52
|
+
*
|
|
53
|
+
* MEASURED (Node 24 / V8, `telemetry.bench.ts`): ~1.4x faster — four per-row
|
|
54
|
+
* combinator closures and the named-tuple key iteration removed.
|
|
55
|
+
*/
|
|
56
|
+
export const readTelemetryRowFast: Reader<TelemetryRow> = (s) => {
|
|
57
|
+
const { buf, view } = s;
|
|
58
|
+
|
|
59
|
+
// host String: length prefix, then the bytes.
|
|
60
|
+
let len = readUVarint(s);
|
|
61
|
+
let start = advance(s, len);
|
|
62
|
+
const host = buf.toString("utf8", start, start + len);
|
|
63
|
+
|
|
64
|
+
// tags Map(String, String): count, then key/value strings.
|
|
65
|
+
const mapN = readUVarint(s);
|
|
66
|
+
const tags = new Map<string, string>();
|
|
67
|
+
for (let i = 0; i < mapN; i++) {
|
|
68
|
+
len = readUVarint(s);
|
|
69
|
+
start = advance(s, len);
|
|
70
|
+
const k = buf.toString("utf8", start, start + len);
|
|
71
|
+
len = readUVarint(s);
|
|
72
|
+
start = advance(s, len);
|
|
73
|
+
tags.set(k, buf.toString("utf8", start, start + len));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// cpu Array(Float64): count, then 8 bytes each.
|
|
77
|
+
const cpuN = readUVarint(s);
|
|
78
|
+
const cpu = new Array<number>(cpuN);
|
|
79
|
+
for (let i = 0; i < cpuN; i++) {
|
|
80
|
+
cpu[i] = view.getFloat64(advance(s, 8), true);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// region Nullable(String): null-flag byte, then if non-null a string.
|
|
84
|
+
let region: string | null;
|
|
85
|
+
if (buf[advance(s, 1)]! !== 0) {
|
|
86
|
+
region = null;
|
|
87
|
+
} else {
|
|
88
|
+
len = readUVarint(s);
|
|
89
|
+
start = advance(s, len);
|
|
90
|
+
region = buf.toString("utf8", start, start + len);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// window Tuple(start UInt32, count UInt16): two adjacent fixed-width fields,
|
|
94
|
+
// bounds-checked once (6 bytes), then read at literal offsets.
|
|
95
|
+
const w = advance(s, 6);
|
|
96
|
+
const window = {
|
|
97
|
+
start: view.getUint32(w, true),
|
|
98
|
+
count: view.getUint16(w + 4, true),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return { host, tags, cpu, region, window };
|
|
102
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Cursor, advance } from "./core.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scratch view for widening a `BFloat16`: its 16 bits are the top half of an
|
|
5
|
+
* IEEE 754 float32, so we stage them into a 4-byte buffer and read a float32.
|
|
6
|
+
*/
|
|
7
|
+
const bf16Scratch = new DataView(new ArrayBuffer(4));
|
|
8
|
+
|
|
9
|
+
/** Read a `Float32`: 4 bytes, little-endian IEEE 754 single precision. */
|
|
10
|
+
export function readFloat32(state: Cursor): number {
|
|
11
|
+
return state.view.getFloat32(advance(state, 4), true);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Read a `Float64`: 8 bytes, little-endian IEEE 754 double precision. */
|
|
15
|
+
export function readFloat64(state: Cursor): number {
|
|
16
|
+
return state.view.getFloat64(advance(state, 8), true);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Read a `BFloat16`: 2 bytes, little-endian. BFloat16 is the high 16 bits of a
|
|
21
|
+
* float32 (same 8-bit exponent, 7-bit mantissa), so placing the bits in the top
|
|
22
|
+
* half of a 32-bit float and reading it back is exact.
|
|
23
|
+
*
|
|
24
|
+
* NOTE: `bf16Scratch` is module-level shared state written-then-read in this
|
|
25
|
+
* function. That is safe because the read is synchronous; do NOT introduce an
|
|
26
|
+
* `await`/`yield` between the `setUint32` and the `getFloat32`.
|
|
27
|
+
*/
|
|
28
|
+
export function readBFloat16(state: Cursor): number {
|
|
29
|
+
const bits = state.view.getUint16(advance(state, 2), true);
|
|
30
|
+
bf16Scratch.setUint32(0, bits * 0x10000, true);
|
|
31
|
+
return bf16Scratch.getFloat32(0, true);
|
|
32
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Cursor } from "./core.js";
|
|
2
|
+
import { readFloat64 } from "./floats.js";
|
|
3
|
+
import { readUInt8 } from "./integers.js";
|
|
4
|
+
import { readUVarint } from "./varint.js";
|
|
5
|
+
|
|
6
|
+
/** A geo `Point`: `[x, y]`, the base of every ClickHouse geo type. */
|
|
7
|
+
export type Point = [x: number, y: number];
|
|
8
|
+
|
|
9
|
+
// Geo types are concrete compositions of Point = Tuple(Float64, Float64). They
|
|
10
|
+
// are monomorphic (no sub-readers) — the generator can emit them as-is.
|
|
11
|
+
|
|
12
|
+
/** Read a `Point`: `Tuple(Float64, Float64)` -> `[x, y]`. */
|
|
13
|
+
export function readPoint(state: Cursor): Point {
|
|
14
|
+
const x = readFloat64(state);
|
|
15
|
+
const y = readFloat64(state);
|
|
16
|
+
return [x, y];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Read a `Ring`: `Array(Point)` — a LEB128 point count, then that many points.
|
|
21
|
+
* `LineString` has the identical wire (see {@link readLineString}). `readPoint`
|
|
22
|
+
* is inlined here (two `readFloat64`s) to drop a call per point on this hot path.
|
|
23
|
+
*/
|
|
24
|
+
export function readRing(state: Cursor): Point[] {
|
|
25
|
+
const n = readUVarint(state);
|
|
26
|
+
const out: Point[] = [];
|
|
27
|
+
for (let i = 0; i < n; i++) {
|
|
28
|
+
const x = readFloat64(state);
|
|
29
|
+
const y = readFloat64(state);
|
|
30
|
+
out.push([x, y]);
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Read a `LineString`: `Array(Point)` (identical wire to a `Ring`). Points are
|
|
37
|
+
* inlined (two `readFloat64`s) to drop a call per point on this hot path.
|
|
38
|
+
*/
|
|
39
|
+
export function readLineString(state: Cursor): Point[] {
|
|
40
|
+
const n = readUVarint(state);
|
|
41
|
+
const out: Point[] = [];
|
|
42
|
+
for (let i = 0; i < n; i++) {
|
|
43
|
+
const x = readFloat64(state);
|
|
44
|
+
const y = readFloat64(state);
|
|
45
|
+
out.push([x, y]);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Read a `Polygon`: `Array(Ring)` — the outer ring first, then any holes. */
|
|
51
|
+
export function readPolygon(state: Cursor): Point[][] {
|
|
52
|
+
const n = readUVarint(state);
|
|
53
|
+
const out: Point[][] = [];
|
|
54
|
+
for (let i = 0; i < n; i++) out.push(readRing(state));
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Read a `MultiLineString`: `Array(LineString)` (identical wire to a `Polygon`). */
|
|
59
|
+
export function readMultiLineString(state: Cursor): Point[][] {
|
|
60
|
+
const n = readUVarint(state);
|
|
61
|
+
const out: Point[][] = [];
|
|
62
|
+
for (let i = 0; i < n; i++) out.push(readLineString(state));
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Read a `MultiPolygon`: `Array(Polygon)`. */
|
|
67
|
+
export function readMultiPolygon(state: Cursor): Point[][][] {
|
|
68
|
+
const n = readUVarint(state);
|
|
69
|
+
const out: Point[][][] = [];
|
|
70
|
+
for (let i = 0; i < n; i++) out.push(readPolygon(state));
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Read a `Geometry`: a named `Variant` over the six geo types. This is the
|
|
76
|
+
* MONOMORPHIZED form of `readVariant` for a concrete variant — a switch over the
|
|
77
|
+
* discriminant with each branch inlined, no reader array. The alternatives,
|
|
78
|
+
* sorted by type name (so in discriminant order), are LineString(0),
|
|
79
|
+
* MultiLineString(1), MultiPolygon(2), Point(3), Polygon(4), Ring(5); 0xFF is NULL.
|
|
80
|
+
*
|
|
81
|
+
* NOTE: the value shapes overlap — LineString and Ring are both `Point[]`,
|
|
82
|
+
* MultiLineString and Polygon both `Point[][]` — so the value alone does not say
|
|
83
|
+
* which geo type it was. If you need the kind, branch on the discriminant.
|
|
84
|
+
*/
|
|
85
|
+
export function readGeometry(
|
|
86
|
+
state: Cursor,
|
|
87
|
+
): Point | Point[] | Point[][] | Point[][][] | null {
|
|
88
|
+
const discriminant = readUInt8(state);
|
|
89
|
+
switch (discriminant) {
|
|
90
|
+
case 0:
|
|
91
|
+
return readLineString(state);
|
|
92
|
+
case 1:
|
|
93
|
+
return readMultiLineString(state);
|
|
94
|
+
case 2:
|
|
95
|
+
return readMultiPolygon(state);
|
|
96
|
+
case 3:
|
|
97
|
+
return readPoint(state);
|
|
98
|
+
case 4:
|
|
99
|
+
return readPolygon(state);
|
|
100
|
+
case 5:
|
|
101
|
+
return readRing(state);
|
|
102
|
+
case 0xff:
|
|
103
|
+
return null;
|
|
104
|
+
default:
|
|
105
|
+
throw new RangeError(
|
|
106
|
+
`RowBinary: unknown Geometry discriminant ${discriminant}`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|