mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 17:05:36 +01:00
Merge 'Implement JavaScript bindings with minimal Rust core' from Pekka Enberg
This rewrites the JavaScript bindings completely by exposing only primitive operations from Rust NAPI-RS code. For example, there is prepare(), bind(), and step(), but high level interfaces like all() and get() are implemented in JavaScript. We're doing this so that we can implement async interfaces in the JavaScript layer instead of having to bring in Tokio. Closes #2372
This commit is contained in:
@@ -12,7 +12,7 @@ crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
turso_core = { workspace = true }
|
||||
napi = { version = "3.1.3", default-features = false }
|
||||
napi = { version = "3.1.3", default-features = false, features = ["napi6"] }
|
||||
napi-derive = { version = "3.1.1", default-features = true }
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
);
|
||||
70
bindings/javascript/bind.js
Normal file
70
bindings/javascript/bind.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// Bind parameters to a statement.
|
||||
//
|
||||
// This function is used to bind parameters to a statement. It supports both
|
||||
// named and positional parameters, and nested arrays.
|
||||
//
|
||||
// The `stmt` parameter is a statement object.
|
||||
// The `params` parameter is an array of parameters.
|
||||
//
|
||||
// The function returns void.
|
||||
function bindParams(stmt, params) {
|
||||
const len = params?.length;
|
||||
if (len === 0) {
|
||||
return;
|
||||
}
|
||||
if (len === 1) {
|
||||
const param = params[0];
|
||||
if (isPlainObject(param)) {
|
||||
bindNamedParams(stmt, param);
|
||||
return;
|
||||
}
|
||||
bindValue(stmt, 1, param);
|
||||
return;
|
||||
}
|
||||
bindPositionalParams(stmt, params);
|
||||
}
|
||||
|
||||
// Check if object is plain (no prototype chain)
|
||||
function isPlainObject(obj) {
|
||||
if (!obj || typeof obj !== 'object') return false;
|
||||
const proto = Object.getPrototypeOf(obj);
|
||||
return proto === Object.prototype || proto === null;
|
||||
}
|
||||
|
||||
// Handle named parameters
|
||||
function bindNamedParams(stmt, paramObj) {
|
||||
const paramCount = stmt.parameterCount();
|
||||
|
||||
for (let i = 1; i <= paramCount; i++) {
|
||||
const paramName = stmt.parameterName(i);
|
||||
if (paramName) {
|
||||
const key = paramName.substring(1); // Remove ':' or '$' prefix
|
||||
const value = paramObj[key];
|
||||
|
||||
if (value !== undefined) {
|
||||
bindValue(stmt, i, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle positional parameters (including nested arrays)
|
||||
function bindPositionalParams(stmt, params) {
|
||||
let bindIndex = 1;
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
const param = params[i];
|
||||
if (Array.isArray(param)) {
|
||||
for (let j = 0; j < param.length; j++) {
|
||||
bindValue(stmt, bindIndex++, param[j]);
|
||||
}
|
||||
} else {
|
||||
bindValue(stmt, bindIndex++, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bindValue(stmt, index, value) {
|
||||
stmt.bindAt(index, value);
|
||||
}
|
||||
|
||||
module.exports = { bindParams };
|
||||
127
bindings/javascript/index.d.ts
vendored
127
bindings/javascript/index.d.ts
vendored
@@ -1,46 +1,101 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
/** A database connection. */
|
||||
export declare class Database {
|
||||
memory: boolean
|
||||
readonly: boolean
|
||||
open: boolean
|
||||
name: string
|
||||
constructor(path: string, options?: OpenDatabaseOptions | undefined | null)
|
||||
/**
|
||||
* Creates a new database instance.
|
||||
*
|
||||
* # Arguments
|
||||
* * `path` - The path to the database file.
|
||||
*/
|
||||
constructor(path: string)
|
||||
/** Returns whether the database is in memory-only mode. */
|
||||
get memory(): boolean
|
||||
/**
|
||||
* Executes a batch of SQL statements.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `sql` - The SQL statements to execute.
|
||||
*
|
||||
* # Returns
|
||||
*/
|
||||
batch(sql: string): void
|
||||
/**
|
||||
* Prepares a statement for execution.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `sql` - The SQL statement to prepare.
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* A `Statement` instance.
|
||||
*/
|
||||
prepare(sql: string): Statement
|
||||
pragma(pragmaName: string, options?: PragmaOptions | undefined | null): unknown
|
||||
backup(): void
|
||||
serialize(): void
|
||||
function(): void
|
||||
aggregate(): void
|
||||
table(): void
|
||||
loadExtension(path: string): void
|
||||
exec(sql: string): void
|
||||
/**
|
||||
* Returns the rowid of the last row inserted.
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* The rowid of the last row inserted.
|
||||
*/
|
||||
lastInsertRowid(): number
|
||||
/**
|
||||
* Returns the number of changes made by the last statement.
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* The number of changes made by the last statement.
|
||||
*/
|
||||
changes(): number
|
||||
/**
|
||||
* Returns the total number of changes made by all statements.
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* The total number of changes made by all statements.
|
||||
*/
|
||||
totalChanges(): number
|
||||
/**
|
||||
* Closes the database connection.
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* `Ok(())` if the database is closed successfully.
|
||||
*/
|
||||
close(): void
|
||||
/** Runs the I/O loop synchronously. */
|
||||
ioLoopSync(): void
|
||||
/** Runs the I/O loop asynchronously, returning a Promise. */
|
||||
ioLoopAsync(): Promise<void>
|
||||
}
|
||||
|
||||
/** A prepared statement. */
|
||||
export declare class Statement {
|
||||
source: string
|
||||
get(args?: Array<unknown> | undefined | null): unknown
|
||||
run(args?: Array<unknown> | undefined | null): RunResult
|
||||
all(args?: Array<unknown> | undefined | null): unknown
|
||||
pluck(pluck?: boolean | undefined | null): void
|
||||
static expand(): void
|
||||
reset(): void
|
||||
/** Returns the number of parameters in the statement. */
|
||||
parameterCount(): number
|
||||
/**
|
||||
* Returns the name of a parameter at a specific 1-based index.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `index` - The 1-based parameter index.
|
||||
*/
|
||||
parameterName(index: number): string | null
|
||||
/**
|
||||
* Binds a parameter at a specific 1-based index with explicit type.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `index` - The 1-based parameter index.
|
||||
* * `value_type` - The type constant (0=null, 1=int, 2=float, 3=text, 4=blob).
|
||||
* * `value` - The value to bind.
|
||||
*/
|
||||
bindAt(index: number, value: unknown): void
|
||||
step(): unknown
|
||||
raw(raw?: boolean | undefined | null): void
|
||||
static columns(): void
|
||||
bind(args?: Array<unknown> | undefined | null): Statement
|
||||
}
|
||||
|
||||
export interface OpenDatabaseOptions {
|
||||
readonly?: boolean
|
||||
fileMustExist?: boolean
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
export interface PragmaOptions {
|
||||
simple: boolean
|
||||
}
|
||||
|
||||
export interface RunResult {
|
||||
changes: number
|
||||
lastInsertRowid: number
|
||||
pluck(pluck?: boolean | undefined | null): void
|
||||
finalize(): void
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"./sync": "./sync.js"
|
||||
},
|
||||
"files": [
|
||||
"bindjs",
|
||||
"browser.js",
|
||||
"index.js",
|
||||
"promise.js",
|
||||
@@ -46,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"
|
||||
},
|
||||
|
||||
487
bindings/javascript/perf/package-lock.json
generated
Normal file
487
bindings/javascript/perf/package-lock.json
generated
Normal file
@@ -0,0 +1,487 @@
|
||||
{
|
||||
"name": "turso-perf",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "turso-perf",
|
||||
"dependencies": {
|
||||
"@tursodatabase/turso": "..",
|
||||
"better-sqlite3": "^9.5.0",
|
||||
"mitata": "^0.1.11"
|
||||
}
|
||||
},
|
||||
"..": {
|
||||
"name": "@tursodatabase/turso",
|
||||
"version": "0.1.3",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^3.0.4",
|
||||
"@napi-rs/wasm-runtime": "^1.0.1",
|
||||
"ava": "^6.0.1",
|
||||
"better-sqlite3": "^11.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tursodatabase/turso": {
|
||||
"resolved": "..",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/better-sqlite3": {
|
||||
"version": "9.6.0",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz",
|
||||
"integrity": "sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"prebuild-install": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"license": "(MIT OR WTFPL)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mitata": {
|
||||
"version": "0.1.14",
|
||||
"resolved": "https://registry.npmjs.org/mitata/-/mitata-0.1.14.tgz",
|
||||
"integrity": "sha512-8kRs0l636eT4jj68PFXOR2D5xl4m56T478g16SzUPOYgkzQU+xaw62guAQxzBPm+SXb15GQi1cCpDxJfkr4CSA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.75.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz",
|
||||
"integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^2.0.0",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuild-install": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
|
||||
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||
"dependencies": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"rc": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
|
||||
"integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
bindings/javascript/perf/package.json
Normal file
10
bindings/javascript/perf/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "turso-perf",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^9.5.0",
|
||||
"@tursodatabase/turso": "..",
|
||||
"mitata": "^0.1.11"
|
||||
}
|
||||
}
|
||||
26
bindings/javascript/perf/perf-better-sqlite3.js
Normal file
26
bindings/javascript/perf/perf-better-sqlite3.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { run, bench, group, baseline } from 'mitata';
|
||||
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
const db = new Database(':memory:');
|
||||
|
||||
db.exec("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')");
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
|
||||
|
||||
group('Statement', () => {
|
||||
bench('Statement.get() bind parameters', () => {
|
||||
stmt.get(1);
|
||||
});
|
||||
});
|
||||
|
||||
await run({
|
||||
units: false,
|
||||
silent: false,
|
||||
avg: true,
|
||||
json: false,
|
||||
colors: true,
|
||||
min_max: true,
|
||||
percentiles: true,
|
||||
});
|
||||
26
bindings/javascript/perf/perf-turso.js
Normal file
26
bindings/javascript/perf/perf-turso.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { run, bench, group, baseline } from 'mitata';
|
||||
|
||||
import Database from '@tursodatabase/turso';
|
||||
|
||||
const db = new Database(':memory:');
|
||||
|
||||
db.exec("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')");
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
|
||||
|
||||
group('Statement', () => {
|
||||
bench('Statement.get() bind parameters', () => {
|
||||
stmt.get(1);
|
||||
});
|
||||
});
|
||||
|
||||
await run({
|
||||
units: false,
|
||||
silent: false,
|
||||
avg: true,
|
||||
json: false,
|
||||
colors: true,
|
||||
min_max: true,
|
||||
percentiles: true,
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const { Database: NativeDB } = require("./index.js");
|
||||
const { bindParams } = require("./bind.js");
|
||||
|
||||
const SqliteError = require("./sqlite-error.js");
|
||||
|
||||
@@ -138,12 +139,12 @@ class Database {
|
||||
if (typeof options !== "object")
|
||||
throw new TypeError("Expected second argument to be an options object");
|
||||
|
||||
const simple = options["simple"];
|
||||
const pragma = `PRAGMA ${source}`;
|
||||
|
||||
return simple
|
||||
? this.db.pragma(source, { simple: true })
|
||||
: this.db.pragma(source);
|
||||
|
||||
const stmt = this.prepare(pragma);
|
||||
const results = stmt.all();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
backup(filename, options) {
|
||||
@@ -181,7 +182,7 @@ class Database {
|
||||
*/
|
||||
exec(sql) {
|
||||
try {
|
||||
this.db.exec(sql);
|
||||
this.db.batch(sql);
|
||||
} catch (err) {
|
||||
throw convertError(err);
|
||||
}
|
||||
@@ -250,8 +251,27 @@ class Statement {
|
||||
/**
|
||||
* Executes the SQL statement and returns an info object.
|
||||
*/
|
||||
run(...bindParameters) {
|
||||
return this.stmt.run(bindParameters.flat());
|
||||
async run(...bindParameters) {
|
||||
const totalChangesBefore = this.db.db.totalChanges();
|
||||
|
||||
this.stmt.reset();
|
||||
bindParams(this.stmt, bindParameters);
|
||||
|
||||
while (true) {
|
||||
const result = this.stmt.step();
|
||||
if (result.io) {
|
||||
await this.db.db.ioLoopAsync();
|
||||
continue;
|
||||
}
|
||||
if (result.done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const lastInsertRowid = this.db.db.lastInsertRowid();
|
||||
const changes = this.db.db.totalChanges() === totalChangesBefore ? 0 : this.db.db.changes();
|
||||
|
||||
return { changes, lastInsertRowid };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,8 +279,21 @@ class Statement {
|
||||
*
|
||||
* @param bindParameters - The bind parameters for executing the statement.
|
||||
*/
|
||||
get(...bindParameters) {
|
||||
return this.stmt.get(bindParameters.flat());
|
||||
async get(...bindParameters) {
|
||||
this.stmt.reset();
|
||||
bindParams(this.stmt, bindParameters);
|
||||
|
||||
while (true) {
|
||||
const result = this.stmt.step();
|
||||
if (result.io) {
|
||||
await this.db.db.ioLoopAsync();
|
||||
continue;
|
||||
}
|
||||
if (result.done) {
|
||||
return undefined;
|
||||
}
|
||||
return result.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,8 +310,23 @@ class Statement {
|
||||
*
|
||||
* @param bindParameters - The bind parameters for executing the statement.
|
||||
*/
|
||||
all(...bindParameters) {
|
||||
return this.stmt.all(bindParameters.flat());
|
||||
async all(...bindParameters) {
|
||||
this.stmt.reset();
|
||||
bindParams(this.stmt, bindParameters);
|
||||
const rows = [];
|
||||
|
||||
while (true) {
|
||||
const result = this.stmt.step();
|
||||
if (result.io) {
|
||||
await this.db.db.ioLoopAsync();
|
||||
continue;
|
||||
}
|
||||
if (result.done) {
|
||||
break;
|
||||
}
|
||||
rows.push(result.value);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,7 +352,8 @@ class Statement {
|
||||
*/
|
||||
bind(...bindParameters) {
|
||||
try {
|
||||
return new Statement(this.stmt.bind(bindParameters.flat()), this.db);
|
||||
bindParams(this.stmt, bindParameters);
|
||||
return this;
|
||||
} catch (err) {
|
||||
throw convertError(err);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const { Database: NativeDB } = require("./index.js");
|
||||
const { bindParams } = require("./bind.js");
|
||||
|
||||
const SqliteError = require("./sqlite-error.js");
|
||||
|
||||
@@ -138,12 +139,12 @@ class Database {
|
||||
if (typeof options !== "object")
|
||||
throw new TypeError("Expected second argument to be an options object");
|
||||
|
||||
const simple = options["simple"];
|
||||
const pragma = `PRAGMA ${source}`;
|
||||
|
||||
return simple
|
||||
? this.db.pragma(source, { simple: true })
|
||||
: this.db.pragma(source);
|
||||
|
||||
const stmt = this.prepare(pragma);
|
||||
const results = stmt.all();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
backup(filename, options) {
|
||||
@@ -181,7 +182,7 @@ class Database {
|
||||
*/
|
||||
exec(sql) {
|
||||
try {
|
||||
this.db.exec(sql);
|
||||
this.db.batch(sql);
|
||||
} catch (err) {
|
||||
throw convertError(err);
|
||||
}
|
||||
@@ -251,7 +252,25 @@ class Statement {
|
||||
* Executes the SQL statement and returns an info object.
|
||||
*/
|
||||
run(...bindParameters) {
|
||||
return this.stmt.run(bindParameters.flat());
|
||||
const totalChangesBefore = this.db.db.totalChanges();
|
||||
|
||||
this.stmt.reset();
|
||||
bindParams(this.stmt, bindParameters);
|
||||
for (;;) {
|
||||
const result = this.stmt.step();
|
||||
if (result.io) {
|
||||
this.db.db.ioLoopSync();
|
||||
continue;
|
||||
}
|
||||
if (result.done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const lastInsertRowid = this.db.db.lastInsertRowid();
|
||||
const changes = this.db.db.totalChanges() === totalChangesBefore ? 0 : this.db.db.changes();
|
||||
|
||||
return { changes, lastInsertRowid };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,7 +279,19 @@ class Statement {
|
||||
* @param bindParameters - The bind parameters for executing the statement.
|
||||
*/
|
||||
get(...bindParameters) {
|
||||
return this.stmt.get(bindParameters.flat());
|
||||
this.stmt.reset();
|
||||
bindParams(this.stmt, bindParameters);
|
||||
for (;;) {
|
||||
const result = this.stmt.step();
|
||||
if (result.io) {
|
||||
this.db.db.ioLoopSync();
|
||||
continue;
|
||||
}
|
||||
if (result.done) {
|
||||
return undefined;
|
||||
}
|
||||
return result.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,7 +309,21 @@ class Statement {
|
||||
* @param bindParameters - The bind parameters for executing the statement.
|
||||
*/
|
||||
all(...bindParameters) {
|
||||
return this.stmt.all(bindParameters.flat());
|
||||
this.stmt.reset();
|
||||
bindParams(this.stmt, bindParameters);
|
||||
const rows = [];
|
||||
for (;;) {
|
||||
const result = this.stmt.step();
|
||||
if (result.io) {
|
||||
this.db.db.ioLoopSync();
|
||||
continue;
|
||||
}
|
||||
if (result.done) {
|
||||
break;
|
||||
}
|
||||
rows.push(result.value);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,7 +349,8 @@ class Statement {
|
||||
*/
|
||||
bind(...bindParameters) {
|
||||
try {
|
||||
return new Statement(this.stmt.bind(bindParameters.flat()), this.db);
|
||||
bindParams(this.stmt, bindParameters);
|
||||
return this;
|
||||
} catch (err) {
|
||||
throw convertError(err);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ 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);
|
||||
});
|
||||
@@ -78,7 +78,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']);
|
||||
});
|
||||
|
||||
@@ -88,15 +88,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) => {
|
||||
@@ -105,19 +105,19 @@ 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");
|
||||
});
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ 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.skip("Statement.iterate() [empty]", async (t) => {
|
||||
@@ -403,7 +403,7 @@ test.skip("Timeout option", async (t) => {
|
||||
fs.unlinkSync(path);
|
||||
});
|
||||
|
||||
test.serial("Concurrent writes over same connection", async (t) => {
|
||||
test.skip("Concurrent writes over same connection", async (t) => {
|
||||
const db = t.context.db;
|
||||
await db.exec(`
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
||||
Reference in New Issue
Block a user