mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 11:59:00 +00:00
## Summary Fixes all oxlint `no-unused-expressions` violations across the codebase by: - Adding an oxlint override to disable the rule for `src/js/builtins/**`, where special syntax markers like `$getter`, `$constructor`, etc. are intentionally used as standalone expressions - Converting short-circuit expressions (`condition && fn()`) to proper if statements for improved code clarity - Wrapping intentional property access side effects (e.g., `this.stdio;`, `err.stack;`) with the `void` operator - Converting ternary expressions used for control flow to if/else statements ## Test plan - [x] `bun lint` passes with no errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
689 lines
19 KiB
TypeScript
689 lines
19 KiB
TypeScript
// Hardcoded module "sqlite"
|
|
import type * as SqliteTypes from "bun:sqlite";
|
|
|
|
const kSafeIntegersFlag = 1 << 1;
|
|
const kStrictFlag = 1 << 2;
|
|
|
|
const defineProperties = Object.defineProperties;
|
|
const toStringTag = Symbol.toStringTag;
|
|
const isArray = Array.isArray;
|
|
const isTypedArray = ArrayBuffer.isView;
|
|
|
|
let internalFieldTuple;
|
|
|
|
function initializeSQL() {
|
|
({ 0: SQL, 1: internalFieldTuple } = $cpp("JSSQLStatement.cpp", "createJSSQLStatementConstructor"));
|
|
}
|
|
|
|
function createChangesObject() {
|
|
return {
|
|
changes: $getInternalField(internalFieldTuple, 0),
|
|
lastInsertRowid: $getInternalField(internalFieldTuple, 1),
|
|
};
|
|
}
|
|
|
|
const constants = {
|
|
SQLITE_OPEN_READONLY: 0x00000001 /* Ok for sqlite3_open_v2() */,
|
|
SQLITE_OPEN_READWRITE: 0x00000002 /* Ok for sqlite3_open_v2() */,
|
|
SQLITE_OPEN_CREATE: 0x00000004 /* Ok for sqlite3_open_v2() */,
|
|
SQLITE_OPEN_DELETEONCLOSE: 0x00000008 /* VFS only */,
|
|
SQLITE_OPEN_EXCLUSIVE: 0x00000010 /* VFS only */,
|
|
SQLITE_OPEN_AUTOPROXY: 0x00000020 /* VFS only */,
|
|
SQLITE_OPEN_URI: 0x00000040 /* Ok for sqlite3_open_v2() */,
|
|
SQLITE_OPEN_MEMORY: 0x00000080 /* Ok for sqlite3_open_v2() */,
|
|
SQLITE_OPEN_MAIN_DB: 0x00000100 /* VFS only */,
|
|
SQLITE_OPEN_TEMP_DB: 0x00000200 /* VFS only */,
|
|
SQLITE_OPEN_TRANSIENT_DB: 0x00000400 /* VFS only */,
|
|
SQLITE_OPEN_MAIN_JOURNAL: 0x00000800 /* VFS only */,
|
|
SQLITE_OPEN_TEMP_JOURNAL: 0x00001000 /* VFS only */,
|
|
SQLITE_OPEN_SUBJOURNAL: 0x00002000 /* VFS only */,
|
|
SQLITE_OPEN_SUPER_JOURNAL: 0x00004000 /* VFS only */,
|
|
SQLITE_OPEN_NOMUTEX: 0x00008000 /* Ok for sqlite3_open_v2() */,
|
|
SQLITE_OPEN_FULLMUTEX: 0x00010000 /* Ok for sqlite3_open_v2() */,
|
|
SQLITE_OPEN_SHAREDCACHE: 0x00020000 /* Ok for sqlite3_open_v2() */,
|
|
SQLITE_OPEN_PRIVATECACHE: 0x00040000 /* Ok for sqlite3_open_v2() */,
|
|
SQLITE_OPEN_WAL: 0x00080000 /* VFS only */,
|
|
SQLITE_OPEN_NOFOLLOW: 0x01000000 /* Ok for sqlite3_open_v2() */,
|
|
SQLITE_OPEN_EXRESCODE: 0x02000000 /* Extended result codes */,
|
|
SQLITE_PREPARE_PERSISTENT: 0x01,
|
|
SQLITE_PREPARE_NORMALIZE: 0x02,
|
|
SQLITE_PREPARE_NO_VTAB: 0x04,
|
|
|
|
SQLITE_DESERIALIZE_READONLY: 0x00000004 /* Ok for sqlite3_deserialize() */,
|
|
|
|
SQLITE_FCNTL_LOCKSTATE: 1,
|
|
SQLITE_FCNTL_GET_LOCKPROXYFILE: 2,
|
|
SQLITE_FCNTL_SET_LOCKPROXYFILE: 3,
|
|
SQLITE_FCNTL_LAST_ERRNO: 4,
|
|
SQLITE_FCNTL_SIZE_HINT: 5,
|
|
SQLITE_FCNTL_CHUNK_SIZE: 6,
|
|
SQLITE_FCNTL_FILE_POINTER: 7,
|
|
SQLITE_FCNTL_SYNC_OMITTED: 8,
|
|
SQLITE_FCNTL_WIN32_AV_RETRY: 9,
|
|
SQLITE_FCNTL_PERSIST_WAL: 10,
|
|
SQLITE_FCNTL_OVERWRITE: 11,
|
|
SQLITE_FCNTL_VFSNAME: 12,
|
|
SQLITE_FCNTL_POWERSAFE_OVERWRITE: 13,
|
|
SQLITE_FCNTL_PRAGMA: 14,
|
|
SQLITE_FCNTL_BUSYHANDLER: 15,
|
|
SQLITE_FCNTL_TEMPFILENAME: 16,
|
|
SQLITE_FCNTL_MMAP_SIZE: 18,
|
|
SQLITE_FCNTL_TRACE: 19,
|
|
SQLITE_FCNTL_HAS_MOVED: 20,
|
|
SQLITE_FCNTL_SYNC: 21,
|
|
SQLITE_FCNTL_COMMIT_PHASETWO: 22,
|
|
SQLITE_FCNTL_WIN32_SET_HANDLE: 23,
|
|
SQLITE_FCNTL_WAL_BLOCK: 24,
|
|
SQLITE_FCNTL_ZIPVFS: 25,
|
|
SQLITE_FCNTL_RBU: 26,
|
|
SQLITE_FCNTL_VFS_POINTER: 27,
|
|
SQLITE_FCNTL_JOURNAL_POINTER: 28,
|
|
SQLITE_FCNTL_WIN32_GET_HANDLE: 29,
|
|
SQLITE_FCNTL_PDB: 30,
|
|
SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: 31,
|
|
SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: 32,
|
|
SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: 33,
|
|
SQLITE_FCNTL_LOCK_TIMEOUT: 34,
|
|
SQLITE_FCNTL_DATA_VERSION: 35,
|
|
SQLITE_FCNTL_SIZE_LIMIT: 36,
|
|
SQLITE_FCNTL_CKPT_DONE: 37,
|
|
SQLITE_FCNTL_RESERVE_BYTES: 38,
|
|
SQLITE_FCNTL_CKPT_START: 39,
|
|
SQLITE_FCNTL_EXTERNAL_READER: 40,
|
|
SQLITE_FCNTL_CKSM_FILE: 41,
|
|
SQLITE_FCNTL_RESET_CACHE: 42,
|
|
};
|
|
|
|
// This is interface is the JS equivalent of what JSSQLStatement.cpp defines
|
|
interface CppSQLStatement {
|
|
run: (...args: TODO[]) => TODO;
|
|
get: (...args: TODO[]) => TODO;
|
|
all: (...args: TODO[]) => TODO;
|
|
iterate: (...args: TODO[]) => TODO;
|
|
as: (...args: TODO[]) => TODO;
|
|
values: (...args: TODO[]) => TODO;
|
|
raw: (...args: TODO[]) => TODO;
|
|
finalize: (...args: TODO[]) => TODO;
|
|
toString: (...args: TODO[]) => TODO;
|
|
columns: string[];
|
|
columnsCount: number;
|
|
paramsCount: number;
|
|
columnTypes: string[];
|
|
declaredTypes: (string | null)[];
|
|
safeIntegers: boolean;
|
|
}
|
|
|
|
interface CppSQL {
|
|
open(filename: string, flags: number, db: Database): TODO;
|
|
isInTransaction(handle: TODO): boolean;
|
|
loadExtension(handle: TODO, name: string, entryPoint: string): void;
|
|
serialize(handle: TODO, name: string): Buffer;
|
|
deserialize(serialized: NodeJS.TypedArray | ArrayBufferLike, openFlags: number, deserializeFlags: number): TODO;
|
|
fcntl(handle: TODO, ...args: TODO[]): TODO;
|
|
close(handle: TODO, throwOnError: boolean): void;
|
|
setCustomSQLite(path: string): void;
|
|
}
|
|
|
|
let SQL: CppSQL;
|
|
let controllers: WeakMap<Database, any> | undefined;
|
|
|
|
class Statement {
|
|
constructor(raw: CppSQLStatement) {
|
|
this.#raw = raw;
|
|
|
|
switch (raw.paramsCount) {
|
|
case 0: {
|
|
this.get = this.#getNoArgs;
|
|
this.all = this.#allNoArgs;
|
|
this.iterate = this.#iterateNoArgs;
|
|
this.values = this.#valuesNoArgs;
|
|
this.raw = this.#rawNoArgs;
|
|
this.run = this.#runNoArgs;
|
|
break;
|
|
}
|
|
default: {
|
|
this.get = this.#get;
|
|
this.all = this.#all;
|
|
this.iterate = this.#iterate;
|
|
this.values = this.#values;
|
|
this.raw = this.#rawValues;
|
|
this.run = this.#run;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#raw: CppSQLStatement;
|
|
|
|
get: SqliteTypes.Statement["get"];
|
|
all: SqliteTypes.Statement["all"];
|
|
iterate: SqliteTypes.Statement["iterate"];
|
|
values: SqliteTypes.Statement["values"];
|
|
raw: SqliteTypes.Statement["raw"];
|
|
run: SqliteTypes.Statement["run"];
|
|
isFinalized = false;
|
|
|
|
toJSON() {
|
|
return {
|
|
sql: this.native.toString(),
|
|
isFinalized: this.isFinalized,
|
|
paramsCount: this.paramsCount,
|
|
columnNames: this.columnNames,
|
|
};
|
|
}
|
|
|
|
get [toStringTag]() {
|
|
return `"${this.native.toString()}"`;
|
|
}
|
|
|
|
toString() {
|
|
return this.native.toString();
|
|
}
|
|
|
|
get native() {
|
|
return this.#raw;
|
|
}
|
|
|
|
#getNoArgs() {
|
|
return this.#raw.get();
|
|
}
|
|
|
|
#allNoArgs() {
|
|
return this.#raw.all();
|
|
}
|
|
|
|
*#iterateNoArgs() {
|
|
for (let res = this.#raw.iterate(); res; res = this.#raw.iterate()) {
|
|
yield res;
|
|
}
|
|
}
|
|
|
|
#valuesNoArgs() {
|
|
return this.#raw.values();
|
|
}
|
|
|
|
#rawNoArgs() {
|
|
return this.#raw.raw();
|
|
}
|
|
|
|
#runNoArgs() {
|
|
this.#raw.run(internalFieldTuple);
|
|
|
|
return createChangesObject();
|
|
}
|
|
|
|
safeIntegers(updatedValue?: boolean) {
|
|
if (updatedValue !== undefined) {
|
|
this.#raw.safeIntegers = !!updatedValue;
|
|
return this;
|
|
}
|
|
|
|
return this.#raw.safeIntegers;
|
|
}
|
|
|
|
as(ClassType: any) {
|
|
this.#raw.as(ClassType);
|
|
|
|
return this;
|
|
}
|
|
|
|
#get(...args) {
|
|
if (args.length === 0) return this.#getNoArgs();
|
|
var arg0 = args[0];
|
|
// ["foo"] => ["foo"]
|
|
// ("foo") => ["foo"]
|
|
// (Uint8Array(1024)) => [Uint8Array]
|
|
// (123) => [123]
|
|
return !isArray(arg0) && (!arg0 || typeof arg0 !== "object" || isTypedArray(arg0))
|
|
? this.#raw.get(args)
|
|
: this.#raw.get(...args);
|
|
}
|
|
|
|
#all(...args) {
|
|
if (args.length === 0) return this.#allNoArgs();
|
|
var arg0 = args[0];
|
|
// ["foo"] => ["foo"]
|
|
// ("foo") => ["foo"]
|
|
// (Uint8Array(1024)) => [Uint8Array]
|
|
// (123) => [123]
|
|
return !isArray(arg0) && (!arg0 || typeof arg0 !== "object" || isTypedArray(arg0))
|
|
? this.#raw.all(args)
|
|
: this.#raw.all(...args);
|
|
}
|
|
|
|
*#iterate(...args) {
|
|
if (args.length === 0) return yield* this.#iterateNoArgs();
|
|
var arg0 = args[0];
|
|
// ["foo"] => ["foo"]
|
|
// ("foo") => ["foo"]
|
|
// (Uint8Array(1024)) => [Uint8Array]
|
|
// (123) => [123]
|
|
let res =
|
|
!isArray(arg0) && (!arg0 || typeof arg0 !== "object" || isTypedArray(arg0))
|
|
? this.#raw.iterate(args)
|
|
: this.#raw.iterate(...args);
|
|
for (; res; res = this.#raw.iterate()) {
|
|
yield res;
|
|
}
|
|
}
|
|
|
|
#values(...args) {
|
|
if (args.length === 0) return this.#valuesNoArgs();
|
|
var arg0 = args[0];
|
|
// ["foo"] => ["foo"]
|
|
// ("foo") => ["foo"]
|
|
// (Uint8Array(1024)) => [Uint8Array]
|
|
// (123) => [123]
|
|
return !isArray(arg0) && (!arg0 || typeof arg0 !== "object" || isTypedArray(arg0))
|
|
? this.#raw.values(args)
|
|
: this.#raw.values(...args);
|
|
}
|
|
|
|
#rawValues(...args) {
|
|
if (args.length === 0) return this.#rawNoArgs();
|
|
var arg0 = args[0];
|
|
// ["foo"] => ["foo"]
|
|
// ("foo") => ["foo"]
|
|
// (Uint8Array(1024)) => [Uint8Array]
|
|
// (123) => [123]
|
|
return !isArray(arg0) && (!arg0 || typeof arg0 !== "object" || isTypedArray(arg0))
|
|
? this.#raw.raw(args)
|
|
: this.#raw.raw(...args);
|
|
}
|
|
|
|
#run(...args) {
|
|
if (args.length === 0) {
|
|
this.#runNoArgs();
|
|
return createChangesObject();
|
|
}
|
|
|
|
var arg0 = args[0];
|
|
|
|
if (!isArray(arg0) && (!arg0 || typeof arg0 !== "object" || isTypedArray(arg0))) {
|
|
this.#raw.run(internalFieldTuple, args);
|
|
} else {
|
|
this.#raw.run(internalFieldTuple, ...args);
|
|
}
|
|
|
|
return createChangesObject();
|
|
}
|
|
|
|
get columnNames() {
|
|
return this.#raw.columns;
|
|
}
|
|
|
|
get columnTypes() {
|
|
return this.#raw.columnTypes;
|
|
}
|
|
|
|
get declaredTypes() {
|
|
return this.#raw.declaredTypes;
|
|
}
|
|
|
|
get paramsCount() {
|
|
return this.#raw.paramsCount;
|
|
}
|
|
|
|
finalize(...args) {
|
|
this.isFinalized = true;
|
|
return this.#raw.finalize(...args);
|
|
}
|
|
|
|
*[Symbol.iterator]() {
|
|
yield* this.#iterateNoArgs();
|
|
}
|
|
|
|
[Symbol.dispose]() {
|
|
if (!this.isFinalized) {
|
|
this.finalize();
|
|
}
|
|
}
|
|
}
|
|
|
|
const cachedCount = Symbol.for("Bun.Database.cache.count");
|
|
|
|
class Database implements SqliteTypes.Database {
|
|
constructor(
|
|
filenameGiven: string | undefined | NodeJS.TypedArray | Buffer<ArrayBufferLike>,
|
|
options?: SqliteTypes.DatabaseOptions | number,
|
|
) {
|
|
if (typeof filenameGiven === "undefined") {
|
|
} else if (typeof filenameGiven !== "string") {
|
|
if (isTypedArray(filenameGiven)) {
|
|
let deserializeFlags = 0;
|
|
if (options && typeof options === "object") {
|
|
if (options.strict) {
|
|
this.#internalFlags |= kStrictFlag;
|
|
}
|
|
|
|
if (options.safeIntegers) {
|
|
this.#internalFlags |= kSafeIntegersFlag;
|
|
}
|
|
|
|
if (options.readonly) {
|
|
deserializeFlags |= constants.SQLITE_DESERIALIZE_READONLY;
|
|
}
|
|
}
|
|
|
|
this.#handle = Database.#deserialize(filenameGiven, this.#internalFlags, deserializeFlags);
|
|
this.filename = ":memory:";
|
|
|
|
return;
|
|
}
|
|
|
|
throw new TypeError(`Expected 'filename' to be a string, got '${typeof filenameGiven}'`);
|
|
}
|
|
|
|
var filename = typeof filenameGiven === "string" ? filenameGiven.trim() : ":memory:";
|
|
var flags = constants.SQLITE_OPEN_READWRITE | constants.SQLITE_OPEN_CREATE;
|
|
if (typeof options === "object" && options) {
|
|
flags = 0;
|
|
|
|
if (options.readonly) {
|
|
flags = constants.SQLITE_OPEN_READONLY;
|
|
}
|
|
|
|
if ("readOnly" in options) throw new TypeError('Misspelled option "readOnly" should be "readonly"');
|
|
|
|
if (options.create) {
|
|
flags = constants.SQLITE_OPEN_READWRITE | constants.SQLITE_OPEN_CREATE;
|
|
}
|
|
|
|
if (options.readwrite) {
|
|
flags |= constants.SQLITE_OPEN_READWRITE;
|
|
}
|
|
|
|
if ("strict" in options || "safeIntegers" in options) {
|
|
if (options.safeIntegers) {
|
|
this.#internalFlags |= kSafeIntegersFlag;
|
|
}
|
|
|
|
if (options.strict) {
|
|
this.#internalFlags |= kStrictFlag;
|
|
}
|
|
|
|
// If they only set strict: true, reset it back.
|
|
if (flags === 0) {
|
|
flags = constants.SQLITE_OPEN_READWRITE | constants.SQLITE_OPEN_CREATE;
|
|
}
|
|
}
|
|
} else if (typeof options === "number") {
|
|
flags = options;
|
|
}
|
|
|
|
const anonymous = filename === "" || filename === ":memory:";
|
|
if (anonymous && (flags & constants.SQLITE_OPEN_READONLY) !== 0) {
|
|
throw new Error("Cannot open an anonymous database in read-only mode.");
|
|
}
|
|
|
|
if (!SQL) {
|
|
initializeSQL();
|
|
}
|
|
|
|
this.#handle = SQL.open(anonymous ? ":memory:" : filename, flags, this);
|
|
this.filename = filename;
|
|
}
|
|
|
|
#internalFlags = 0;
|
|
#handle;
|
|
#cachedQueriesKeys: string[] = [];
|
|
#cachedQueriesLengths: number[] = [];
|
|
#cachedQueriesValues: Statement[] = [];
|
|
filename;
|
|
#hasClosed = false;
|
|
get handle() {
|
|
return this.#handle;
|
|
}
|
|
|
|
get inTransaction() {
|
|
return SQL.isInTransaction(this.#handle);
|
|
}
|
|
|
|
static open(filename, options) {
|
|
return new Database(filename, options);
|
|
}
|
|
|
|
loadExtension(name, entryPoint) {
|
|
return SQL.loadExtension(this.#handle, name, entryPoint);
|
|
}
|
|
|
|
serialize(optionalName?: string) {
|
|
return SQL.serialize(this.#handle, optionalName || "main");
|
|
}
|
|
|
|
static #deserialize(serialized: NodeJS.TypedArray | ArrayBufferLike, openFlags: number, deserializeFlags: number) {
|
|
if (!SQL) {
|
|
initializeSQL();
|
|
}
|
|
|
|
return SQL.deserialize(serialized, openFlags, deserializeFlags);
|
|
}
|
|
|
|
static deserialize(
|
|
serialized: NodeJS.TypedArray | ArrayBufferLike,
|
|
options: boolean | { readonly?: boolean; strict?: boolean; safeIntegers?: boolean } = false,
|
|
) {
|
|
if (typeof options === "boolean") {
|
|
// Maintain backward compatibility with existing API
|
|
return new Database(serialized, { readonly: options });
|
|
} else if (options && typeof options === "object") {
|
|
return new Database(serialized, options);
|
|
} else {
|
|
return new Database(serialized, 0);
|
|
}
|
|
}
|
|
|
|
[Symbol.dispose]() {
|
|
if (!this.#hasClosed) {
|
|
this.close(true);
|
|
}
|
|
}
|
|
|
|
static setCustomSQLite(path) {
|
|
if (!SQL) {
|
|
initializeSQL();
|
|
}
|
|
|
|
return SQL.setCustomSQLite(path);
|
|
}
|
|
|
|
fileControl(_cmd, _arg) {
|
|
const handle = this.#handle;
|
|
|
|
if (arguments.length <= 2) {
|
|
return SQL.fcntl(handle, null, arguments[0], arguments[1]);
|
|
}
|
|
|
|
return SQL.fcntl(handle, ...arguments);
|
|
}
|
|
|
|
close(throwOnError = false) {
|
|
this.clearQueryCache();
|
|
this.#hasClosed = true;
|
|
return SQL.close(this.#handle, throwOnError);
|
|
}
|
|
clearQueryCache() {
|
|
for (let item of this.#cachedQueriesValues) {
|
|
item?.finalize?.();
|
|
}
|
|
this.#cachedQueriesKeys.length = 0;
|
|
this.#cachedQueriesValues.length = 0;
|
|
this.#cachedQueriesLengths.length = 0;
|
|
}
|
|
|
|
run(query, ...params) {
|
|
if (params.length === 0) {
|
|
SQL.run(this.#handle, this.#internalFlags, internalFieldTuple, query);
|
|
return createChangesObject();
|
|
}
|
|
|
|
var arg0 = params[0];
|
|
if (!isArray(arg0) && (!arg0 || typeof arg0 !== "object" || isTypedArray(arg0))) {
|
|
SQL.run(this.#handle, this.#internalFlags, internalFieldTuple, query, params);
|
|
} else {
|
|
SQL.run(this.#handle, this.#internalFlags, internalFieldTuple, query, ...params);
|
|
}
|
|
|
|
return createChangesObject();
|
|
}
|
|
|
|
prepare(query: string, params: any[] | undefined, flags: number = 0) {
|
|
return new Statement(SQL.prepare(this.#handle, query, params, flags || 0, this.#internalFlags));
|
|
}
|
|
|
|
static MAX_QUERY_CACHE_SIZE = 20;
|
|
|
|
get [cachedCount]() {
|
|
return this.#cachedQueriesKeys.length;
|
|
}
|
|
|
|
query(query) {
|
|
if (typeof query !== "string") {
|
|
throw new TypeError(`Expected 'query' to be a string, got '${typeof query}'`);
|
|
}
|
|
|
|
if (query.length === 0) {
|
|
throw new Error("SQL query cannot be empty.");
|
|
}
|
|
|
|
const willCache = this.#cachedQueriesKeys.length < Database.MAX_QUERY_CACHE_SIZE;
|
|
|
|
// this list should be pretty small
|
|
let index = this.#cachedQueriesLengths.indexOf(query.length);
|
|
while (index !== -1) {
|
|
if (this.#cachedQueriesKeys[index] !== query) {
|
|
index = this.#cachedQueriesLengths.indexOf(query.length, index + 1);
|
|
continue;
|
|
}
|
|
|
|
const stmt = this.#cachedQueriesValues[index];
|
|
if (stmt.isFinalized) {
|
|
return (this.#cachedQueriesValues[index] = this.prepare(
|
|
query,
|
|
undefined,
|
|
willCache ? constants.SQLITE_PREPARE_PERSISTENT : 0,
|
|
));
|
|
}
|
|
return stmt;
|
|
}
|
|
|
|
var stmt = this.prepare(query, undefined, willCache ? constants.SQLITE_PREPARE_PERSISTENT : 0);
|
|
|
|
if (willCache) {
|
|
this.#cachedQueriesKeys.push(query);
|
|
this.#cachedQueriesLengths.push(query.length);
|
|
this.#cachedQueriesValues.push(stmt);
|
|
}
|
|
|
|
return stmt;
|
|
}
|
|
|
|
// Code for transactions is largely copied from better-sqlite3
|
|
// https://github.com/JoshuaWise/better-sqlite3/blob/master/lib/methods/transaction.js
|
|
// thank you @JoshuaWise!
|
|
transaction(fn, self) {
|
|
if (typeof fn !== "function") throw new TypeError("Expected first argument to be a function");
|
|
|
|
const db = this;
|
|
const controller = getController(db, self);
|
|
|
|
// Each version of the transaction function has these same properties
|
|
const properties = {
|
|
default: { value: wrapTransaction(fn, db, controller.default) },
|
|
deferred: { value: wrapTransaction(fn, db, controller.deferred) },
|
|
immediate: {
|
|
value: wrapTransaction(fn, db, controller.immediate),
|
|
},
|
|
exclusive: {
|
|
value: wrapTransaction(fn, db, controller.exclusive),
|
|
},
|
|
database: { value: this, enumerable: true },
|
|
};
|
|
|
|
defineProperties(properties.default.value, properties);
|
|
defineProperties(properties.deferred.value, properties);
|
|
defineProperties(properties.immediate.value, properties);
|
|
defineProperties(properties.exclusive.value, properties);
|
|
|
|
// Return the default version of the transaction function
|
|
return properties.default.value;
|
|
}
|
|
}
|
|
|
|
// @ts-expect-error
|
|
Database.prototype.exec = Database.prototype.run;
|
|
|
|
// Return the database's cached transaction controller, or create a new one
|
|
const getController = (db, _self) => {
|
|
let controller = (controllers ||= new WeakMap()).get(db);
|
|
if (!controller) {
|
|
const shared = {
|
|
commit: db.prepare("COMMIT", undefined, 0),
|
|
rollback: db.prepare("ROLLBACK", undefined, 0),
|
|
savepoint: db.prepare("SAVEPOINT `\t_bs3.\t`", undefined, 0),
|
|
release: db.prepare("RELEASE `\t_bs3.\t`", undefined, 0),
|
|
rollbackTo: db.prepare("ROLLBACK TO `\t_bs3.\t`", undefined, 0),
|
|
};
|
|
|
|
controllers.set(
|
|
db,
|
|
(controller = {
|
|
default: Object.assign({ begin: db.prepare("BEGIN", undefined, 0) }, shared),
|
|
deferred: Object.assign({ begin: db.prepare("BEGIN DEFERRED", undefined, 0) }, shared),
|
|
immediate: Object.assign({ begin: db.prepare("BEGIN IMMEDIATE", undefined, 0) }, shared),
|
|
exclusive: Object.assign({ begin: db.prepare("BEGIN EXCLUSIVE", undefined, 0) }, shared),
|
|
}),
|
|
);
|
|
}
|
|
return controller;
|
|
};
|
|
|
|
// Return a new transaction function by wrapping the given function
|
|
const wrapTransaction = (fn, db, { begin, commit, rollback, savepoint, release, rollbackTo }) =>
|
|
function transaction(this, ...args) {
|
|
let before, after, undo;
|
|
if (db.inTransaction) {
|
|
before = savepoint;
|
|
after = release;
|
|
undo = rollbackTo;
|
|
} else {
|
|
before = begin;
|
|
after = commit;
|
|
undo = rollback;
|
|
}
|
|
try {
|
|
before.run();
|
|
const result = fn.$apply(this, args);
|
|
after.run();
|
|
return result;
|
|
} catch (ex) {
|
|
if (db.inTransaction) {
|
|
undo.run();
|
|
if (undo !== rollback) after.run();
|
|
}
|
|
throw ex;
|
|
}
|
|
};
|
|
|
|
// This class is never actually thrown
|
|
// so we implement instanceof so that it could theoretically be caught
|
|
class SQLiteError extends Error {
|
|
static [Symbol.hasInstance](instance) {
|
|
return instance?.name === "SQLiteError";
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
throw new Error("SQLiteError can only be constructed by bun:sqlite");
|
|
}
|
|
}
|
|
|
|
export default {
|
|
__esModule: true,
|
|
Database,
|
|
Statement,
|
|
constants,
|
|
default: Database,
|
|
SQLiteError,
|
|
};
|