Skip to content

Commit

Permalink
feat: support static placeholders with sql template
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Jul 21, 2023
1 parent 84c52d8 commit 378fe62
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 33 deletions.
28 changes: 2 additions & 26 deletions src/database.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Connector, Database, Primitive } from "./types";
import { sqlTemplate } from "./template";
import type { Connector, Database } from "./types";

const SQL_WITH_RES_RE = /^select/i;

Expand Down Expand Up @@ -26,28 +27,3 @@ export function createDatabase(connector: Connector): Database {
},
};
}

export function sqlTemplate(
strings: TemplateStringsArray,
...values: Primitive[]
): [string, Primitive[]] {
if (!isTemplateStringsArray(strings) || !Array.isArray(values)) {
throw new Error("[db0] invalid template invokation");
}

let result = strings[0] || "";

for (let i = 1; i < strings.length; i++) {
result += `?${strings[i] ?? ""}`;
}

return [result, values];
}

function isTemplateStringsArray(
strings: unknown,
): strings is TemplateStringsArray {
return (
Array.isArray(strings) && "raw" in strings && Array.isArray(strings.raw)
);
}
34 changes: 34 additions & 0 deletions src/template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Primitive } from "./types";

export function sqlTemplate(
strings: TemplateStringsArray,
...values: Primitive[]
): [string, Primitive[]] {
if (!isTemplateStringsArray(strings) || !Array.isArray(values)) {
throw new Error("[db0] invalid template invokation");
}

const staticIndexes: number[] = [];

let result = strings[0] || "";
for (let i = 1; i < strings.length; i++) {
if (result.endsWith("{") && strings[i].startsWith("}")) {
result = result.slice(0, -1) + values[i - 1] + strings[i].slice(1);
staticIndexes.push(i - 1);
continue;
}
result += `?${strings[i] ?? ""}`;
}

const dynamicValues = values.filter((_, i) => !staticIndexes.includes(i));

return [result.trim(), dynamicValues];
}

function isTemplateStringsArray(
strings: unknown,
): strings is TemplateStringsArray {
return (
Array.isArray(strings) && "raw" in strings && Array.isArray(strings.raw)
);
}
19 changes: 12 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ export type Connector = {
prepare: (sql: string) => Statement;
};

type SqlTemplate<T> = (
strings: TemplateStringsArray,
...values: Primitive[]
) => T;
type DefaultSQLResult = {
lastInsertRowid?: number;
changes?: number;
error?: string;
rows?: { id?: string | number; [key: string]: unknown }[];
};

export type Database = {
export interface Database {
exec: (sql: string) => Promise<ExecResult>;
prepare: (sql: string) => Statement;
sql: SqlTemplate<Promise<{ rows?: unknown[]; [key: string]: unknown }>>;
};
sql: <T = DefaultSQLResult>(
strings: TemplateStringsArray,
...values: Primitive[]
) => Promise<T>;
}
24 changes: 24 additions & 0 deletions test/template.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, it, expect } from "vitest";
import { sqlTemplate } from "../src/template";

describe("SQL Template", () => {
const tests = [
{
sql: sqlTemplate`SELECT * FROM {${"users"}} WHERE age > ${25} AND type = ${"test"}`,
query: "SELECT * FROM users WHERE age > ? AND type = ?",
values: [25, "test"],
},
{
sql: sqlTemplate`INSERT INTO {${"users"}} ({${"name"}}, {${"age"}}) VALUES (${25}, ${"test"})`,
query: "INSERT INTO users (name, age) VALUES (?, ?)",
values: [25, "test"],
},
];

for (const test of tests) {
const testName = `${test.query} (${test.values.join(", ")}))`;
it(testName, () => {
expect(test.sql).toEqual([test.query, test.values]);
});
}
});

0 comments on commit 378fe62

Please sign in to comment.