728 lines
19 KiB
TypeScript
728 lines
19 KiB
TypeScript
import type { Database } from "./database.ts";
|
|
import { readCstr, toCString, unwrap } from "./util.ts";
|
|
import ffi from "./ffi.ts";
|
|
import {
|
|
SQLITE3_DONE,
|
|
SQLITE3_ROW,
|
|
SQLITE_BLOB,
|
|
SQLITE_FLOAT,
|
|
SQLITE_INTEGER,
|
|
SQLITE_TEXT,
|
|
} from "./constants.ts";
|
|
|
|
const {
|
|
sqlite3_prepare_v2,
|
|
sqlite3_reset,
|
|
sqlite3_clear_bindings,
|
|
sqlite3_step,
|
|
sqlite3_column_count,
|
|
sqlite3_column_type,
|
|
sqlite3_column_value,
|
|
sqlite3_value_subtype,
|
|
sqlite3_column_text,
|
|
sqlite3_finalize,
|
|
sqlite3_column_int64,
|
|
sqlite3_column_double,
|
|
sqlite3_column_blob,
|
|
sqlite3_column_bytes,
|
|
sqlite3_column_name,
|
|
sqlite3_expanded_sql,
|
|
sqlite3_bind_parameter_count,
|
|
sqlite3_bind_int,
|
|
sqlite3_bind_int64,
|
|
sqlite3_bind_text,
|
|
sqlite3_bind_blob,
|
|
sqlite3_bind_double,
|
|
sqlite3_bind_parameter_index,
|
|
sqlite3_sql,
|
|
sqlite3_stmt_readonly,
|
|
sqlite3_bind_parameter_name,
|
|
sqlite3_changes,
|
|
sqlite3_column_int,
|
|
} = ffi;
|
|
|
|
/** Types that can be possibly serialized as SQLite bind values */
|
|
export type BindValue =
|
|
| number
|
|
| string
|
|
| symbol
|
|
| bigint
|
|
| boolean
|
|
| null
|
|
| undefined
|
|
| Date
|
|
| Uint8Array
|
|
| BindValue[]
|
|
| { [key: string]: BindValue };
|
|
|
|
export type BindParameters = BindValue[] | Record<string, BindValue>;
|
|
export type RestBindParameters = BindValue[] | [BindParameters];
|
|
|
|
/** Maps sqlite_stmt* pointers to sqlite* db pointers. */
|
|
export const STATEMENTS_TO_DB = new Map<Deno.PointerValue, Deno.PointerValue>();
|
|
|
|
const emptyStringBuffer = new Uint8Array(1);
|
|
|
|
const statementFinalizer = new FinalizationRegistry(
|
|
(ptr: Deno.PointerValue) => {
|
|
if (STATEMENTS_TO_DB.has(ptr)) {
|
|
sqlite3_finalize(ptr);
|
|
STATEMENTS_TO_DB.delete(ptr);
|
|
}
|
|
},
|
|
);
|
|
|
|
// https://github.com/sqlite/sqlite/blob/195611d8e6fc0bba559a49e91e6ceb42e4bdd6ba/src/json.c#L125-L126
|
|
const JSON_SUBTYPE = 74;
|
|
|
|
const BIG_MAX = BigInt(Number.MAX_SAFE_INTEGER);
|
|
|
|
function getColumn(handle: Deno.PointerValue, i: number, int64: boolean): any {
|
|
const ty = sqlite3_column_type(handle, i);
|
|
|
|
if (ty === SQLITE_INTEGER && !int64) return sqlite3_column_int(handle, i);
|
|
|
|
switch (ty) {
|
|
case SQLITE_TEXT: {
|
|
const ptr = sqlite3_column_text(handle, i);
|
|
if (ptr === null) return null;
|
|
const text = readCstr(ptr, 0);
|
|
const value = sqlite3_column_value(handle, i);
|
|
const subtype = sqlite3_value_subtype(value);
|
|
if (subtype === JSON_SUBTYPE) {
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch (_error) {
|
|
return text;
|
|
}
|
|
}
|
|
return text;
|
|
}
|
|
|
|
case SQLITE_INTEGER: {
|
|
const val = sqlite3_column_int64(handle, i);
|
|
if (val < -BIG_MAX || val > BIG_MAX) {
|
|
return val;
|
|
}
|
|
return Number(val);
|
|
}
|
|
|
|
case SQLITE_FLOAT: {
|
|
return sqlite3_column_double(handle, i);
|
|
}
|
|
|
|
case SQLITE_BLOB: {
|
|
const ptr = sqlite3_column_blob(handle, i);
|
|
|
|
if (ptr === null) {
|
|
return new Uint8Array();
|
|
}
|
|
|
|
const bytes = sqlite3_column_bytes(handle, i);
|
|
return new Uint8Array(
|
|
Deno.UnsafePointerView.getArrayBuffer(ptr, bytes).slice(0),
|
|
);
|
|
}
|
|
|
|
default: {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents a prepared statement.
|
|
*
|
|
* See `Database#prepare` for more information.
|
|
*/
|
|
export class Statement {
|
|
#handle: Deno.PointerValue;
|
|
#finalizerToken: { handle: Deno.PointerValue };
|
|
#bound = false;
|
|
#hasNoArgs = false;
|
|
#unsafeConcurrency;
|
|
|
|
/**
|
|
* Whether the query might call into JavaScript or not.
|
|
*
|
|
* Must enable if the query makes use of user defined functions,
|
|
* otherwise there can be V8 crashes.
|
|
*
|
|
* Off by default. Causes performance degradation.
|
|
*/
|
|
callback = false;
|
|
|
|
/** Unsafe Raw (pointer) to the sqlite object */
|
|
get unsafeHandle(): Deno.PointerValue {
|
|
return this.#handle;
|
|
}
|
|
|
|
/** SQL string including bindings */
|
|
get expandedSql(): string {
|
|
return readCstr(sqlite3_expanded_sql(this.#handle)!);
|
|
}
|
|
|
|
/** The SQL string that we passed when creating statement */
|
|
get sql(): string {
|
|
return readCstr(sqlite3_sql(this.#handle)!);
|
|
}
|
|
|
|
/** Whether this statement doesn't make any direct changes to the DB */
|
|
get readonly(): boolean {
|
|
return sqlite3_stmt_readonly(this.#handle) !== 0;
|
|
}
|
|
|
|
/** Simply run the query without retrieving any output there may be. */
|
|
run(...args: RestBindParameters): number {
|
|
return this.#runWithArgs(...args);
|
|
}
|
|
|
|
/**
|
|
* Run the query and return the resulting rows where rows are array of columns.
|
|
*/
|
|
values<T extends unknown[] = any[]>(...args: RestBindParameters): T[] {
|
|
return this.#valuesWithArgs(...args);
|
|
}
|
|
|
|
/**
|
|
* Run the query and return the resulting rows where rows are objects
|
|
* mapping column name to their corresponding values.
|
|
*/
|
|
all<T extends object = Record<string, any>>(
|
|
...args: RestBindParameters
|
|
): T[] {
|
|
return this.#allWithArgs(...args);
|
|
}
|
|
|
|
#bindParameterCount: number;
|
|
|
|
/** Number of parameters (to be) bound */
|
|
get bindParameterCount(): number {
|
|
return this.#bindParameterCount;
|
|
}
|
|
|
|
constructor(public db: Database, sql: string) {
|
|
const pHandle = new BigUint64Array(1);
|
|
unwrap(
|
|
sqlite3_prepare_v2(
|
|
db.unsafeHandle,
|
|
toCString(sql),
|
|
sql.length,
|
|
pHandle,
|
|
null,
|
|
),
|
|
db.unsafeHandle,
|
|
);
|
|
this.#handle = Deno.UnsafePointer.create(pHandle[0]);
|
|
STATEMENTS_TO_DB.set(this.#handle, db.unsafeHandle);
|
|
this.#unsafeConcurrency = db.unsafeConcurrency;
|
|
this.#finalizerToken = { handle: this.#handle };
|
|
statementFinalizer.register(this, this.#handle, this.#finalizerToken);
|
|
|
|
if (
|
|
(this.#bindParameterCount = sqlite3_bind_parameter_count(
|
|
this.#handle,
|
|
)) === 0
|
|
) {
|
|
this.#hasNoArgs = true;
|
|
this.all = this.#allNoArgs;
|
|
this.values = this.#valuesNoArgs;
|
|
this.run = this.#runNoArgs;
|
|
this.value = this.#valueNoArgs;
|
|
this.get = this.#getNoArgs;
|
|
}
|
|
}
|
|
|
|
/** Shorthand for `this.callback = true`. Enables calling user defined functions. */
|
|
enableCallback(): this {
|
|
this.callback = true;
|
|
return this;
|
|
}
|
|
|
|
/** Get bind parameter name by index */
|
|
bindParameterName(i: number): string {
|
|
return readCstr(sqlite3_bind_parameter_name(this.#handle, i)!);
|
|
}
|
|
|
|
/** Get bind parameter index by name */
|
|
bindParameterIndex(name: string): number {
|
|
if (name[0] !== ":" && name[0] !== "@" && name[0] !== "$") {
|
|
name = ":" + name;
|
|
}
|
|
return sqlite3_bind_parameter_index(this.#handle, toCString(name));
|
|
}
|
|
|
|
#begin(): void {
|
|
sqlite3_reset(this.#handle);
|
|
if (!this.#bound && !this.#hasNoArgs) {
|
|
sqlite3_clear_bindings(this.#handle);
|
|
this.#bindRefs.clear();
|
|
}
|
|
}
|
|
|
|
#bindRefs: Set<any> = new Set();
|
|
|
|
#bind(i: number, param: BindValue): void {
|
|
switch (typeof param) {
|
|
case "number": {
|
|
if (Number.isInteger(param)) {
|
|
if (
|
|
Number.isSafeInteger(param) && param >= -(2 ** 31) &&
|
|
param < 2 ** 31
|
|
) {
|
|
unwrap(sqlite3_bind_int(this.#handle, i + 1, param));
|
|
} else {
|
|
unwrap(sqlite3_bind_int64(this.#handle, i + 1, BigInt(param)));
|
|
}
|
|
} else {
|
|
unwrap(sqlite3_bind_double(this.#handle, i + 1, param));
|
|
}
|
|
break;
|
|
}
|
|
case "string": {
|
|
if (param === "") {
|
|
// Empty string is encoded as empty buffer in Deno. And as of
|
|
// right now (Deno 1.29.1), ffi layer converts it to NULL pointer,
|
|
// which causes sqlite3_bind_text to bind the NULL value instead
|
|
// of an empty string. As a workaround let's use a special
|
|
// non-empty buffer, but specify zero length.
|
|
unwrap(
|
|
sqlite3_bind_text(this.#handle, i + 1, emptyStringBuffer, 0, null),
|
|
);
|
|
} else {
|
|
const str = new TextEncoder().encode(param);
|
|
this.#bindRefs.add(str);
|
|
unwrap(
|
|
sqlite3_bind_text(this.#handle, i + 1, str, str.byteLength, null),
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "object": {
|
|
if (param === null) {
|
|
// pass
|
|
} else if (param instanceof Uint8Array) {
|
|
this.#bindRefs.add(param);
|
|
unwrap(
|
|
sqlite3_bind_blob(
|
|
this.#handle,
|
|
i + 1,
|
|
param.byteLength === 0 ? emptyStringBuffer : param,
|
|
param.byteLength,
|
|
null,
|
|
),
|
|
);
|
|
} else if (param instanceof Date) {
|
|
const cstring = toCString(param.toISOString());
|
|
this.#bindRefs.add(cstring);
|
|
unwrap(
|
|
sqlite3_bind_text(
|
|
this.#handle,
|
|
i + 1,
|
|
cstring,
|
|
-1,
|
|
null,
|
|
),
|
|
);
|
|
} else {
|
|
const cstring = toCString(JSON.stringify(param));
|
|
this.#bindRefs.add(cstring);
|
|
unwrap(
|
|
sqlite3_bind_text(
|
|
this.#handle,
|
|
i + 1,
|
|
cstring,
|
|
-1,
|
|
null,
|
|
),
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case "bigint": {
|
|
unwrap(sqlite3_bind_int64(this.#handle, i + 1, param));
|
|
break;
|
|
}
|
|
|
|
case "boolean":
|
|
unwrap(sqlite3_bind_int(
|
|
this.#handle,
|
|
i + 1,
|
|
param ? 1 : 0,
|
|
));
|
|
break;
|
|
default: {
|
|
throw new Error(`Value of unsupported type: ${Deno.inspect(param)}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bind parameters to the statement. This method can only be called once
|
|
* to set the parameters to be same throughout the statement. You cannot
|
|
* change the parameters after this method is called.
|
|
*
|
|
* This method is merely just for optimization to avoid binding parameters
|
|
* each time in prepared statement.
|
|
*/
|
|
bind(...params: RestBindParameters): this {
|
|
this.#bindAll(params);
|
|
this.#bound = true;
|
|
return this;
|
|
}
|
|
|
|
#bindAll(params: RestBindParameters | BindParameters): void {
|
|
if (this.#bound) throw new Error("Statement already bound to values");
|
|
if (
|
|
typeof params[0] === "object" && params[0] !== null &&
|
|
!(params[0] instanceof Uint8Array) && !(params[0] instanceof Date)
|
|
) {
|
|
params = params[0];
|
|
}
|
|
if (Array.isArray(params)) {
|
|
for (let i = 0; i < params.length; i++) {
|
|
this.#bind(i, (params as BindValue[])[i]);
|
|
}
|
|
} else {
|
|
for (const [name, param] of Object.entries(params)) {
|
|
const i = this.bindParameterIndex(name);
|
|
if (i === 0) {
|
|
throw new Error(`No such parameter "${name}"`);
|
|
}
|
|
this.#bind(i - 1, param as BindValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
#runNoArgs(): number {
|
|
const handle = this.#handle;
|
|
this.#begin();
|
|
const status = sqlite3_step(handle);
|
|
if (status !== SQLITE3_ROW && status !== SQLITE3_DONE) {
|
|
unwrap(status, this.db.unsafeHandle);
|
|
}
|
|
sqlite3_reset(handle);
|
|
return sqlite3_changes(this.db.unsafeHandle);
|
|
}
|
|
|
|
#runWithArgs(...params: RestBindParameters): number {
|
|
const handle = this.#handle;
|
|
this.#begin();
|
|
this.#bindAll(params);
|
|
const status = sqlite3_step(handle);
|
|
if (!this.#hasNoArgs && !this.#bound && params.length) {
|
|
this.#bindRefs.clear();
|
|
}
|
|
if (status !== SQLITE3_ROW && status !== SQLITE3_DONE) {
|
|
unwrap(status, this.db.unsafeHandle);
|
|
}
|
|
sqlite3_reset(handle);
|
|
return sqlite3_changes(this.db.unsafeHandle);
|
|
}
|
|
|
|
#valuesNoArgs<T extends Array<unknown>>(): T[] {
|
|
const handle = this.#handle;
|
|
this.#begin();
|
|
const columnCount = sqlite3_column_count(handle);
|
|
const result: T[] = [];
|
|
const getRowArray = new Function(
|
|
"getColumn",
|
|
`
|
|
return function(h) {
|
|
return [${
|
|
Array.from({ length: columnCount }).map((_, i) =>
|
|
`getColumn(h, ${i}, ${this.db.int64})`
|
|
)
|
|
.join(", ")
|
|
}];
|
|
};
|
|
`,
|
|
)(getColumn);
|
|
let status = sqlite3_step(handle);
|
|
while (status === SQLITE3_ROW) {
|
|
result.push(getRowArray(handle));
|
|
status = sqlite3_step(handle);
|
|
}
|
|
if (status !== SQLITE3_DONE) {
|
|
unwrap(status, this.db.unsafeHandle);
|
|
}
|
|
sqlite3_reset(handle);
|
|
return result as T[];
|
|
}
|
|
|
|
#valuesWithArgs<T extends Array<unknown>>(
|
|
...params: RestBindParameters
|
|
): T[] {
|
|
const handle = this.#handle;
|
|
this.#begin();
|
|
this.#bindAll(params);
|
|
const columnCount = sqlite3_column_count(handle);
|
|
const result: T[] = [];
|
|
const getRowArray = new Function(
|
|
"getColumn",
|
|
`
|
|
return function(h) {
|
|
return [${
|
|
Array.from({ length: columnCount }).map((_, i) =>
|
|
`getColumn(h, ${i}, ${this.db.int64})`
|
|
)
|
|
.join(", ")
|
|
}];
|
|
};
|
|
`,
|
|
)(getColumn);
|
|
let status = sqlite3_step(handle);
|
|
while (status === SQLITE3_ROW) {
|
|
result.push(getRowArray(handle));
|
|
status = sqlite3_step(handle);
|
|
}
|
|
if (!this.#hasNoArgs && !this.#bound && params.length) {
|
|
this.#bindRefs.clear();
|
|
}
|
|
if (status !== SQLITE3_DONE) {
|
|
unwrap(status, this.db.unsafeHandle);
|
|
}
|
|
sqlite3_reset(handle);
|
|
return result as T[];
|
|
}
|
|
|
|
#rowObjectFn: ((h: Deno.PointerValue) => any) | undefined;
|
|
|
|
getRowObject(): (h: Deno.PointerValue) => any {
|
|
if (!this.#rowObjectFn || !this.#unsafeConcurrency) {
|
|
const columnNames = this.columnNames();
|
|
const getRowObject = new Function(
|
|
"getColumn",
|
|
`
|
|
return function(h) {
|
|
return {
|
|
${
|
|
columnNames.map((name, i) =>
|
|
`"${name}": getColumn(h, ${i}, ${this.db.int64})`
|
|
).join(",\n")
|
|
}
|
|
};
|
|
};
|
|
`,
|
|
)(getColumn);
|
|
this.#rowObjectFn = getRowObject;
|
|
}
|
|
return this.#rowObjectFn!;
|
|
}
|
|
|
|
#allNoArgs<T extends object>(): T[] {
|
|
const handle = this.#handle;
|
|
this.#begin();
|
|
const getRowObject = this.getRowObject();
|
|
const result: T[] = [];
|
|
let status = sqlite3_step(handle);
|
|
while (status === SQLITE3_ROW) {
|
|
result.push(getRowObject(handle));
|
|
status = sqlite3_step(handle);
|
|
}
|
|
if (status !== SQLITE3_DONE) {
|
|
unwrap(status, this.db.unsafeHandle);
|
|
}
|
|
sqlite3_reset(handle);
|
|
return result as T[];
|
|
}
|
|
|
|
#allWithArgs<T extends object>(
|
|
...params: RestBindParameters
|
|
): T[] {
|
|
const handle = this.#handle;
|
|
this.#begin();
|
|
this.#bindAll(params);
|
|
const getRowObject = this.getRowObject();
|
|
const result: T[] = [];
|
|
let status = sqlite3_step(handle);
|
|
while (status === SQLITE3_ROW) {
|
|
result.push(getRowObject(handle));
|
|
status = sqlite3_step(handle);
|
|
}
|
|
if (!this.#hasNoArgs && !this.#bound && params.length) {
|
|
this.#bindRefs.clear();
|
|
}
|
|
if (status !== SQLITE3_DONE) {
|
|
unwrap(status, this.db.unsafeHandle);
|
|
}
|
|
sqlite3_reset(handle);
|
|
return result as T[];
|
|
}
|
|
|
|
/** Fetch only first row as an array, if any. */
|
|
value<T extends Array<unknown>>(
|
|
...params: RestBindParameters
|
|
): T | undefined {
|
|
const handle = this.#handle;
|
|
const int64 = this.db.int64;
|
|
const arr = new Array(sqlite3_column_count(handle));
|
|
sqlite3_reset(handle);
|
|
if (!this.#hasNoArgs && !this.#bound) {
|
|
sqlite3_clear_bindings(handle);
|
|
this.#bindRefs.clear();
|
|
if (params.length) {
|
|
this.#bindAll(params);
|
|
}
|
|
}
|
|
|
|
const status = sqlite3_step(handle);
|
|
|
|
if (!this.#hasNoArgs && !this.#bound && params.length) {
|
|
this.#bindRefs.clear();
|
|
}
|
|
|
|
if (status === SQLITE3_ROW) {
|
|
for (let i = 0; i < arr.length; i++) {
|
|
arr[i] = getColumn(handle, i, int64);
|
|
}
|
|
sqlite3_reset(this.#handle);
|
|
return arr as T;
|
|
} else if (status === SQLITE3_DONE) {
|
|
return;
|
|
} else {
|
|
unwrap(status, this.db.unsafeHandle);
|
|
}
|
|
}
|
|
|
|
#valueNoArgs<T extends Array<unknown>>(): T | undefined {
|
|
const handle = this.#handle;
|
|
const int64 = this.db.int64;
|
|
const cc = sqlite3_column_count(handle);
|
|
const arr = new Array(cc);
|
|
sqlite3_reset(handle);
|
|
const status = sqlite3_step(handle);
|
|
if (status === SQLITE3_ROW) {
|
|
for (let i = 0; i < cc; i++) {
|
|
arr[i] = getColumn(handle, i, int64);
|
|
}
|
|
sqlite3_reset(this.#handle);
|
|
return arr as T;
|
|
} else if (status === SQLITE3_DONE) {
|
|
return;
|
|
} else {
|
|
unwrap(status, this.db.unsafeHandle);
|
|
}
|
|
}
|
|
|
|
#columnNames: string[] | undefined;
|
|
#rowObject: Record<string, unknown> = {};
|
|
|
|
columnNames(): string[] {
|
|
if (!this.#columnNames || !this.#unsafeConcurrency) {
|
|
const columnCount = sqlite3_column_count(this.#handle);
|
|
const columnNames = new Array(columnCount);
|
|
for (let i = 0; i < columnCount; i++) {
|
|
columnNames[i] = readCstr(sqlite3_column_name(this.#handle, i)!);
|
|
}
|
|
this.#columnNames = columnNames;
|
|
this.#rowObject = {};
|
|
for (const name of columnNames) {
|
|
this.#rowObject![name] = undefined;
|
|
}
|
|
}
|
|
return this.#columnNames!;
|
|
}
|
|
|
|
/** Fetch only first row as an object, if any. */
|
|
get<T extends object>(
|
|
...params: RestBindParameters
|
|
): T | undefined {
|
|
const handle = this.#handle;
|
|
const int64 = this.db.int64;
|
|
|
|
const columnNames = this.columnNames();
|
|
|
|
const row: Record<string, unknown> = {};
|
|
sqlite3_reset(handle);
|
|
if (!this.#hasNoArgs && !this.#bound) {
|
|
sqlite3_clear_bindings(handle);
|
|
this.#bindRefs.clear();
|
|
if (params.length) {
|
|
this.#bindAll(params);
|
|
}
|
|
}
|
|
|
|
const status = sqlite3_step(handle);
|
|
|
|
if (!this.#hasNoArgs && !this.#bound && params.length) {
|
|
this.#bindRefs.clear();
|
|
}
|
|
|
|
if (status === SQLITE3_ROW) {
|
|
for (let i = 0; i < columnNames.length; i++) {
|
|
row[columnNames[i]] = getColumn(handle, i, int64);
|
|
}
|
|
sqlite3_reset(this.#handle);
|
|
return row as T;
|
|
} else if (status === SQLITE3_DONE) {
|
|
return;
|
|
} else {
|
|
unwrap(status, this.db.unsafeHandle);
|
|
}
|
|
}
|
|
|
|
#getNoArgs<T extends object>(): T | undefined {
|
|
const handle = this.#handle;
|
|
const int64 = this.db.int64;
|
|
const columnNames = this.columnNames();
|
|
const row: Record<string, unknown> = this.#rowObject;
|
|
sqlite3_reset(handle);
|
|
const status = sqlite3_step(handle);
|
|
if (status === SQLITE3_ROW) {
|
|
for (let i = 0; i < columnNames?.length; i++) {
|
|
row[columnNames[i]] = getColumn(handle, i, int64);
|
|
}
|
|
sqlite3_reset(handle);
|
|
return row as T;
|
|
} else if (status === SQLITE3_DONE) {
|
|
return;
|
|
} else {
|
|
unwrap(status, this.db.unsafeHandle);
|
|
}
|
|
}
|
|
|
|
/** Free up the statement object. */
|
|
finalize(): void {
|
|
if (!STATEMENTS_TO_DB.has(this.#handle)) return;
|
|
this.#bindRefs.clear();
|
|
statementFinalizer.unregister(this.#finalizerToken);
|
|
STATEMENTS_TO_DB.delete(this.#handle);
|
|
unwrap(sqlite3_finalize(this.#handle));
|
|
}
|
|
|
|
/** Coerces the statement to a string, which in this case is expanded SQL. */
|
|
toString(): string {
|
|
return readCstr(sqlite3_expanded_sql(this.#handle)!);
|
|
}
|
|
|
|
/** Iterate over resultant rows from query. */
|
|
*iter(...params: RestBindParameters): IterableIterator<any> {
|
|
this.#begin();
|
|
this.#bindAll(params);
|
|
const getRowObject = this.getRowObject();
|
|
let status = sqlite3_step(this.#handle);
|
|
while (status === SQLITE3_ROW) {
|
|
yield getRowObject(this.#handle);
|
|
status = sqlite3_step(this.#handle);
|
|
}
|
|
if (status !== SQLITE3_DONE) {
|
|
unwrap(status, this.db.unsafeHandle);
|
|
}
|
|
sqlite3_reset(this.#handle);
|
|
}
|
|
|
|
[Symbol.iterator](): IterableIterator<any> {
|
|
return this.iter();
|
|
}
|
|
|
|
[Symbol.dispose](): void {
|
|
this.finalize();
|
|
}
|
|
|
|
[Symbol.for("Deno.customInspect")](): string {
|
|
return `Statement { ${this.expandedSql} }`;
|
|
}
|
|
}
|