Skip to content
Open
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
83 changes: 71 additions & 12 deletions compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ function splitBindParameters(bindParameters) {
return { params: bindParameters.length === 1 ? bindParameters[0] : bindParameters, queryOptions: undefined };
}

const symbolDispose = typeof Symbol.dispose === "symbol" ? Symbol.dispose : null;
const hasWeakRef = typeof WeakRef === "function";

/**
* Database represents a connection that can prepare and execute SQL statements.
*/
Expand All @@ -61,6 +64,8 @@ class Database {
constructor(path, opts) {
this.db = new NativeDb(path, opts);
this.memory = this.db.memory
this._closed = false;
this._statements = hasWeakRef ? new Set() : null;
const db = this.db;
Object.defineProperties(this, {
inTransaction: {
Expand Down Expand Up @@ -95,7 +100,13 @@ class Database {
prepare(sql) {
try {
const stmt = databasePrepareSync(this.db, sql);
return new Statement(stmt);
const wrappedStmt = new Statement(stmt, this);
if (this._statements != null) {
const statementRef = new WeakRef(wrappedStmt);
wrappedStmt._statementRef = statementRef;
this._statements.add(statementRef);
}
return wrappedStmt;
} catch (err) {
throw convertError(err);
}
Expand Down Expand Up @@ -215,6 +226,21 @@ class Database {
* Closes the database connection.
*/
close() {
if (this._closed) {
return;
}
this._closed = true;
if (this._statements != null) {
for (const statementRef of Array.from(this._statements)) {
const statement = statementRef.deref();
if (statement == null) {
this._statements.delete(statementRef);
continue;
}
statement.close();
}
this._statements.clear();
}
this.db.close();
}

Expand All @@ -240,8 +266,36 @@ class Database {
* Statement represents a prepared SQL statement that can be executed.
*/
class Statement {
constructor(stmt) {
constructor(stmt, database) {
this.stmt = stmt;
this.database = database;
this._statementRef = null;
this._closed = false;
}

close() {
if (this._closed) {
return this;
}
this._closed = true;
if (this.database != null && this.database._statements != null && this._statementRef != null) {
this.database._statements.delete(this._statementRef);
}
if (this.database != null) {
this.database = null;
}
if (this.stmt != null) {
this.stmt.close();
this.stmt = null;
}
return this;
}

_getNativeStatement() {
if (this._closed || this.stmt == null) {
throw new TypeError("The database connection is not open");
}
return this.stmt;
}

/**
Expand All @@ -250,7 +304,7 @@ class Statement {
* @param raw Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled.
*/
raw(raw) {
this.stmt.raw(raw);
this._getNativeStatement().raw(raw);
return this;
}

Expand All @@ -260,7 +314,7 @@ class Statement {
* @param pluckMode Enable or disable pluck mode. If you don't pass the parameter, pluck mode is enabled.
*/
pluck(pluckMode) {
this.stmt.pluck(pluckMode);
this._getNativeStatement().pluck(pluckMode);
return this;
}

Expand All @@ -270,12 +324,12 @@ class Statement {
* @param timing Enable or disable query timing. If you don't pass the parameter, query timing is enabled.
*/
timing(timingMode) {
this.stmt.timing(timingMode);
this._getNativeStatement().timing(timingMode);
return this;
}

get reader() {
return this.stmt.columns().length > 0;
return this._getNativeStatement().columns().length > 0;
}

/**
Expand All @@ -284,7 +338,7 @@ class Statement {
run(...bindParameters) {
try {
const { params, queryOptions } = splitBindParameters(bindParameters);
return statementRunSync(this.stmt, params, queryOptions);
return statementRunSync(this._getNativeStatement(), params, queryOptions);
} catch (err) {
throw convertError(err);
}
Expand All @@ -298,7 +352,7 @@ class Statement {
get(...bindParameters) {
try {
const { params, queryOptions } = splitBindParameters(bindParameters);
return statementGetSync(this.stmt, params, queryOptions);
return statementGetSync(this._getNativeStatement(), params, queryOptions);
} catch (err) {
throw convertError(err);
}
Expand All @@ -312,7 +366,7 @@ class Statement {
iterate(...bindParameters) {
try {
const { params, queryOptions } = splitBindParameters(bindParameters);
const it = statementIterateSync(this.stmt, params, queryOptions);
const it = statementIterateSync(this._getNativeStatement(), params, queryOptions);
return {
next: () => iteratorNextSync(it),
[Symbol.iterator]() {
Expand Down Expand Up @@ -349,26 +403,31 @@ class Statement {
* Interrupts the statement.
*/
interrupt() {
this.stmt.interrupt();
this._getNativeStatement().interrupt();
return this;
}

/**
* Returns the columns in the result set returned by this prepared statement.
*/
columns() {
return this.stmt.columns();
return this._getNativeStatement().columns();
}

/**
* Toggle 64-bit integer support.
*/
safeIntegers(toggle) {
this.stmt.safeIntegers(toggle);
this._getNativeStatement().safeIntegers(toggle);
return this;
}
}

if (symbolDispose != null) {
Database.prototype[symbolDispose] = Database.prototype.close;
Statement.prototype[symbolDispose] = Statement.prototype.close;
}

module.exports = Database;
module.exports.SqliteError = SqliteError;
module.exports.Authorization = Authorization;
5 changes: 5 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ Cancel ongoing operations and make them return at earliest opportunity.
### close() ⇒ this

Closes the database connection.
All statements created from this database are closed as well.

# class Statement

Expand Down Expand Up @@ -159,6 +160,10 @@ Executes the SQL statement and returns an iterator to the resulting rows.
| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |
| queryOptions | <code>object</code> | Optional per-query overrides (for example, `{ queryTimeout: 100 }`). |

### close() ⇒ this

Closes the prepared statement and releases its resources.

### pluck([toggleState]) ⇒ this

This function is currently not supported.
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ export declare class Statement {
columns(): unknown[]
safeIntegers(toggle?: boolean | undefined | null): this
interrupt(): void
/** Closes the statement. */
close(): void
}
/** A raw iterator over rows. The JavaScript layer wraps this in a iterable. */
export declare class RowsIterator {
Expand Down
24 changes: 24 additions & 0 deletions integration-tests/tests/async.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,30 @@ test.serial("Database.exec() after close()", async (t) => {
});
});

test.serial("Statement.get() after Database.close()", async (t) => {
const db = t.context.db;
const stmt = await db.prepare("SELECT 1");
db.close();
await t.throwsAsync(async () => {
await stmt.get();
}, {
instanceOf: TypeError,
message: "The database connection is not open"
});
});

test.serial("Statement.close()", async (t) => {
const db = t.context.db;
const stmt = await db.prepare("SELECT 1");
stmt.close();
await t.throwsAsync(async () => {
await stmt.get();
}, {
instanceOf: TypeError,
message: "The database connection is not open"
});
});

test.serial("Database.interrupt()", async (t) => {
const db = t.context.db;
const stmt = await db.prepare("WITH RECURSIVE infinite_loop(n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM infinite_loop) SELECT * FROM infinite_loop;");
Expand Down
28 changes: 28 additions & 0 deletions integration-tests/tests/sync.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,34 @@ test.serial("Database.exec() after close()", async (t) => {
});
});

test.serial("Statement.get() after Database.close()", async (t) => {
const db = t.context.db;
const stmt = db.prepare("SELECT 1");
db.close();
t.throws(() => {
stmt.get();
}, {
instanceOf: TypeError,
message: "The database connection is not open"
});
});

test.serial("Statement.close()", async (t) => {
const db = t.context.db;
const stmt = db.prepare("SELECT 1");
if (typeof stmt.close !== "function") {
t.pass();
return;
}
stmt.close();
t.throws(() => {
stmt.get();
}, {
instanceOf: TypeError,
message: "The database connection is not open"
});
});

test.serial("Timeout option", async (t) => {
const timeout = 1000;
const path = genDatabaseFilename();
Expand Down
Loading
Loading