fix(sql): respect PostgreSQL ARRAY brackets
This commit is contained in:
1 parent
11179676ed
commit
f274a599ed
6 files changed
+282
-57
No files matched your search
@@ -116,6 +116,94 @@ describe("extractSqlParameters", () => {
|
||||
expect(extractSqlParameters(sql, { databaseType: "postgres" })).toEqual(["?1", "?2", "?3", "?4", "?5", "?6"]);
|
||||
});
|
||||
|
||||
it("does not expose date format tokens after PostgreSQL ARRAY expressions", () => {
|
||||
const sql = `
|
||||
WITH rec_flow AS (
|
||||
SELECT
|
||||
order_no,
|
||||
ARRAY[
|
||||
concat(
|
||||
operator_name, '(', COALESCE(remark, ''), ')[',
|
||||
CASE operate_action
|
||||
WHEN 'CREATE' THEN '创建工单'
|
||||
WHEN 'SUBMIT' THEN '提交至下一处理人'
|
||||
WHEN 'BACK' THEN '退回上一环节'
|
||||
WHEN 'FINISH' THEN '已完成'
|
||||
WHEN 'SUBMIT-CONFIRM' THEN '提交给创建人确认'
|
||||
WHEN 'COMPLETE' THEN '确认工单'
|
||||
ELSE operate_action
|
||||
END, ']'
|
||||
)
|
||||
]::varchar[]
|
||||
|| CASE
|
||||
WHEN operate_action NOT IN ('FINISH','COMPLETE')
|
||||
THEN ARRAY[target_handler_name]::varchar[]
|
||||
ELSE ARRAY[]::varchar[]
|
||||
END AS name_arr,
|
||||
rn
|
||||
FROM (
|
||||
SELECT
|
||||
'ORD20260821001' AS order_no,
|
||||
'张三' AS operator_name,
|
||||
'发起流程' AS remark,
|
||||
'SUBMIT' AS operate_action,
|
||||
'李四' AS target_handler_name,
|
||||
1 AS rn
|
||||
) AS mock_t3_flow
|
||||
)
|
||||
SELECT to_char(current_timestamp, 'yyyy-MM-dd HH24:mi:ss');
|
||||
`;
|
||||
|
||||
expect(extractSqlParameters(sql, { databaseType: "postgres" })).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps PostgreSQL ARRAY literals and subscripts in the lexical stream", () => {
|
||||
const dateSql = "to_char(current_timestamp, 'yyyy-MM-dd HH24:mi:ss')";
|
||||
for (const arrayExpression of ["ARRAY['x']", "ARRAY[']']", "ARRAY['[']", "ARRAY['a]b']", "ARRAY[]::varchar[]"]) {
|
||||
expect(extractSqlParameters(`SELECT ${arrayExpression}, ${dateSql};`, { databaseType: "postgres" })).toEqual([]);
|
||||
}
|
||||
|
||||
expect(extractSqlParameters("SELECT ARRAY['x'][:array_index], values[:subscript_index];", { databaseType: "postgres" })).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not treat a standalone PostgreSQL date format as a parameter", () => {
|
||||
expect(extractSqlParameters("SELECT to_char(current_timestamp, 'HH24:MI:SS');", { databaseType: "postgres" })).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps PostgreSQL named parameters inside ARRAY constructors", () => {
|
||||
const sql = "SELECT ARRAY[:first_value, :second_value];";
|
||||
expect(extractSqlParameters(sql, { databaseType: "postgres" })).toEqual(["first_value", "second_value"]);
|
||||
expect(substituteSqlParameters(sql, { first_value: { kind: "number", value: "1" }, second_value: { kind: "number", value: "2" } }, { databaseType: "postgres" })).toBe("SELECT ARRAY[1, 2];");
|
||||
expect(extractSqlParameters("SELECT :id;", { databaseType: "postgres" })).toEqual(["id"]);
|
||||
});
|
||||
|
||||
it("does not treat PostgreSQL slice bounds as named parameters", () => {
|
||||
const sql = "SELECT arr[lower:upper], arr[:upper], ARRAY[arr[:nested_upper], :constructor_value];";
|
||||
|
||||
expect(extractSqlParameters(sql, { databaseType: "postgres" })).toEqual(["constructor_value"]);
|
||||
expect(substituteSqlParameters(sql, { constructor_value: { kind: "number", value: "7" } }, { databaseType: "postgres" })).toBe("SELECT arr[lower:upper], arr[:upper], ARRAY[arr[:nested_upper], 7];");
|
||||
});
|
||||
|
||||
it("keeps named parameters inside nested PostgreSQL ARRAY constructors and parenthesized subscripts", () => {
|
||||
const sql = "SELECT ARRAY[[:first_value, :second_value], ARRAY[:third_value]], values[(:subscript_index)];";
|
||||
|
||||
expect(extractSqlParameters(sql, { databaseType: "postgres" })).toEqual(["first_value", "second_value", "third_value", "subscript_index"]);
|
||||
});
|
||||
|
||||
it("preserves SQL Server bracketed identifiers while scanning parameters", () => {
|
||||
const sql = "SELECT [column:inside], :actual";
|
||||
expect(extractSqlParameters(sql, { databaseType: "sqlserver" })).toEqual(["actual"]);
|
||||
expect(substituteSqlParameters(sql, { actual: { kind: "number", value: "7" } }, { databaseType: "sqlserver" })).toBe("SELECT [column:inside], 7");
|
||||
});
|
||||
|
||||
it.each(["sqlite", "jdbc", "access"] as const)("preserves bracketed identifiers for %s", (databaseType) => {
|
||||
expect(extractSqlParameters("SELECT [column:inside], :actual", { databaseType })).toEqual(["actual"]);
|
||||
});
|
||||
|
||||
it("keeps historical bracket scanning when no database dialect is supplied", () => {
|
||||
expect(extractSqlParameters("SELECT [column:inside], :actual")).toEqual(["actual"]);
|
||||
});
|
||||
|
||||
it("keeps question marks as positional placeholders for other databases", () => {
|
||||
expect(extractSqlParameters("select payload ? 'callingResults' from events", { databaseType: "mysql" })).toEqual(["?1"]);
|
||||
});
|
||||
|
||||
@@ -60,6 +60,22 @@ describe("expandSqlVariables", () => {
|
||||
expect(expandSqlVariables(sql)).toEqual({ sql, expanded: false });
|
||||
});
|
||||
|
||||
it("keeps PostgreSQL ARRAY literals from corrupting later variable references", () => {
|
||||
const sql = ["@set selected = 7;", "select ARRAY[']']::varchar[], @selected, ${missing}"].join("\n");
|
||||
expect(expandSqlVariables(sql, { databaseType: "postgres" }).sql).toBe("select ARRAY[']']::varchar[], 7, ${missing}");
|
||||
});
|
||||
|
||||
it("keeps multiline PostgreSQL ARRAY values in @set declarations", () => {
|
||||
const sql = ["@set values = ARRAY[", " 'first',", " 'second'", "];", "select @values;"].join("\n");
|
||||
|
||||
expect(expandSqlVariables(sql, { databaseType: "postgres" }).sql).toBe("select ARRAY[\n 'first',\n 'second'\n];");
|
||||
});
|
||||
|
||||
it.each(["sqlserver", "sqlite", "jdbc"] as const)("preserves %s bracket identifiers during variable expansion", (databaseType) => {
|
||||
const sql = ["@set selected = 7;", "select [column:@ignored], @selected"].join("\n");
|
||||
expect(expandSqlVariables(sql, { databaseType }).sql).toBe("select [column:@ignored], 7");
|
||||
});
|
||||
|
||||
it("keeps a value's own quotes and parentheses intact", () => {
|
||||
const sql = ["@set filter = (status = 'drafted' and deleted_at is null);", "select * from t where @filter"].join("\n");
|
||||
expect(expandSqlVariables(sql).sql).toBe("select * from t where (status = 'drafted' and deleted_at is null)");
|
||||
|
||||
Reference in new issue
Block a user