feat: 支持 SQL 参数查询
* Fix: 支持 SQL 参数查询 * Fix: 修复前端格式检查 --------- Co-authored-by: staff <staff@qimaos-MacBook-Pro.local> Co-authored-by: t8y2 <1156263951@qq.com>
This commit is contained in:
15 files changed
+681
-2
No files matched your search
@@ -0,0 +1,47 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { loadSqlParameterHistory, MAX_SQL_PARAMETER_HISTORY, rememberSqlParameterValue } from "../sqlParameterHistory";
|
||||
|
||||
const storage = new Map<string, string>();
|
||||
|
||||
beforeEach(() => {
|
||||
storage.clear();
|
||||
vi.stubGlobal("localStorage", {
|
||||
getItem: (key: string) => storage.get(key) ?? null,
|
||||
setItem: (key: string, value: string) => storage.set(key, value),
|
||||
removeItem: (key: string) => storage.delete(key),
|
||||
});
|
||||
});
|
||||
|
||||
describe("sqlParameterHistory", () => {
|
||||
it("stores recent values by parameter name", () => {
|
||||
rememberSqlParameterValue("start_date", { kind: "string", value: "2026-01-01" });
|
||||
rememberSqlParameterValue("start_date", { kind: "string", value: "2026-01-02" });
|
||||
|
||||
expect(loadSqlParameterHistory("start_date")).toEqual([
|
||||
{ kind: "string", value: "2026-01-02" },
|
||||
{ kind: "string", value: "2026-01-01" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("deduplicates by type and value and moves the latest value to the front", () => {
|
||||
rememberSqlParameterValue("min_id", { kind: "number", value: "1" });
|
||||
rememberSqlParameterValue("min_id", { kind: "number", value: "2" });
|
||||
rememberSqlParameterValue("min_id", { kind: "number", value: "1" });
|
||||
|
||||
expect(loadSqlParameterHistory("min_id")).toEqual([
|
||||
{ kind: "number", value: "1" },
|
||||
{ kind: "number", value: "2" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("matches parameter names case-insensitively and limits history size", () => {
|
||||
for (let i = 0; i < MAX_SQL_PARAMETER_HISTORY + 2; i += 1) {
|
||||
rememberSqlParameterValue("UserId", { kind: "number", value: String(i) });
|
||||
}
|
||||
|
||||
const history = loadSqlParameterHistory("userId");
|
||||
expect(history).toHaveLength(MAX_SQL_PARAMETER_HISTORY);
|
||||
expect(history[0]).toEqual({ kind: "number", value: String(MAX_SQL_PARAMETER_HISTORY + 1) });
|
||||
expect(history.at(-1)).toEqual({ kind: "number", value: "2" });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { extractSqlParameters, sqlParameterLiteral, substituteSqlParameters } from "../sqlParameters";
|
||||
|
||||
describe("extractSqlParameters", () => {
|
||||
it("extracts unique template parameters in order", () => {
|
||||
const sql = "select * from t where pt_dt between ${start_date} and ${end_date} or pt_dt = ${start_date}";
|
||||
expect(extractSqlParameters(sql)).toEqual(["start_date", "end_date"]);
|
||||
});
|
||||
|
||||
it("ignores placeholders inside strings, quoted identifiers, and comments", () => {
|
||||
const sql = `
|
||||
select '\${quoted}' as a, "\${identifier}" as b, \`\${mysql_identifier}\`
|
||||
-- \${line_comment}
|
||||
# \${hash_comment}
|
||||
/* \${block_comment} */
|
||||
from t
|
||||
where id = \${id}
|
||||
`;
|
||||
expect(extractSqlParameters(sql)).toEqual(["id"]);
|
||||
});
|
||||
|
||||
it("ignores placeholders inside Postgres dollar-quoted strings", () => {
|
||||
const sql = "select $$ ${body_param} $$, $tag$ ${tag_param} $tag$, ${real_param}";
|
||||
expect(extractSqlParameters(sql)).toEqual(["real_param"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("substituteSqlParameters", () => {
|
||||
it("replaces placeholders with SQL literals", () => {
|
||||
const sql = "select * from t where dt >= ${start_date} and amount > ${amount} and enabled = ${enabled}";
|
||||
expect(
|
||||
substituteSqlParameters(sql, {
|
||||
start_date: { kind: "string", value: "2026-06-26" },
|
||||
amount: { kind: "number", value: "100.50" },
|
||||
enabled: { kind: "boolean", value: "true" },
|
||||
}),
|
||||
).toBe("select * from t where dt >= '2026-06-26' and amount > 100.50 and enabled = TRUE");
|
||||
});
|
||||
|
||||
it("escapes string values and supports null and raw SQL", () => {
|
||||
const sql = "select ${name}, ${empty_value}, ${expression}";
|
||||
expect(
|
||||
substituteSqlParameters(sql, {
|
||||
name: { kind: "string", value: "O'Reilly" },
|
||||
empty_value: { kind: "null", value: "" },
|
||||
expression: { kind: "raw", value: "current_date" },
|
||||
}),
|
||||
).toBe("select 'O''Reilly', NULL, current_date");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sqlParameterLiteral", () => {
|
||||
it("falls back to quoted strings for invalid boolean input", () => {
|
||||
expect(sqlParameterLiteral({ kind: "boolean", value: "maybe" })).toBe("'maybe'");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user