Skip to content

Commit feea30f

Browse files
committed
feat: support string templates for query
1 parent e7a551c commit feea30f

File tree

6 files changed

+89
-36
lines changed

6 files changed

+89
-36
lines changed

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,19 @@ pnpm add db0
3434
import { createDB, sql } from "db0";
3535
import sqlite from "db0/connectors/better-sqlite3";
3636

37+
// Initiate database with SQLite connector
3738
const db = createDB(sqlite({}));
3839

39-
await db.exec(
40-
"CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, firstName TEXT, lastName TEXT, email TEXT)"
41-
);
40+
// Create users table
41+
await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`;
4242

43-
await db.prepare("INSERT INTO users VALUES (?, 'John', 'Doe', '')").run(id);
43+
// Add a new user
44+
const userId = "1001";
45+
await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', '')`;
4446

45-
const row = await db.prepare("SELECT * FROM users WHERE id = ?").get(id);
46-
47-
console.log(row);
47+
// Query for users
48+
const { rows } = await db.query`SELECT * FROM users WHERE id = ${userId}`;
49+
console.log(rows);
4850
```
4951

5052
<!-- 👉 Check out the [the documentation](https://db0.unjs.io) for usage information. -->

docs/content/2.usage.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@ pnpm add db0
2828
import { createDB, sql } from "db0";
2929
import sqlite from "db0/connectors/better-sqlite3";
3030

31+
// Initiate database with SQLite connector
3132
const db = createDB(sqlite({}));
3233

33-
await db.exec(
34-
"CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, firstName TEXT, lastName TEXT, email TEXT)"
35-
);
34+
// Create users table
35+
await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`;
3636

37-
await db.prepare("INSERT INTO users VALUES (?, 'John', 'Doe', '')").run(id);
37+
// Add a new user
38+
const userId = "1001";
39+
await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', '')`;
3840

39-
const row = await db.prepare("SELECT * FROM users WHERE id = ?").get(id);
40-
41-
console.log(row);
41+
// Query for users
42+
const { rows } = await db.query`SELECT * FROM users WHERE id = ${userId}`;
43+
console.log(rows);
4244
```

src/connectors/postgresql.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default function sqliteConnector(opts: ConnectorOptions) {
4444
run(...params) {
4545
return query(this._sql, params || this._params).then((r) => ({
4646
result: r,
47+
rows: r.rows,
4748
}));
4849
},
4950
get(...params) {

src/db.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,52 @@
1-
import type { Connector, Database } from "./types";
1+
import type { Connector, Database, Primitive } from "./types";
22

33
export function createDatabase(connector: Connector): Database {
44
return <Database>{
55
exec: (sql: string) => {
66
return Promise.resolve(connector.exec(sql));
77
},
8+
89
prepare: (sql: string) => {
910
return connector.prepare(sql);
1011
},
12+
13+
sql: async (strings, ...values) => {
14+
const [sql, params] = sqlTemplate(strings, ...values);
15+
const res = await connector.prepare(sql).run(...params);
16+
return res;
17+
},
18+
19+
query: async (strings, ...values) => {
20+
const [sql, params] = sqlTemplate(strings, ...values);
21+
const rows = await connector.prepare(sql).all(...params);
22+
return {
23+
rows,
24+
};
25+
},
1126
};
1227
}
28+
29+
export function sqlTemplate(
30+
strings: TemplateStringsArray,
31+
...values: Primitive[]
32+
): [string, Primitive[]] {
33+
if (!isTemplateStringsArray(strings) || !Array.isArray(values)) {
34+
throw new Error("[db0] invalid template invokation");
35+
}
36+
37+
let result = strings[0] || "";
38+
39+
for (let i = 1; i < strings.length; i++) {
40+
result += `?${strings[i] ?? ""}`;
41+
}
42+
43+
return [result, values];
44+
}
45+
46+
function isTemplateStringsArray(
47+
strings: unknown
48+
): strings is TemplateStringsArray {
49+
return (
50+
Array.isArray(strings) && "raw" in strings && Array.isArray(strings.raw)
51+
);
52+
}

src/types.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
export type Primitive = string | number | boolean | undefined | null;
2+
13
export type Statement = {
2-
bind(...params: unknown[]): Statement;
3-
all(...params: unknown[]): Promise<unknown[]>;
4-
run(...params: unknown[]): Promise<{ success: boolean }>;
5-
get(...params: unknown[]): Promise<unknown>;
4+
bind(...params: Primitive[]): Statement;
5+
all(...params: Primitive[]): Promise<unknown[]>;
6+
run(...params: Primitive[]): Promise<{ success: boolean }>;
7+
get(...params: Primitive[]): Promise<unknown>;
68
};
79

810
export type ExecResult = unknown;
@@ -13,7 +15,15 @@ export type Connector = {
1315
prepare: (sql: string) => Statement;
1416
};
1517

18+
type SqlTemplate<T> = (
19+
strings: TemplateStringsArray,
20+
...values: Primitive[]
21+
) => T;
22+
1623
export type Database = {
1724
exec: (sql: string) => Promise<ExecResult>;
1825
prepare: (sql: string) => Statement;
26+
27+
sql: SqlTemplate<Promise<ExecResult>>;
28+
query: SqlTemplate<Promise<{ rows: unknown[] }>>;
1929
};

test/connectors/_tests.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,28 @@ export function testConnector(opts: { connector: Connector }) {
77
db = createDatabase(opts.connector);
88
});
99

10+
const userId = "1001";
11+
1012
it("drop and create table", async () => {
11-
await db.exec(`DROP TABLE IF EXISTS users`);
12-
const res = await db.exec(
13-
`CREATE TABLE users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`
14-
);
15-
expect(res).toBeDefined();
13+
await db.sql`DROP TABLE IF EXISTS users`;
14+
await db.sql`CREATE TABLE users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`;
1615
});
1716

1817
it("insert", async () => {
19-
const res = await db
20-
.prepare("INSERT INTO users VALUES (?, 'John', 'Doe', '')")
21-
.run("123");
22-
expect(res).toBeDefined();
18+
await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', '')`;
2319
});
2420

2521
it("select", async () => {
26-
const row = await db.prepare("SELECT * FROM users WHERE id = ?").get("123");
27-
expect(row).toMatchInlineSnapshot(`
28-
{
29-
"email": "",
30-
"firstName": "John",
31-
"id": "123",
32-
"lastName": "Doe",
33-
}
22+
const { rows } = await db.query`SELECT * FROM users WHERE id = ${userId}`;
23+
expect(rows).toMatchInlineSnapshot(`
24+
[
25+
{
26+
"email": "",
27+
"firstName": "John",
28+
"id": "1001",
29+
"lastName": "Doe",
30+
},
31+
]
3432
`);
3533
});
3634
}

0 commit comments

Comments
 (0)