mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-30 13:24:22 +01:00
Merge 'Import JavaScript bindings test suite from libSQL' from Mikaël Francoeur
This PR imports the `sync` test suite from libSQL, and modifies the export structure match `better-sqlite3`, so that at least a few tests from the new test suite are passing. I also changed the `package.json` to expose `wrapper.js` as an entrypoint. I think the plain `index.js` was probably never meant to be exposed directly to clients, because the wrapper does some important transformation. It's also how libSQL-js is [structured](https://github.com/tursodatabase/libsql- js/blob/main/package.json#L20). ## DualTest test runner The test suite that I imported was previously run twice, with different environment variables: one run for libSQL-js, one run for better- sqlite3. This worked well with libSQL, because it's compatible with better-sqlite3. But Turso isn't there yet, so even though all tests need to run on better-sqlite3, not all tests are passing on Turso. To make it possible to run the test suite on both implementation, and to make it possible to track the progress of Turso, I've added a `DualTest` test runner that emulates the API of the Ava test runner, but instead of a single `test()` function, it exposes two methods: `both()`, and `onlySqlitePasses()`. The first one runs the test on both implementations, and the second one also runs it on both, but expects that Turso will fail. When Turso is completely compatible with better-sqlite3, it will be easy to remove the dual-test runner, since it has the same API as Ava. ## Future development ### Existing tests The existing tests were divided in two files: `better-sqlite3.spec.mjs`, and `dual-test.mjs` that are kept in sync manually. The first one is mostly a superset of the second one, with additional tests indicating behaviour that isn't implemented in Turso yet. I want to merge these test suites to also use the dual-test test runner. This will make it easier to evolve test suites and to track the progress of Turso. ### `SqliteError` Modifying the test called `errors` to `both()` will show that Turso is still missing an `SqliteError` type, to be compatible with better- sqlite3. I have the required changes in a stash and will open a PR shortly. ----- as part of https://github.com/tursodatabase/turso/issues/1900 Closes #1941
This commit is contained in:
@@ -23,7 +23,7 @@ npm install @tursodatabase/turso
|
||||
### In-Memory Database
|
||||
|
||||
```javascript
|
||||
import { Database } from '@tursodatabase/turso';
|
||||
import Database from '@tursodatabase/turso';
|
||||
|
||||
// Create an in-memory database
|
||||
const db = new Database(':memory:');
|
||||
@@ -48,7 +48,7 @@ console.log(users);
|
||||
### File-Based Database
|
||||
|
||||
```javascript
|
||||
import { Database } from '@tursodatabase/turso';
|
||||
import Database from '@tursodatabase/turso';
|
||||
|
||||
// Create or open a database file
|
||||
const db = new Database('my-database.db');
|
||||
|
||||
82
bindings/javascript/__test__/dual-test.mjs
Normal file
82
bindings/javascript/__test__/dual-test.mjs
Normal file
@@ -0,0 +1,82 @@
|
||||
import avaTest from "ava";
|
||||
import turso from "../wrapper.js";
|
||||
import sqlite from "better-sqlite3";
|
||||
|
||||
class DualTest {
|
||||
|
||||
#libs = { turso, sqlite };
|
||||
#beforeEaches = [];
|
||||
#pathFn;
|
||||
#options;
|
||||
|
||||
constructor(path_opt, options = {}) {
|
||||
if (typeof path_opt === 'function') {
|
||||
this.#pathFn = path_opt;
|
||||
} else {
|
||||
this.#pathFn = () => path_opt ?? "hello.db";
|
||||
}
|
||||
this.#options = options;
|
||||
}
|
||||
|
||||
beforeEach(fn) {
|
||||
this.#beforeEaches.push(fn);
|
||||
}
|
||||
|
||||
only(name, impl, ...rest) {
|
||||
avaTest.serial.only('[TESTING TURSO] ' + name, this.#wrap('turso', impl), ...rest);
|
||||
avaTest.serial.only('[TESTING BETTER-SQLITE3] ' + name, this.#wrap('sqlite', impl), ...rest);
|
||||
}
|
||||
|
||||
onlySqlitePasses(name, impl, ...rest) {
|
||||
avaTest.serial.failing('[TESTING TURSO] ' + name, this.#wrap('turso', impl), ...rest);
|
||||
avaTest.serial('[TESTING BETTER-SQLITE3] ' + name, this.#wrap('sqlite', impl), ...rest);
|
||||
}
|
||||
|
||||
both(name, impl, ...rest) {
|
||||
avaTest.serial('[TESTING TURSO] ' + name, this.#wrap('turso', impl), ...rest);
|
||||
avaTest.serial('[TESTING BETTER-SQLITE3] ' + name, this.#wrap('sqlite', impl), ...rest);
|
||||
}
|
||||
|
||||
skip(name, impl, ...rest) {
|
||||
avaTest.serial.skip('[TESTING TURSO] ' + name, this.#wrap('turso', impl), ...rest);
|
||||
avaTest.serial.skip('[TESTING BETTER-SQLITE3] ' + name, this.#wrap('sqlite', impl), ...rest);
|
||||
}
|
||||
|
||||
async #runBeforeEach(t) {
|
||||
for (const beforeEach of this.#beforeEaches) {
|
||||
await beforeEach(t);
|
||||
}
|
||||
}
|
||||
|
||||
#wrap(provider, fn) {
|
||||
return async (t, ...rest) => {
|
||||
const path = this.#pathFn();
|
||||
const Lib = this.#libs[provider];
|
||||
const db = this.#connect(Lib, path, this.#options)
|
||||
t.context = {
|
||||
...t,
|
||||
connect: this.#curry(this.#connect)(Lib),
|
||||
db,
|
||||
errorType: Lib.SqliteError,
|
||||
path,
|
||||
provider,
|
||||
};
|
||||
|
||||
t.teardown(() => db.close());
|
||||
await this.#runBeforeEach(t);
|
||||
await fn(t, ...rest);
|
||||
};
|
||||
}
|
||||
|
||||
#connect(constructor, path, options) {
|
||||
return new constructor(path, options);
|
||||
}
|
||||
|
||||
#curry(fn) {
|
||||
return (first) => (...rest) => fn(first, ...rest);
|
||||
}
|
||||
}
|
||||
|
||||
export default DualTest;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import fs from "node:fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "node:path";
|
||||
|
||||
import { Database } from "../wrapper.js";
|
||||
import Database from "../wrapper.js";
|
||||
|
||||
test("Open in-memory database", async (t) => {
|
||||
const [db] = await connect(":memory:");
|
||||
|
||||
456
bindings/javascript/__test__/sync.spec.mjs
Normal file
456
bindings/javascript/__test__/sync.spec.mjs
Normal file
@@ -0,0 +1,456 @@
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import DualTest from "./dual-test.mjs";
|
||||
|
||||
const dualTest = new DualTest();
|
||||
|
||||
new DualTest(":memory:").both("Open in-memory database", async (t) => {
|
||||
const db = t.context.db;
|
||||
t.is(db.memory, true);
|
||||
});
|
||||
|
||||
dualTest.beforeEach(async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
db.exec(`
|
||||
DROP TABLE IF EXISTS users;
|
||||
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)
|
||||
`);
|
||||
db.exec(
|
||||
"INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.org')"
|
||||
);
|
||||
db.exec(
|
||||
"INSERT INTO users (id, name, email) VALUES (2, 'Bob', 'bob@example.com')"
|
||||
);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.prepare() error", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
t.throws(() => {
|
||||
return db.prepare("SYNTAX ERROR");
|
||||
}, {
|
||||
any: true,
|
||||
instanceOf: t.context.errorType,
|
||||
message: 'near "SYNTAX": syntax error'
|
||||
});
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.run() returning rows", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT 1");
|
||||
const info = stmt.run();
|
||||
t.is(info.changes, 0);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.run() [positional]", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("INSERT INTO users(name, email) VALUES (?, ?)");
|
||||
const info = stmt.run(["Carol", "carol@example.net"]);
|
||||
t.is(info.changes, 1);
|
||||
t.is(info.lastInsertRowid, 3);
|
||||
|
||||
// Verify that the data is inserted
|
||||
const stmt2 = db.prepare("SELECT * FROM users WHERE id = 3");
|
||||
t.is(stmt2.get().name, "Carol");
|
||||
t.is(stmt2.get().email, "carol@example.net");
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.run() [named]", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("INSERT INTO users(name, email) VALUES (@name, @email);");
|
||||
const info = stmt.run({ "name": "Carol", "email": "carol@example.net" });
|
||||
t.is(info.changes, 1);
|
||||
t.is(info.lastInsertRowid, 3);
|
||||
});
|
||||
|
||||
dualTest.both("Statement.get() returns no rows", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users WHERE id = 0");
|
||||
t.is(stmt.get(), undefined);
|
||||
});
|
||||
|
||||
dualTest.both("Statement.get() [no parameters]", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
var stmt = 0;
|
||||
|
||||
stmt = db.prepare("SELECT * FROM users");
|
||||
t.is(stmt.get().name, "Alice");
|
||||
t.deepEqual(stmt.raw().get(), [1, 'Alice', 'alice@example.org']);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.get() [positional]", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
var stmt = 0;
|
||||
|
||||
stmt = 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");
|
||||
|
||||
stmt = 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");
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.get() [named]", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
var stmt = undefined;
|
||||
|
||||
stmt = db.prepare("SELECT :b, :a");
|
||||
t.deepEqual(stmt.raw().get({ a: 'a', b: 'b' }), ['b', 'a']);
|
||||
|
||||
stmt = 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");
|
||||
|
||||
stmt = 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");
|
||||
|
||||
stmt = 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");
|
||||
});
|
||||
|
||||
dualTest.both("Statement.get() [raw]", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
|
||||
t.deepEqual(stmt.raw().get(1), [1, "Alice", "alice@example.org"]);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.iterate() [empty]", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users WHERE id = 0");
|
||||
t.is(stmt.iterate().next().done, true);
|
||||
t.is(stmt.iterate([]).next().done, true);
|
||||
t.is(stmt.iterate({}).next().done, true);
|
||||
});
|
||||
|
||||
dualTest.both("Statement.iterate()", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [1, 2];
|
||||
var idx = 0;
|
||||
for (const row of stmt.iterate()) {
|
||||
t.is(row.id, expected[idx++]);
|
||||
}
|
||||
});
|
||||
|
||||
dualTest.both("Statement.all()", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
{ id: 1, name: "Alice", email: "alice@example.org" },
|
||||
{ id: 2, name: "Bob", email: "bob@example.com" },
|
||||
];
|
||||
t.deepEqual(stmt.all(), expected);
|
||||
});
|
||||
|
||||
dualTest.both("Statement.all() [raw]", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
[1, "Alice", "alice@example.org"],
|
||||
[2, "Bob", "bob@example.com"],
|
||||
];
|
||||
t.deepEqual(stmt.raw().all(), expected);
|
||||
});
|
||||
|
||||
dualTest.both("Statement.all() [pluck]", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
1,
|
||||
2,
|
||||
];
|
||||
t.deepEqual(stmt.pluck().all(), expected);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.all() [default safe integers]", async (t) => {
|
||||
const db = t.context.db;
|
||||
db.defaultSafeIntegers();
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
[1n, "Alice", "alice@example.org"],
|
||||
[2n, "Bob", "bob@example.com"],
|
||||
];
|
||||
t.deepEqual(stmt.raw().all(), expected);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.all() [statement safe integers]", async (t) => {
|
||||
const db = t.context.db;
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
stmt.safeIntegers();
|
||||
const expected = [
|
||||
[1n, "Alice", "alice@example.org"],
|
||||
[2n, "Bob", "bob@example.com"],
|
||||
];
|
||||
t.deepEqual(stmt.raw().all(), expected);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.raw() [failure]", async (t) => {
|
||||
const db = t.context.db;
|
||||
const stmt = db.prepare("INSERT INTO users (id, name, email) VALUES (?, ?, ?)");
|
||||
await t.throws(() => {
|
||||
stmt.raw()
|
||||
}, {
|
||||
message: 'The raw() method is only for statements that return data'
|
||||
});
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.run() with array bind parameter", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
db.exec(`
|
||||
DROP TABLE IF EXISTS t;
|
||||
CREATE TABLE t (value BLOB);
|
||||
`);
|
||||
|
||||
const array = [1, 2, 3];
|
||||
|
||||
const insertStmt = db.prepare("INSERT INTO t (value) VALUES (?)");
|
||||
await t.throws(() => {
|
||||
insertStmt.run([array]);
|
||||
}, {
|
||||
message: 'SQLite3 can only bind numbers, strings, bigints, buffers, and null'
|
||||
});
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.run() with Float32Array bind parameter", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
db.exec(`
|
||||
DROP TABLE IF EXISTS t;
|
||||
CREATE TABLE t (value BLOB);
|
||||
`);
|
||||
|
||||
const array = new Float32Array([1, 2, 3]);
|
||||
|
||||
const insertStmt = db.prepare("INSERT INTO t (value) VALUES (?)");
|
||||
insertStmt.run([array]);
|
||||
|
||||
const selectStmt = db.prepare("SELECT value FROM t");
|
||||
t.deepEqual(selectStmt.raw().get()[0], Buffer.from(array.buffer));
|
||||
});
|
||||
|
||||
/// This test is not supported by better-sqlite3, but is supported by libsql.
|
||||
/// Therefore, when implementing it in Turso, only enable the test for Turso.
|
||||
dualTest.skip("Statement.run() for vector feature with Float32Array bind parameter", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
db.exec(`
|
||||
DROP TABLE IF EXISTS t;
|
||||
CREATE TABLE t (embedding FLOAT32(8));
|
||||
CREATE INDEX t_idx ON t ( libsql_vector_idx(embedding) );
|
||||
`);
|
||||
|
||||
const insertStmt = db.prepare("INSERT INTO t VALUES (?)");
|
||||
insertStmt.run([new Float32Array([1, 1, 1, 1, 1, 1, 1, 1])]);
|
||||
insertStmt.run([new Float32Array([-1, -1, -1, -1, -1, -1, -1, -1])]);
|
||||
|
||||
const selectStmt = db.prepare("SELECT embedding FROM vector_top_k('t_idx', vector('[2,2,2,2,2,2,2,2]'), 1) n JOIN t ON n.rowid = t.rowid");
|
||||
t.deepEqual(selectStmt.raw().get()[0], Buffer.from(new Float32Array([1, 1, 1, 1, 1, 1, 1, 1]).buffer));
|
||||
|
||||
// we need to explicitly delete this table because later when sqlite-based (not LibSQL) tests will delete table 't' they will leave 't_idx_shadow' table untouched
|
||||
db.exec(`DROP TABLE t`);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Statement.columns()", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
var stmt = undefined;
|
||||
|
||||
stmt = db.prepare("SELECT 1");
|
||||
t.deepEqual(stmt.columns(), [
|
||||
{
|
||||
column: null,
|
||||
database: null,
|
||||
name: '1',
|
||||
table: null,
|
||||
type: null,
|
||||
},
|
||||
]);
|
||||
|
||||
stmt = db.prepare("SELECT * FROM users WHERE id = ?");
|
||||
t.deepEqual(stmt.columns(), [
|
||||
{
|
||||
column: "id",
|
||||
database: "main",
|
||||
name: "id",
|
||||
table: "users",
|
||||
type: "INTEGER",
|
||||
},
|
||||
{
|
||||
column: "name",
|
||||
database: "main",
|
||||
name: "name",
|
||||
table: "users",
|
||||
type: "TEXT",
|
||||
},
|
||||
{
|
||||
column: "email",
|
||||
database: "main",
|
||||
name: "email",
|
||||
table: "users",
|
||||
type: "TEXT",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Database.transaction()", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const insert = db.prepare(
|
||||
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
||||
);
|
||||
|
||||
const insertMany = db.transaction((users) => {
|
||||
t.is(db.inTransaction, true);
|
||||
for (const user of users) insert.run(user);
|
||||
});
|
||||
|
||||
t.is(db.inTransaction, false);
|
||||
insertMany([
|
||||
{ name: "Joey", email: "joey@example.org" },
|
||||
{ name: "Sally", email: "sally@example.org" },
|
||||
{ name: "Junior", email: "junior@example.org" },
|
||||
]);
|
||||
t.is(db.inTransaction, false);
|
||||
|
||||
const stmt = 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");
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Database.transaction().immediate()", async (t) => {
|
||||
const db = t.context.db;
|
||||
const insert = db.prepare(
|
||||
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
||||
);
|
||||
const insertMany = db.transaction((users) => {
|
||||
t.is(db.inTransaction, true);
|
||||
for (const user of users) insert.run(user);
|
||||
});
|
||||
t.is(db.inTransaction, false);
|
||||
insertMany.immediate([
|
||||
{ name: "Joey", email: "joey@example.org" },
|
||||
{ name: "Sally", email: "sally@example.org" },
|
||||
{ name: "Junior", email: "junior@example.org" },
|
||||
]);
|
||||
t.is(db.inTransaction, false);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("values", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT ?").raw();
|
||||
t.deepEqual(stmt.get(1), [1]);
|
||||
t.deepEqual(stmt.get(Number.MIN_VALUE), [Number.MIN_VALUE]);
|
||||
t.deepEqual(stmt.get(Number.MAX_VALUE), [Number.MAX_VALUE]);
|
||||
t.deepEqual(stmt.get(Number.MAX_SAFE_INTEGER), [Number.MAX_SAFE_INTEGER]);
|
||||
t.deepEqual(stmt.get(9007199254740991n), [9007199254740991]);
|
||||
});
|
||||
|
||||
dualTest.both("Database.pragma()", async (t) => {
|
||||
const db = t.context.db;
|
||||
db.pragma("cache_size = 2000");
|
||||
t.deepEqual(db.pragma("cache_size"), [{ "cache_size": 2000 }]);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("errors", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const syntaxError = await t.throws(() => {
|
||||
db.exec("SYNTAX ERROR");
|
||||
}, {
|
||||
any: true,
|
||||
instanceOf: t.context.errorType,
|
||||
message: 'near "SYNTAX": syntax error',
|
||||
code: 'SQLITE_ERROR'
|
||||
});
|
||||
const noTableError = await t.throws(() => {
|
||||
db.exec("SELECT * FROM missing_table");
|
||||
}, {
|
||||
any: true,
|
||||
instanceOf: t.context.errorType,
|
||||
message: "no such table: missing_table",
|
||||
code: 'SQLITE_ERROR'
|
||||
});
|
||||
|
||||
if (t.context.provider === 'libsql') {
|
||||
t.is(noTableError.rawCode, 1)
|
||||
t.is(syntaxError.rawCode, 1)
|
||||
}
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Database.prepare() after close()", async (t) => {
|
||||
const db = t.context.db;
|
||||
db.close();
|
||||
t.throws(() => {
|
||||
db.prepare("SELECT 1");
|
||||
}, {
|
||||
instanceOf: TypeError,
|
||||
message: "The database connection is not open"
|
||||
});
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("Database.exec() after close()", async (t) => {
|
||||
const db = t.context.db;
|
||||
db.close();
|
||||
t.throws(() => {
|
||||
db.exec("SELECT 1");
|
||||
}, {
|
||||
instanceOf: TypeError,
|
||||
message: "The database connection is not open"
|
||||
});
|
||||
});
|
||||
|
||||
/// Generate a unique database filename
|
||||
const genDatabaseFilename = () => {
|
||||
return `test-${crypto.randomBytes(8).toString('hex')}.db`;
|
||||
};
|
||||
|
||||
new DualTest(genDatabaseFilename).onlySqlitePasses("Timeout option", async (t) => {
|
||||
t.teardown(() => fs.unlinkSync(t.context.path));
|
||||
|
||||
const timeout = 1000;
|
||||
const { db: conn1 } = t.context;
|
||||
conn1.exec("CREATE TABLE t(x)");
|
||||
conn1.exec("BEGIN IMMEDIATE");
|
||||
conn1.exec("INSERT INTO t VALUES (1)")
|
||||
const options = { timeout };
|
||||
const conn2 = t.context.connect(t.context.path, options);
|
||||
const start = Date.now();
|
||||
try {
|
||||
conn2.exec("INSERT INTO t VALUES (1)")
|
||||
} catch (e) {
|
||||
t.is(e.code, "SQLITE_BUSY");
|
||||
const end = Date.now();
|
||||
const elapsed = end - start;
|
||||
// Allow some tolerance for the timeout.
|
||||
t.is(elapsed > timeout / 2, true);
|
||||
}
|
||||
conn1.close();
|
||||
conn2.close();
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"url": "https://github.com/tursodatabase/turso"
|
||||
},
|
||||
"description": "The Turso database library",
|
||||
"main": "index.js",
|
||||
"main": "wrapper.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
"name": "turso",
|
||||
@@ -42,4 +42,4 @@
|
||||
"version": "napi version"
|
||||
},
|
||||
"packageManager": "yarn@4.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,4 +268,4 @@ class Statement {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Database = Database;
|
||||
module.exports = Database;
|
||||
|
||||
Reference in New Issue
Block a user