Setting the file. One moment. JSON Test · Clickhouse Js Node Rowbinary · ClickHouse/agent-skills · Skills Docs(opens in a new tab)
tests/JSON.test.ts
TypeScript·106 lines·4 KB
8
9async function reader(expr: string): Promise<Cursor> {
10 return new Cursor(await query(`SELECT ${expr} ${J} FORMAT RowBinary`));
11}
12
13describe("readJSON", () => {
14 it("reads (path, Dynamic value) pairs into a Map", async () => {
15 const r = await reader(`'{"a":1}'::JSON`);
16 expect(readJSON(r)).toEqual(new Map([["a", 1n]])); // a -> Int64 1
17 });
18
19 it("reads multiple paths, each with its own inline type", async () => {
20 const r = await reader(`'{"a":1,"b":"hi"}'::JSON`);
21 expect(readJSON(r)).toEqual(
22 new Map<string, unknown>([
23 ["b", "hi"], // String
24 ["a", 1n], // Int64
25 ]),
26 );
27 });
28
29 it("FLATTENS nested objects to dotted paths", async () => {
30 const r = await reader(`'{"a":{"b":2}}'::JSON`);
31 expect(readJSON(r)).toEqual(new Map([["a.b", 2n]]));
32 });
33
34 it("an empty object is zero paths", async () => {
35 const r = await reader(`'{}'::JSON`);
36 expect(readJSON(r)).toEqual(new Map());
37 });
38
39 it("a null-valued path is NOT stored — same as an empty object", async () => {
40 const r = await reader(`'{"a":null}'::JSON`);
41 expect(readJSON(r)).toEqual(new Map()); // {"a":null} serializes as 0 paths
42 });
43
44 it("a JSON array path decodes as Array(Nullable(T)) via Dynamic", async () => {
45 const r = await reader(`'{"a":[1,2]}'::JSON`);
46 expect(readJSON(r)).toEqual(new Map([["a", [1n, 2n]]]));
47 });
48
49 it("decodes mixed scalar types (Float64, Bool)", async () => {
50 const r = await reader(`'{"x":1.5,"y":true}'::JSON`);
51 expect(readJSON(r)).toEqual(
52 new Map<string, unknown>([
53 ["y", true],
54 ["x", 1.5],
55 ]),
56 );
57 });
58
59 // JSON nested inside a Dynamic: the 0x30 tag's type-encoding header precedes
60 // the body, which readDynamicType consumes before delegating to readJSON.
61 describe("inside a Dynamic (tag 0x30, with the type-encoding header)", () => {
62 async function dyn(expr: string): Promise<Cursor> {
63 return new Cursor(
64 await query(
65 `SELECT CAST(${expr} AS Dynamic) ${J}, allow_experimental_dynamic_type = 1 FORMAT RowBinary`,
66 ),
67 );
68 }
69
70 it("decodes a JSON value, skipping the parameter header", async () => {
71 const r = await dyn(`'{"a":1}'::JSON`);
72 expect(readDynamic(r)).toEqual(new Map([["a", 1n]]));
73 });
74
75 it("recurses through Array(JSON)", async () => {
76 const r = await dyn(`['{"a":1}'::JSON, '{"b":2}'::JSON]`);
77 expect(readDynamic(r)).toEqual([
78 new Map([["a", 1n]]),
79 new Map([["b", 2n]]),
80 ]);
81 });
82
83 it("throws on declared typed paths (need the schema to read them)", async () => {
84 const r = await dyn(`'{"a":1}'::JSON(b UInt32)`);
85 expect(() => readDynamic(r)).toThrow(/typed paths/);
86 });
87 });
88
89 describe("advance() edge cases", () => {
90 it("throws NeedMoreData for every incomplete prefix (0 .. full.length-1)", async () => {
91 const full = await query(`SELECT '{"a":1}'::JSON ${J} FORMAT RowBinary`);
92 for (let len = 0; len < full.length; len++) {
93 const r = new Cursor(full.subarray(0, len));
94 let thrown: unknown;
95 try {
96 readJSON(r);
97 } catch (e) {
98 thrown = e;
99 }
100 expect(thrown, `prefix length ${len} of ${full.length}`).toBe(
101 NeedMoreData,
102 );
103 }
104 });
105 });
106});