Skip to content
79 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { openD1, sessionConstraint, timedD1, type D1Timing } from "./d1.ts";
5
6/** A binding that answers every query after a tick, and remembers what batch was given. */
7function fakeDb() {
8 const given: object[][] = [];
9 const statement = (query: string, binds: unknown[] = []) => ({
10 query,
11 binds,
12 bind: (...args: unknown[]) => statement(query, args),
13 all: async () => ({ results: [{ query, binds }] }),
14 first: async () => ({ query, binds }),
15 run: async () => ({ success: true }),
16 raw: async () => [[query]],
17 });
18 const db = {
19 prepare: (query: string) => statement(query),
20 batch: async (statements: object[]) => {
21 given.push(statements);
22 return statements.map(() => ({ success: true }));
23 },
24 withSession: (constraint?: string) => ({ ...db, constraint, getBookmark: () => "bm-1" }),
25 };
26 return { db, given };
27}
28
29test("timedD1 counts each statement's trip and each batch, and keeps bindings", async () => {
30 const { db, given } = fakeDb();
31 const timing: D1Timing = { trips: 0, ms: 0 };
32 const timed = timedD1(db, timing);
33 const first = await timed.prepare("SELECT ?").bind(1).first();
34 assert.deepEqual(first, { query: "SELECT ?", binds: [1] });
35 await timed.prepare("SELECT 2").all();
36 await timed.prepare("UPDATE t").run();
37 await timed.prepare("SELECT 3").raw();
38 assert.equal(timing.trips, 4);
39 const a = timed.prepare("A").bind("x");
40 const b = timed.prepare("B");
41 await timed.batch([a, b]);
42 assert.equal(timing.trips, 5, "a batch is one trip however many statements it carries");
43 // The binding got its own statements back, not the counting wrappers.
44 assert.equal(given.length, 1);
45 assert.deepEqual(
46 given[0]!.map((s) => (s as { query: string; binds: unknown[] }).query),
47 ["A", "B"],
48 );
49 assert.deepEqual((given[0]![0] as { binds: unknown[] }).binds, ["x"]);
50 assert.ok(timing.ms >= 0);
51});
52
53test("openD1 reports the service time, the database time and the bookmark", async () => {
54 const { db } = fakeDb();
55 const opened = openD1(db, new Request("https://service/rpc/x", { headers: { "x-d1-bookmark": "first-unconstrained" } }));
56 await opened.db.prepare("SELECT 1").all();
57 await opened.db.prepare("SELECT 2").all();
58 const answered = opened.finish(Response.json({ ok: true }));
59 const timing = answered.headers.get("server-timing") ?? "";
60 assert.match(timing, /^svc;dur=\d+;desc="session", db;dur=\d+;desc="2 round trips"$/);
61 assert.equal(answered.headers.get("x-d1-bookmark"), "bm-1");
62 assert.deepEqual(await answered.json(), { ok: true });
63});
64
65test("openD1 without a session reads the primary and says so, with no db metric when nothing was read", async () => {
66 const { db } = fakeDb();
67 const opened = openD1(db, new Request("https://service/rpc/x"));
68 const answered = opened.finish(new Response("ok"));
69 assert.equal(answered.headers.get("server-timing"), `svc;dur=${/\d+/.exec(answered.headers.get("server-timing") ?? "")?.[0]};desc="primary"`);
70 assert.equal(answered.headers.get("x-d1-bookmark"), null);
71});
72
73test("sessionConstraint takes the two first-* words and a bookmark, and starts anything else on the primary", () => {
74 assert.equal(sessionConstraint(null), null);
75 assert.equal(sessionConstraint("first-primary"), "first-primary");
76 assert.equal(sessionConstraint("first-unconstrained"), "first-unconstrained");
77 assert.equal(sessionConstraint("0000abc-DEF"), "0000abc-DEF");
78 assert.equal(sessionConstraint("not a bookmark!"), "first-primary");
79});