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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ edition = "2021"
crate-type = ["cdylib"]

[dependencies]
libsql = { version = "0.9.18", features = ["encryption"] }
libsql = { version = "0.9.20", features = ["encryption"] }
napi = { version = "2", default-features = false, features = ["napi6", "tokio_rt", "async"] }
napi-derive = "2"
once_cell = "1.18.0"
Expand Down
12 changes: 9 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export interface SyncResult {
export declare function databasePrepareSync(db: Database, sql: string): Statement
/** Syncs the database in blocking mode. */
export declare function databaseSyncSync(db: Database): SyncResult
/** Executes SQL in blocking mode. */
export declare function databaseExecSync(db: Database, sql: string): void
/** Gets first row from statement in blocking mode. */
export declare function statementGetSync(stmt: Statement, params?: unknown | undefined | null): unknown
/** Runs a statement in blocking mode. */
export declare function statementRunSync(stmt: Statement, params?: unknown | undefined | null): RunResult
export declare function statementIterateSync(stmt: Statement, params?: unknown | undefined | null): RowsIterator
/** SQLite `run()` result object */
export interface RunResult {
Expand Down Expand Up @@ -109,7 +115,7 @@ export declare class Database {
* * `env` - The environment.
* * `sql` - The SQL statement to execute.
*/
exec(sql: string): void
exec(sql: string): Promise<void>
/**
* Syncs the database.
*
Expand Down Expand Up @@ -146,7 +152,7 @@ export declare class Statement {
*
* * `params` - The parameters to bind to the statement.
*/
run(params?: unknown | undefined | null): RunResult
run(params?: unknown | undefined | null): object
/**
* Executes a SQL statement and returns the first row.
*
Expand All @@ -155,7 +161,7 @@ export declare class Statement {
* * `env` - The environment.
* * `params` - The parameters to bind to the statement.
*/
get(params?: unknown | undefined | null): unknown
get(params?: unknown | undefined | null): object
/**
* Create an iterator over the rows of a statement.
*
Expand Down
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,15 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { Database, databasePrepareSync, databaseSyncSync, Statement, statementIterateSync, RowsIterator, iteratorNextSync, Record } = nativeBinding
const { Database, databasePrepareSync, databaseSyncSync, databaseExecSync, Statement, statementGetSync, statementRunSync, statementIterateSync, RowsIterator, iteratorNextSync, Record } = nativeBinding

module.exports.Database = Database
module.exports.databasePrepareSync = databasePrepareSync
module.exports.databaseSyncSync = databaseSyncSync
module.exports.databaseExecSync = databaseExecSync
module.exports.Statement = Statement
module.exports.statementGetSync = statementGetSync
module.exports.statementRunSync = statementRunSync
module.exports.statementIterateSync = statementIterateSync
module.exports.RowsIterator = RowsIterator
module.exports.iteratorNextSync = iteratorNextSync
Expand Down
58 changes: 29 additions & 29 deletions integration-tests/tests/async.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,21 @@ test.serial("Statement.run() [positional]", async (t) => {
const db = t.context.db;

const stmt = await db.prepare("INSERT INTO users(name, email) VALUES (?, ?)");
const info = stmt.run(["Carol", "carol@example.net"]);
const info = await stmt.run(["Carol", "carol@example.net"]);
t.is(info.changes, 1);
t.is(info.lastInsertRowid, 3);

// Verify that the data is inserted
const stmt2 = await db.prepare("SELECT * FROM users WHERE id = 3");
t.is(stmt2.get().name, "Carol");
t.is(stmt2.get().email, "carol@example.net");
t.is((await stmt2.get()).name, "Carol");
t.is((await stmt2.get()).email, "carol@example.net");
});

test.serial("Statement.get() returns no rows", async (t) => {
const db = t.context.db;

const stmt = await db.prepare("SELECT * FROM users WHERE id = 0");
t.is(stmt.get(), undefined);
t.is((await stmt.get()), undefined);
});

test.serial("Statement.get() [no parameters]", async (t) => {
Expand All @@ -70,7 +70,7 @@ test.serial("Statement.get() [no parameters]", async (t) => {
var stmt = 0;

stmt = await db.prepare("SELECT * FROM users");
t.is(stmt.get().name, "Alice");
t.is((await stmt.get()).name, "Alice");
t.deepEqual(await stmt.raw().get(), [1, 'Alice', 'alice@example.org']);
});

Expand All @@ -80,15 +80,15 @@ test.serial("Statement.get() [positional]", async (t) => {
var stmt = 0;

stmt = await db.prepare("SELECT * FROM users WHERE id = ?");
t.is(stmt.get(0), undefined);
t.is(stmt.get([0]), undefined);
t.is(stmt.get(1).name, "Alice");
t.is(stmt.get(2).name, "Bob");
t.is((await stmt.get(0)), undefined);
t.is((await stmt.get([0])), undefined);
t.is((await stmt.get(1)).name, "Alice");
t.is((await stmt.get(2)).name, "Bob");

stmt = await db.prepare("SELECT * FROM users WHERE id = ?1");
t.is(stmt.get({1: 0}), undefined);
t.is(stmt.get({1: 1}).name, "Alice");
t.is(stmt.get({1: 2}).name, "Bob");
t.is((await stmt.get({1: 0})), undefined);
t.is((await stmt.get({1: 1})).name, "Alice");
t.is((await stmt.get({1: 2})).name, "Bob");
});

test.serial("Statement.get() [named]", async (t) => {
Expand All @@ -97,27 +97,27 @@ test.serial("Statement.get() [named]", async (t) => {
var stmt = undefined;

stmt = await db.prepare("SELECT * FROM users WHERE id = :id");
t.is(stmt.get({ id: 0 }), undefined);
t.is(stmt.get({ id: 1 }).name, "Alice");
t.is(stmt.get({ id: 2 }).name, "Bob");
t.is((await stmt.get({ id: 0 })), undefined);
t.is((await stmt.get({ id: 1 })).name, "Alice");
t.is((await stmt.get({ id: 2 })).name, "Bob");

stmt = await db.prepare("SELECT * FROM users WHERE id = @id");
t.is(stmt.get({ id: 0 }), undefined);
t.is(stmt.get({ id: 1 }).name, "Alice");
t.is(stmt.get({ id: 2 }).name, "Bob");
t.is((await stmt.get({ id: 0 })), undefined);
t.is((await stmt.get({ id: 1 })).name, "Alice");
t.is((await stmt.get({ id: 2 })).name, "Bob");

stmt = await db.prepare("SELECT * FROM users WHERE id = $id");
t.is(stmt.get({ id: 0 }), undefined);
t.is(stmt.get({ id: 1 }).name, "Alice");
t.is(stmt.get({ id: 2 }).name, "Bob");
t.is((await stmt.get({ id: 0 })), undefined);
t.is((await stmt.get({ id: 1 })).name, "Alice");
t.is((await stmt.get({ id: 2 })).name, "Bob");
});


test.serial("Statement.get() [raw]", async (t) => {
const db = t.context.db;

const stmt = await db.prepare("SELECT * FROM users WHERE id = ?");
t.deepEqual(stmt.raw().get(1), [1, "Alice", "alice@example.org"]);
t.deepEqual(await stmt.raw().get(1), [1, "Alice", "alice@example.org"]);
});

test.serial("Statement.iterate() [empty]", async (t) => {
Expand Down Expand Up @@ -253,9 +253,9 @@ test.serial("Database.transaction()", async (t) => {
"INSERT INTO users(name, email) VALUES (:name, :email)"
);

const insertMany = db.transaction((users) => {
const insertMany = db.transaction(async (users) => {
t.is(db.inTransaction, true);
for (const user of users) insert.run(user);
for (const user of users) await insert.run(user);
});

t.is(db.inTransaction, false);
Expand All @@ -267,19 +267,19 @@ test.serial("Database.transaction()", async (t) => {
t.is(db.inTransaction, false);

const stmt = await db.prepare("SELECT * FROM users WHERE id = ?");
t.is(stmt.get(3).name, "Joey");
t.is(stmt.get(4).name, "Sally");
t.is(stmt.get(5).name, "Junior");
t.is((await stmt.get(3)).name, "Joey");
t.is((await stmt.get(4)).name, "Sally");
t.is((await stmt.get(5)).name, "Junior");
});

test.serial("Database.transaction().immediate()", async (t) => {
const db = t.context.db;
const insert = await db.prepare(
"INSERT INTO users(name, email) VALUES (:name, :email)"
);
const insertMany = db.transaction((users) => {
const insertMany = db.transaction(async (users) => {
t.is(db.inTransaction, true);
for (const user of users) insert.run(user);
for (const user of users) await insert.run(user);
});
t.is(db.inTransaction, false);
await insertMany.immediate([
Expand Down
51 changes: 4 additions & 47 deletions integration-tests/tests/concurrency.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ test("Concurrent reads", async (t) => {

const promises = [];
for (let i = 0; i < 100; i++) {
promises.push(stmt.get(t.context.aliceId));
promises.push(stmt.get(t.context.bobId));
promises.push(await stmt.get(t.context.aliceId));
promises.push(await stmt.get(t.context.bobId));
}

const results = await Promise.all(promises);
Expand Down Expand Up @@ -63,7 +63,7 @@ test("Concurrent writes", async (t) => {

const promises = [];
for (let i = 0; i < 50; i++) {
promises.push(stmt.run({
promises.push(await stmt.run({
id: generateUUID(),
name: `User${i}`,
email: `user${i}@example.com`
Expand All @@ -79,49 +79,6 @@ test("Concurrent writes", async (t) => {
cleanup(t.context);
});

test("Concurrent transaction isolation", async (t) => {
const db = t.context.db;

await db.exec(`
DROP TABLE IF EXISTS transaction_users;
CREATE TABLE transaction_users (
id TEXT PRIMARY KEY,
name TEXT,
email TEXT
)
`);

const aliceId = generateUUID();
const bobId = generateUUID();

await db.exec(`
INSERT INTO transaction_users (id, name, email) VALUES
('${aliceId}', 'Alice', 'alice@example.org'),
('${bobId}', 'Bob', 'bob@example.com')
`);

const updateUser = db.transaction(async (id, name, email) => {
const stmt = await db.prepare("UPDATE transaction_users SET name = :name, email = :email WHERE id = :id");
await stmt.run({ id, name, email });
});

const promises = [];
for (let i = 0; i < 10; i++) {
promises.push(updateUser(aliceId, `Alice${i}`, `alice${i}@example.org`));
promises.push(updateUser(bobId, `Bob${i}`, `bob${i}@example.com`));
}

await Promise.all(promises);

const stmt = await db.prepare("SELECT * FROM transaction_users ORDER BY name");
const results = await stmt.all();
t.is(results.length, 2);
t.truthy(results[0].name.startsWith('Alice'));
t.truthy(results[1].name.startsWith('Bob'));

cleanup(t.context);
});

test("Concurrent reads and writes", async (t) => {
const db = t.context.db;

Expand All @@ -146,7 +103,7 @@ test("Concurrent reads and writes", async (t) => {
const promises = [];
for (let i = 0; i < 20; i++) {
promises.push(readStmt.get(aliceId));
writeStmt.run({
await writeStmt.run({
id: generateUUID(),
name: `User${i}`,
email: `user${i}@example.com`
Expand Down
22 changes: 11 additions & 11 deletions promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ class Database {

const db = this;
const wrapTxn = (mode) => {
return (...bindParameters) => {
db.exec("BEGIN " + mode);
return async (...bindParameters) => {
await db.exec("BEGIN " + mode);
try {
const result = fn(...bindParameters);
db.exec("COMMIT");
const result = await fn(...bindParameters);
await db.exec("COMMIT");
return result;
} catch (err) {
db.exec("ROLLBACK");
await db.exec("ROLLBACK");
throw err;
}
};
Expand Down Expand Up @@ -172,9 +172,9 @@ class Database {
*
* @param {string} sql - The SQL statement string to execute.
*/
exec(sql) {
async exec(sql) {
try {
this.db.exec(sql);
await this.db.exec(sql);
} catch (err) {
throw convertError(err);
}
Expand Down Expand Up @@ -257,9 +257,9 @@ class Statement {
/**
* Executes the SQL statement and returns an info object.
*/
run(...bindParameters) {
async run(...bindParameters) {
try {
return this.stmt.run(...bindParameters);
return await this.stmt.run(...bindParameters);
} catch (err) {
throw convertError(err);
}
Expand All @@ -270,9 +270,9 @@ class Statement {
*
* @param bindParameters - The bind parameters for executing the statement.
*/
get(...bindParameters) {
async get(...bindParameters) {
try {
return this.stmt.get(...bindParameters);
return await this.stmt.get(...bindParameters);
} catch (err) {
throw convertError(err);
}
Expand Down
Loading
Loading