|
| 1 | +import { describe, expect, it } from "vitest" |
| 2 | + |
| 3 | +import type { InsertNode, JsonAccessNode, SelectNode } from "../src/ast/nodes.ts" |
| 4 | +import { createInsertNode, createSelectNode } from "../src/ast/nodes.ts" |
| 5 | +import { coalesce } from "../src/builder/eb.ts" |
| 6 | +import { MssqlPrinter } from "../src/printer/mssql.ts" |
| 7 | +import { MysqlPrinter } from "../src/printer/mysql.ts" |
| 8 | +import { SqlitePrinter } from "../src/printer/sqlite.ts" |
| 9 | + |
| 10 | +const selectJsonAccess = (op: "->" | "->>", path: string): SelectNode => ({ |
| 11 | + ...createSelectNode(), |
| 12 | + columns: [ |
| 13 | + { |
| 14 | + type: "json_access", |
| 15 | + expr: { type: "column_ref", column: "data" }, |
| 16 | + operator: op, |
| 17 | + path, |
| 18 | + } as JsonAccessNode, |
| 19 | + ], |
| 20 | + from: { type: "table_ref", name: "t" }, |
| 21 | +}) |
| 22 | + |
| 23 | +describe("Audit #24 regressions", () => { |
| 24 | + describe("coalesce() requires at least one argument", () => { |
| 25 | + it("coalesce() with zero args throws at builder time", () => { |
| 26 | + expect(() => coalesce()).toThrow(/requires at least one argument/) |
| 27 | + }) |
| 28 | + |
| 29 | + it("coalesce(x) with one arg still works", () => { |
| 30 | + const r = coalesce({ node: { type: "literal", value: 1 } } as any) |
| 31 | + expect((r as any).node).toEqual({ |
| 32 | + type: "function_call", |
| 33 | + name: "COALESCE", |
| 34 | + args: [{ type: "literal", value: 1 }], |
| 35 | + }) |
| 36 | + }) |
| 37 | + }) |
| 38 | + |
| 39 | + describe("MSSQL OUTPUT drops table qualifier on star (pseudo-tables only)", () => { |
| 40 | + it("INSERT ... RETURNING star('orders') → OUTPUT INSERTED.*", () => { |
| 41 | + const node: InsertNode = { |
| 42 | + ...createInsertNode({ type: "table_ref", name: "orders" }), |
| 43 | + columns: ["name"], |
| 44 | + values: [[{ type: "literal", value: "x" }]], |
| 45 | + returning: [{ type: "star", table: "orders" }], |
| 46 | + } |
| 47 | + const r = new MssqlPrinter().print(node) |
| 48 | + expect(r.sql).toContain("OUTPUT INSERTED.*") |
| 49 | + expect(r.sql).not.toContain("INSERTED.[orders].*") |
| 50 | + }) |
| 51 | + |
| 52 | + it("bare RETURNING * → OUTPUT INSERTED.*", () => { |
| 53 | + const node: InsertNode = { |
| 54 | + ...createInsertNode({ type: "table_ref", name: "orders" }), |
| 55 | + columns: ["name"], |
| 56 | + values: [[{ type: "literal", value: "x" }]], |
| 57 | + returning: [{ type: "star" }], |
| 58 | + } |
| 59 | + const r = new MssqlPrinter().print(node) |
| 60 | + expect(r.sql).toContain("OUTPUT INSERTED.*") |
| 61 | + }) |
| 62 | + }) |
| 63 | + |
| 64 | + describe("MySQL / SQLite JSON `->` uses JSONPath ($.path / $[n])", () => { |
| 65 | + it("MySQL at('name') → `data`->'$.name'", () => { |
| 66 | + const r = new MysqlPrinter().print(selectJsonAccess("->", "name")) |
| 67 | + expect(r.sql).toContain("->'$.name'") |
| 68 | + expect(r.sql).not.toMatch(/->'name'/) |
| 69 | + }) |
| 70 | + |
| 71 | + it("MySQL at('0') → `data`->'$[0]' (array index)", () => { |
| 72 | + const r = new MysqlPrinter().print(selectJsonAccess("->", "0")) |
| 73 | + expect(r.sql).toContain("->'$[0]'") |
| 74 | + expect(r.sql).not.toContain("->'0'") |
| 75 | + }) |
| 76 | + |
| 77 | + it("SQLite ->> name → \"data\"->>'$.name'", () => { |
| 78 | + const r = new SqlitePrinter().print(selectJsonAccess("->>", "name")) |
| 79 | + expect(r.sql).toContain("->>'$.name'") |
| 80 | + }) |
| 81 | + |
| 82 | + it("embedded single quote in path key is escape-doubled", () => { |
| 83 | + // e.g. `at("a'b")` should still land as a well-formed literal. |
| 84 | + const r = new MysqlPrinter().print(selectJsonAccess("->", "a'b")) |
| 85 | + expect(r.sql).toContain("->'$.a''b'") |
| 86 | + }) |
| 87 | + }) |
| 88 | +}) |
0 commit comments