mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-05 00:04:23 +01:00
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:
@@ -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);
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
);
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user