bindings/javascript: Remove test suite

We have `testing/javascript` to test both the native bindings and
serverless driver, so let's use that instead.
This commit is contained in:
Pekka Enberg
2025-08-01 12:08:25 +03:00
parent 02db72cc2c
commit 845fc13d6e
5 changed files with 1 additions and 1061 deletions

View File

@@ -1,3 +0,0 @@
CREATE TABLE users (name TEXT, age INTEGER);
INSERT INTO users (name, age) VALUES ('Bob', 24);
INSERT INTO users (name, age) VALUES ('Alice', 42);

View File

@@ -1,445 +0,0 @@
import crypto from "crypto";
import fs from "node:fs";
import { fileURLToPath } from "url";
import path from "node:path";
import DualTest from "./dual-test.mjs";
const inMemoryTest = new DualTest(":memory:");
const foobarTest = new DualTest("foobar.db");
inMemoryTest.both("Open in-memory database", async (t) => {
const db = t.context.db;
t.is(db.memory, true);
});
inMemoryTest.both("Property .name of in-memory database", async (t) => {
const db = t.context.db;
t.is(db.name, t.context.path);
});
foobarTest.both("Property .name of database", async (t) => {
const db = t.context.db;
t.is(db.name, t.context.path);
});
new DualTest("foobar.db", { readonly: true }).both(
"Property .readonly of database if set",
async (t) => {
const db = t.context.db;
t.is(db.readonly, true);
},
);
const genDatabaseFilename = () => {
return `test-${crypto.randomBytes(8).toString("hex")}.db`;
};
new DualTest().both(
"opening a read-only database fails if the file doesn't exist",
async (t) => {
t.throws(
() => t.context.connect(genDatabaseFilename(), { readonly: true }),
{
any: true,
code: "SQLITE_CANTOPEN",
},
);
},
);
foobarTest.both("Property .readonly of database if not set", async (t) => {
const db = t.context.db;
t.is(db.readonly, false);
});
foobarTest.both("Property .open of database", async (t) => {
const db = t.context.db;
t.is(db.open, true);
});
inMemoryTest.both("Statement.get() returns data", async (t) => {
const db = t.context.db;
const stmt = db.prepare("SELECT 1");
const result = stmt.get();
t.is(result["1"], 1);
const result2 = stmt.get();
t.is(result2["1"], 1);
});
inMemoryTest.both(
"Statement.get() returns undefined when no data",
async (t) => {
const db = t.context.db;
const stmt = db.prepare("SELECT 1 WHERE 1 = 2");
const result = stmt.get();
t.is(result, undefined);
},
);
inMemoryTest.both(
"Statement.run() returns correct result object",
async (t) => {
const db = t.context.db;
db.prepare("CREATE TABLE users (name TEXT)").run();
const rows = db.prepare("INSERT INTO users (name) VALUES (?)").run("Alice");
t.deepEqual(rows, { changes: 1, lastInsertRowid: 1 });
},
);
inMemoryTest.onlySqlitePasses(
"Statment.iterate() should correctly return an iterable object",
async (t) => {
const db = t.context.db;
db.prepare(
"CREATE TABLE users (name TEXT, age INTEGER, nationality TEXT)",
).run();
db.prepare(
"INSERT INTO users (name, age, nationality) VALUES (?, ?, ?)",
).run(["Alice", 42], "UK");
db.prepare(
"INSERT INTO users (name, age, nationality) VALUES (?, ?, ?)",
).run("Bob", 24, "USA");
let rows = db.prepare("SELECT * FROM users").iterate();
for (const row of rows) {
t.truthy(row.name);
t.truthy(row.nationality);
t.true(typeof row.age === "number");
}
},
);
inMemoryTest.both(
"Empty prepared statement should throw the correct error",
async (t) => {
const db = t.context.db;
t.throws(
() => {
db.prepare("");
},
{
instanceOf: RangeError,
message: "The supplied SQL string contains no statements",
},
);
},
);
inMemoryTest.both("Test pragma()", async (t) => {
const db = t.context.db;
t.deepEqual(typeof db.pragma("cache_size")[0].cache_size, "number");
t.deepEqual(typeof db.pragma("cache_size", { simple: true }), "number");
});
inMemoryTest.both("pragma query", async (t) => {
const db = t.context.db;
let page_size = db.pragma("page_size");
let expectedValue = [{ page_size: 4096 }];
t.deepEqual(page_size, expectedValue);
});
inMemoryTest.both("pragma table_list", async (t) => {
const db = t.context.db;
let param = "sqlite_schema";
let actual = db.pragma(`table_info(${param})`);
let expectedValue = [
{ cid: 0, name: "type", type: "TEXT", notnull: 0, dflt_value: null, pk: 0 },
{ cid: 1, name: "name", type: "TEXT", notnull: 0, dflt_value: null, pk: 0 },
{
cid: 2,
name: "tbl_name",
type: "TEXT",
notnull: 0,
dflt_value: null,
pk: 0,
},
{
cid: 3,
name: "rootpage",
type: "INT",
notnull: 0,
dflt_value: null,
pk: 0,
},
{ cid: 4, name: "sql", type: "TEXT", notnull: 0, dflt_value: null, pk: 0 },
];
t.deepEqual(actual, expectedValue);
});
inMemoryTest.both("simple pragma table_list", async (t) => {
const db = t.context.db;
let param = "sqlite_schema";
let actual = db.pragma(`table_info(${param})`, { simple: true });
let expectedValue = 0;
t.deepEqual(actual, expectedValue);
});
inMemoryTest.onlySqlitePasses(
"Statement shouldn't bind twice with bind()",
async (t) => {
const db = t.context.db;
db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
let stmt = db.prepare("SELECT * FROM users WHERE name = ?").bind("Alice");
let row = stmt.get();
t.truthy(row.name);
t.true(typeof row.age === "number");
t.throws(
() => {
stmt.bind("Bob");
},
{
instanceOf: TypeError,
message:
"The bind() method can only be invoked once per statement object",
},
);
},
);
inMemoryTest.both(
"Test pluck(): Rows should only have the values of the first column",
async (t) => {
const db = t.context.db;
db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24);
let stmt = db.prepare("SELECT * FROM users").pluck();
for (const row of stmt.all()) {
t.truthy(row);
t.assert(typeof row === "string");
}
},
);
inMemoryTest.both(
"Test raw(): Rows should be returned as arrays",
async (t) => {
const db = t.context.db;
db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24);
let stmt = db.prepare("SELECT * FROM users").raw();
for (const row of stmt.all()) {
t.true(Array.isArray(row));
t.true(typeof row[0] === "string");
t.true(typeof row[1] === "number");
}
stmt = db.prepare("SELECT * FROM users WHERE name = ?").raw();
const row = stmt.get("Alice");
t.true(Array.isArray(row));
t.is(row.length, 2);
t.is(row[0], "Alice");
t.is(row[1], 42);
const noRow = stmt.get("Charlie");
t.is(noRow, undefined);
stmt = db.prepare("SELECT * FROM users").raw();
const rows = stmt.all();
t.true(Array.isArray(rows));
t.is(rows.length, 2);
t.deepEqual(rows[0], ["Alice", 42]);
t.deepEqual(rows[1], ["Bob", 24]);
},
);
inMemoryTest.onlySqlitePasses(
"Test expand(): Columns should be namespaced",
async (t) => {
const expandedResults = [
{
users: {
name: "Alice",
type: "premium",
},
addresses: {
userName: "Alice",
type: "home",
street: "Alice's street",
},
},
{
users: {
name: "Bob",
type: "basic",
},
addresses: {
userName: "Bob",
type: "work",
street: "Bob's street",
},
},
];
let regularResults = [
{
name: "Alice",
street: "Alice's street",
type: "home",
userName: "Alice",
},
{
name: "Bob",
street: "Bob's street",
type: "work",
userName: "Bob",
},
];
const db = t.context.db;
db.prepare("CREATE TABLE users (name TEXT, type TEXT)").run();
db.prepare(
"CREATE TABLE addresses (userName TEXT, street TEXT, type TEXT)",
).run();
db.prepare("INSERT INTO users (name, type) VALUES (?, ?)").run(
"Alice",
"premium",
);
db.prepare("INSERT INTO users (name, type) VALUES (?, ?)").run(
"Bob",
"basic",
);
db.prepare(
"INSERT INTO addresses (userName, street, type) VALUES (?, ?, ?)",
).run("Alice", "Alice's street", "home");
db.prepare(
"INSERT INTO addresses (userName, street, type) VALUES (?, ?, ?)",
).run("Bob", "Bob's street", "work");
let allRows = db
.prepare(
"SELECT * FROM users u JOIN addresses a ON (u.name = a.userName)",
)
.expand(true)
.all();
t.deepEqual(allRows, expandedResults);
allRows = db
.prepare(
"SELECT * FROM users u JOIN addresses a ON (u.name = a.userName)",
)
.expand()
.all();
t.deepEqual(allRows, expandedResults);
allRows = db
.prepare(
"SELECT * FROM users u JOIN addresses a ON (u.name = a.userName)",
)
.expand(false)
.all();
t.deepEqual(allRows, regularResults);
},
);
inMemoryTest.both(
"Presentation modes should be mutually exclusive",
async (t) => {
const db = t.context.db;
db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24);
// test raw()
let stmt = db.prepare("SELECT * FROM users").pluck().raw();
for (const row of stmt.all()) {
t.true(Array.isArray(row));
t.true(typeof row[0] === "string");
t.true(typeof row[1] === "number");
}
stmt = db.prepare("SELECT * FROM users WHERE name = ?").raw();
const row = stmt.get("Alice");
t.true(Array.isArray(row));
t.is(row.length, 2);
t.is(row[0], "Alice");
t.is(row[1], 42);
const noRow = stmt.get("Charlie");
t.is(noRow, undefined);
stmt = db.prepare("SELECT * FROM users").raw();
let rows = stmt.all();
t.true(Array.isArray(rows));
t.is(rows.length, 2);
t.deepEqual(rows[0], ["Alice", 42]);
t.deepEqual(rows[1], ["Bob", 24]);
// test pluck()
stmt = db.prepare("SELECT * FROM users").raw().pluck();
for (const name of stmt.all()) {
t.truthy(name);
t.assert(typeof name === "string");
}
},
);
inMemoryTest.onlySqlitePasses(
"Presentation mode 'expand' should be mutually exclusive",
async (t) => {
// this test can be appended to the previous one when 'expand' is implemented in Turso
const db = t.context.db;
db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run();
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42);
db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24);
let stmt = db.prepare("SELECT * FROM users").pluck().raw();
// test expand()
stmt = db.prepare("SELECT * FROM users").raw().pluck().expand();
const rows = stmt.all();
t.true(Array.isArray(rows));
t.is(rows.length, 2);
t.deepEqual(rows[0], { users: { name: "Alice", age: 42 } });
t.deepEqual(rows[1], { users: { name: "Bob", age: 24 } });
},
);
inMemoryTest.both(
"Test exec(): Should correctly load multiple statements from file",
async (t) => {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const db = t.context.db;
const file = fs.readFileSync(
path.resolve(__dirname, "./artifacts/basic-test.sql"),
"utf8",
);
db.exec(file);
let rows = db.prepare("SELECT * FROM users").all();
for (const row of rows) {
t.truthy(row.name);
t.true(typeof row.age === "number");
}
},
);
inMemoryTest.both(
"Test Statement.database gets the database object",
async (t) => {
const db = t.context.db;
let stmt = db.prepare("SELECT 1");
t.is(stmt.database, db);
},
);
inMemoryTest.both("Test Statement.source", async (t) => {
const db = t.context.db;
let sql = "CREATE TABLE t (id int)";
let stmt = db.prepare(sql);
t.is(stmt.source, sql);
});

View File

@@ -1,82 +0,0 @@
import avaTest from "ava";
import turso from "../sync.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;

View File

@@ -1,530 +0,0 @@
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.both("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.both("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.both("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.both("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.both("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.onlySqlitePasses("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.both(
"Statement.raw() [passing false should disable raw mode]",
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.raw(false).all(), expected);
},
);
dualTest.both(
"Statement.pluck() [passing false should disable pluck mode]",
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.pluck(false).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.both("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:
/(Parse error: Table missing_table not found|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();
},
);

View File

@@ -47,7 +47,7 @@
"build": "napi build --platform --release",
"build:debug": "napi build --platform",
"prepublishOnly": "napi prepublish -t npm",
"test": "ava -s",
"test": "true",
"universal": "napi universalize",
"version": "napi version"
},