Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,31 @@ const f32FromSqlite = new Float32Array(u8FromSqlite.buffer); // safely convert b

Note 3: The `parseJson` option allows you to disable JSON parsing which is
enabled by default.

## Statement Status Counters

Prepared statements expose SQLite `sqlite3_stmt_status()` values through helper
methods:

- `statusFullscanStep(reset?: boolean): number`
- `statusSort(reset?: boolean): number`
- `statusAutoindex(reset?: boolean): number`
- `statusVmStep(reset?: boolean): number`
- `statusReprepare(reset?: boolean): number`
- `statusRun(reset?: boolean): number`
- `statusFilterMiss(reset?: boolean): number`
- `statusFilterHit(reset?: boolean): number`
- `statusMemused(): number`

Passing `true` to `reset` returns the current counter value and resets it to
zero for future reads.

```ts
const stmt = db.prepare("SELECT text FROM test ORDER BY text");
stmt.values();

const runs = stmt.statusRun(); // > 0
const vmSteps = stmt.statusVmStep(true); // read and reset
const vmStepsAfterReset = stmt.statusVmStep(); // 0
const mem = stmt.statusMemused(); // approximate bytes used by statement
```
11 changes: 11 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,14 @@ export const SQLITE_FLOAT = 2;
export const SQLITE_TEXT = 3;
export const SQLITE_BLOB = 4;
export const SQLITE_NULL = 5;

// Statement Status
export const SQLITE_STMTSTATUS_FULLSCAN_STEP = 1;
export const SQLITE_STMTSTATUS_SORT = 2;
export const SQLITE_STMTSTATUS_AUTOINDEX = 3;
export const SQLITE_STMTSTATUS_VM_STEP = 4;
export const SQLITE_STMTSTATUS_REPREPARE = 5;
export const SQLITE_STMTSTATUS_RUN = 6;
export const SQLITE_STMTSTATUS_FILTER_MISS = 7;
export const SQLITE_STMTSTATUS_FILTER_HIT = 8;
export const SQLITE_STMTSTATUS_MEMUSED = 99;
5 changes: 5 additions & 0 deletions src/ffi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,11 @@ const symbols = {
],
result: "pointer",
},

sqlite3_stmt_status: {
parameters: ["pointer", "i32", "i32"],
result: "i32",
},
} as const satisfies Deno.ForeignLibraryInterface;

let lib: Deno.DynamicLibrary<typeof symbols>["symbols"];
Expand Down
79 changes: 79 additions & 0 deletions src/statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ import {
SQLITE_BLOB,
SQLITE_FLOAT,
SQLITE_INTEGER,
SQLITE_STMTSTATUS_AUTOINDEX,
SQLITE_STMTSTATUS_FILTER_HIT,
SQLITE_STMTSTATUS_FILTER_MISS,
SQLITE_STMTSTATUS_FULLSCAN_STEP,
SQLITE_STMTSTATUS_MEMUSED,
SQLITE_STMTSTATUS_REPREPARE,
SQLITE_STMTSTATUS_RUN,
SQLITE_STMTSTATUS_SORT,
SQLITE_STMTSTATUS_VM_STEP,
SQLITE_TEXT,
} from "./constants.ts";

Expand Down Expand Up @@ -39,6 +48,7 @@ const {
sqlite3_bind_parameter_name,
sqlite3_changes,
sqlite3_column_int,
sqlite3_stmt_status,
} = ffi;

/** Types that can be possibly serialized as SQLite bind values */
Expand All @@ -55,6 +65,17 @@ export type BindValue =
| BindValue[]
| { [key: string]: BindValue };

type StmtStatusOp =
| typeof SQLITE_STMTSTATUS_FULLSCAN_STEP
| typeof SQLITE_STMTSTATUS_SORT
| typeof SQLITE_STMTSTATUS_AUTOINDEX
| typeof SQLITE_STMTSTATUS_VM_STEP
| typeof SQLITE_STMTSTATUS_REPREPARE
| typeof SQLITE_STMTSTATUS_RUN
| typeof SQLITE_STMTSTATUS_FILTER_MISS
| typeof SQLITE_STMTSTATUS_FILTER_HIT
| typeof SQLITE_STMTSTATUS_MEMUSED;

export type BindParameters = BindValue[] | Record<string, BindValue>;
export type RestBindParameters = BindValue[] | [BindParameters];

Expand Down Expand Up @@ -738,6 +759,64 @@ export class Statement<TStatement extends object = Record<string, any>> {
}
}

#status(op: StmtStatusOp, reset?: boolean): number {
return sqlite3_stmt_status(this.#handle, op, reset ? 1 : 0);
}

/** This is the number of times that SQLite has stepped forward in a table as part of a full table scan.
* Large numbers for this counter may indicate opportunities for performance improvement through careful use of indices. */
statusFullscanStep(reset?: boolean): number {
return this.#status(SQLITE_STMTSTATUS_FULLSCAN_STEP, reset);
}

/** This is the number of sort operations that have occurred.
* A non-zero value in this counter may indicate an opportunity to improve performance through careful use of indices. */
statusSort(reset?: boolean): number {
return this.#status(SQLITE_STMTSTATUS_SORT, reset);
}

/** This is the number of rows inserted into transient indices that were created automatically in order to help joins run faster.
* A non-zero value in this counter may indicate an opportunity to improve performance by adding permanent indices that do not need to be reinitialized each time the statement is run. */
statusAutoindex(reset?: boolean): number {
return this.#status(SQLITE_STMTSTATUS_AUTOINDEX, reset);
}

/** This is the number of virtual machine operations executed by the prepared statement if that number is less than or equal to 2147483647.
* The number of virtual machine operations can be used as a proxy for the total work done by the prepared statement.
* If the number of virtual machine operations exceeds 2147483647 then the value returned by this statement status code is undefined. */
statusVmStep(reset?: boolean): number {
return this.#status(SQLITE_STMTSTATUS_VM_STEP, reset);
}

/** This is the number of times that the prepare statement has been automatically regenerated due to schema changes or changes to bound parameters that might affect the query plan. */
statusReprepare(reset?: boolean): number {
return this.#status(SQLITE_STMTSTATUS_REPREPARE, reset);
}

/** This is the number of times that the prepared statement has been run.
* A single "run" for the purposes of this counter is one or more calls to sqlite3_step() followed by a call to sqlite3_reset().
* The counter is incremented on the first sqlite3_step() call of each cycle. */
statusRun(reset?: boolean): number {
return this.#status(SQLITE_STMTSTATUS_RUN, reset);
}

/** This is the number of times that a join step was bypassed because a Bloom filter returned not-found. */
statusFilterMiss(reset?: boolean): number {
return this.#status(SQLITE_STMTSTATUS_FILTER_MISS, reset);
}

/** The corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of times that the Bloom filter returned a find,
* and thus the join step had to be processed as normal. */
statusFilterHit(reset?: boolean): number {
return this.#status(SQLITE_STMTSTATUS_FILTER_HIT, reset);
}

/** This is the approximate number of bytes of heap memory used to store the prepared statement.
* This value is not actually a counter. */
statusMemused(): number {
return this.#status(SQLITE_STMTSTATUS_MEMUSED);
}

/** Free up the statement object. */
finalize(): void {
if (!STATEMENTS_TO_DB.has(this.#handle)) return;
Expand Down
36 changes: 36 additions & 0 deletions test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,42 @@ Deno.test("sqlite", async (t) => {
assertEquals(db.totalChanges, 12);
});

await t.step("statement status", () => {
const stmt = db.prepare("select text from test order by text");

assert(stmt.statusMemused() > 0);
assertEquals(stmt.statusRun(), 0);
assertEquals(stmt.statusVmStep(), 0);
assertEquals(stmt.statusSort(), 0);

const rows = stmt.values<[string]>();
assert(rows.length > 0);

assertEquals(stmt.statusRun(), 1);
assert(stmt.statusVmStep() > 0);
assert(stmt.statusFullscanStep() > 0);
assert(stmt.statusSort() > 0);

assertEquals(typeof stmt.statusAutoindex(), "number");
assertEquals(typeof stmt.statusReprepare(), "number");
assertEquals(typeof stmt.statusFilterMiss(), "number");
assertEquals(typeof stmt.statusFilterHit(), "number");

const sortCount = stmt.statusSort(true);
assert(sortCount > 0);
assertEquals(stmt.statusSort(), 0);

const runCount = stmt.statusRun(true);
assert(runCount > 0);
assertEquals(stmt.statusRun(), 0);

const vmStepCount = stmt.statusVmStep(true);
assert(vmStepCount > 0);
assertEquals(stmt.statusVmStep(), 0);

stmt.finalize();
});

await t.step("query array", () => {
const row = db.prepare("select * from test where integer = 0").values<
[number, string, number, Uint8Array, null]
Expand Down
Loading