mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-06 01:34:21 +01:00
Merge 'bindings/javascript: Switch to napi v3' from Diego Reis
Switch to napi [v3](https://napi.rs/blog/announce-v3). With the exception of `Statement.iterate()`, the behavior is preserved. I had to temporarily remove it because the trait `Generator` doesn't supports the new lifetime scoped values, I already brought this issue in napi's discord server and it should be fixed soon. Closes #2262
This commit is contained in:
15
.github/workflows/napi.yml
vendored
15
.github/workflows/napi.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
env:
|
||||
DEBUG: napi:*
|
||||
APP_NAME: turso
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
MACOSX_DEPLOYMENT_TARGET: "10.13"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
if: ${{ matrix.settings.docker }}
|
||||
with:
|
||||
image: ${{ matrix.settings.docker }}
|
||||
options: '--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build/bindings/javascript'
|
||||
options: "--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build/bindings/javascript"
|
||||
run: ${{ matrix.settings.build }}
|
||||
- name: Build
|
||||
run: ${{ matrix.settings.build }}
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
- host: macos-13
|
||||
target: x86_64-apple-darwin
|
||||
node:
|
||||
- '20'
|
||||
- "20"
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node:
|
||||
- '20'
|
||||
- "20"
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -176,12 +176,15 @@ jobs:
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-x86_64-apple-darwin
|
||||
path: bindings/javascript/artifacts
|
||||
path: bindings/javascript
|
||||
- name: Download macOS arm64 artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-aarch64-apple-darwin
|
||||
path: bindings/javascript/artifacts
|
||||
path: bindings/javascript
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Combine binaries
|
||||
run: yarn universal
|
||||
- name: Upload artifact
|
||||
|
||||
74
Cargo.lock
generated
74
Cargo.lock
generated
@@ -556,9 +556,9 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
@@ -749,14 +749,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.9"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
||||
checksum = "a4735f265ba6a1188052ca32d461028a7d1125868be18e287e756019da7607b5"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"ctor-proc-macro",
|
||||
"dtor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor-proc-macro"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d"
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.4.5"
|
||||
@@ -931,6 +937,21 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "dtor"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8"
|
||||
dependencies = [
|
||||
"dtor-proc-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtor-proc-macro"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.19"
|
||||
@@ -2192,31 +2213,32 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "2.16.17"
|
||||
version = "3.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3"
|
||||
checksum = "afaf586c21f260e9dc327ae3585fc6efcbb24a416d5151da38bbd35a1f2663c8"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"ctor",
|
||||
"napi-derive",
|
||||
"napi-build",
|
||||
"napi-sys",
|
||||
"once_cell",
|
||||
"nohash-hasher",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-build"
|
||||
version = "2.2.0"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03acbfa4f156a32188bfa09b86dc11a431b5725253fc1fc6f6df5bed273382c4"
|
||||
checksum = "dcae8ad5609d14afb3a3b91dee88c757016261b151e9dcecabf1b2a31a6cab14"
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive"
|
||||
version = "2.16.13"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c"
|
||||
checksum = "43e61844e0c0bb81e711f2084abe7cff187b03ca21ff8b000cb59bbda61e15a9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"convert_case",
|
||||
"ctor",
|
||||
"napi-derive-backend",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2225,24 +2247,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive-backend"
|
||||
version = "1.0.75"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf"
|
||||
checksum = "b7ab19e9b98efb13895f492a2e367ca50c955ac3c4723613af73fdda4011afcc"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"semver",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-sys"
|
||||
version = "2.4.0"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
|
||||
checksum = "3e4e7135a8f97aa0f1509cce21a8a1f9dcec1b50d8dee006b48a5adb69a9d64d"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
@@ -2290,6 +2310,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nohash-hasher"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "8.0.0"
|
||||
@@ -3046,6 +3072,12 @@ version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
|
||||
934
bindings/javascript/.yarn/releases/yarn-4.6.0.cjs
vendored
934
bindings/javascript/.yarn/releases/yarn-4.6.0.cjs
vendored
File diff suppressed because one or more lines are too long
942
bindings/javascript/.yarn/releases/yarn-4.9.2.cjs
vendored
Executable file
942
bindings/javascript/.yarn/releases/yarn-4.9.2.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
@@ -1,3 +1,4 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.6.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.9.2.cjs
|
||||
enableHardenedMode: false
|
||||
|
||||
@@ -12,8 +12,8 @@ crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
turso_core = { workspace = true }
|
||||
napi = { version = "2.16.17", default-features = false, features = ["napi4"] }
|
||||
napi-derive = { version = "2.16.13", default-features = true }
|
||||
napi = { version = "3.1.3", default-features = false }
|
||||
napi-derive = { version = "3.1.1", default-features = true }
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.2.0"
|
||||
napi-build = "2.2.3"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import crypto from 'crypto';
|
||||
import crypto from "crypto";
|
||||
import fs from "node:fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "node:path"
|
||||
import path from "node:path";
|
||||
import DualTest from "./dual-test.mjs";
|
||||
|
||||
const inMemoryTest = new DualTest(":memory:");
|
||||
@@ -22,23 +22,30 @@ foobarTest.both("Property .name of database", async (t) => {
|
||||
t.is(db.name, t.context.path);
|
||||
});
|
||||
|
||||
new DualTest("foobar.db", { readonly: true })
|
||||
.both("Property .readonly of database if set", async (t) => {
|
||||
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`;
|
||||
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',
|
||||
});
|
||||
})
|
||||
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;
|
||||
@@ -59,55 +66,64 @@ inMemoryTest.both("Statement.get() returns data", async (t) => {
|
||||
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.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.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.both("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",
|
||||
);
|
||||
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");
|
||||
}
|
||||
});
|
||||
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(
|
||||
"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;
|
||||
@@ -129,8 +145,22 @@ inMemoryTest.both("pragma table_list", async (t) => {
|
||||
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: 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);
|
||||
@@ -144,236 +174,272 @@ inMemoryTest.both("simple pragma table_list", async (t) => {
|
||||
t.deepEqual(actual, expectedValue);
|
||||
});
|
||||
|
||||
inMemoryTest.both("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");
|
||||
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");
|
||||
|
||||
for (const row of stmt.iterate()) {
|
||||
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',
|
||||
},
|
||||
);
|
||||
});
|
||||
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);
|
||||
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();
|
||||
let stmt = db.prepare("SELECT * FROM users").pluck();
|
||||
|
||||
for (const row of stmt.iterate()) {
|
||||
t.truthy(row);
|
||||
t.assert(typeof row === "string");
|
||||
}
|
||||
});
|
||||
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);
|
||||
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();
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
for (const row of stmt.iterate()) {
|
||||
stmt = db.prepare("SELECT * FROM users WHERE name = ?").raw();
|
||||
const row = stmt.get("Alice");
|
||||
t.true(Array.isArray(row));
|
||||
t.true(typeof row[0] === "string");
|
||||
t.true(typeof row[1] === "number");
|
||||
}
|
||||
t.is(row.length, 2);
|
||||
t.is(row[0], "Alice");
|
||||
t.is(row[1], 42);
|
||||
|
||||
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);
|
||||
|
||||
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]);
|
||||
},
|
||||
);
|
||||
|
||||
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",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
inMemoryTest.onlySqlitePasses("Test expand(): Columns should be namespaced", async (t) => {
|
||||
const expandedResults = [
|
||||
{
|
||||
users: {
|
||||
let regularResults = [
|
||||
{
|
||||
name: "Alice",
|
||||
type: "premium",
|
||||
},
|
||||
addresses: {
|
||||
userName: "Alice",
|
||||
type: "home",
|
||||
street: "Alice's street",
|
||||
type: "home",
|
||||
userName: "Alice",
|
||||
},
|
||||
},
|
||||
{
|
||||
users: {
|
||||
{
|
||||
name: "Bob",
|
||||
type: "basic",
|
||||
},
|
||||
addresses: {
|
||||
userName: "Bob",
|
||||
type: "work",
|
||||
street: "Bob's street",
|
||||
type: "work",
|
||||
userName: "Bob",
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
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");
|
||||
|
||||
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();
|
||||
|
||||
let allRows = db
|
||||
.prepare("SELECT * FROM users u JOIN addresses a ON (u.name = a.userName)")
|
||||
.expand(true)
|
||||
.all();
|
||||
t.deepEqual(allRows, expandedResults);
|
||||
|
||||
t.deepEqual(allRows, expandedResults);
|
||||
allRows = db
|
||||
.prepare(
|
||||
"SELECT * FROM users u JOIN addresses a ON (u.name = a.userName)",
|
||||
)
|
||||
.expand()
|
||||
.all();
|
||||
|
||||
allRows = db
|
||||
.prepare("SELECT * FROM users u JOIN addresses a ON (u.name = a.userName)")
|
||||
.expand()
|
||||
.all();
|
||||
t.deepEqual(allRows, expandedResults);
|
||||
|
||||
t.deepEqual(allRows, expandedResults);
|
||||
allRows = db
|
||||
.prepare(
|
||||
"SELECT * FROM users u JOIN addresses a ON (u.name = a.userName)",
|
||||
)
|
||||
.expand(false)
|
||||
.all();
|
||||
|
||||
allRows = db
|
||||
.prepare("SELECT * FROM users u JOIN addresses a ON (u.name = a.userName)")
|
||||
.expand(false)
|
||||
.all();
|
||||
t.deepEqual(allRows, regularResults);
|
||||
},
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
// test raw()
|
||||
let stmt = db.prepare("SELECT * FROM users").pluck().raw();
|
||||
|
||||
for (const row of stmt.iterate()) {
|
||||
stmt = db.prepare("SELECT * FROM users WHERE name = ?").raw();
|
||||
const row = stmt.get("Alice");
|
||||
t.true(Array.isArray(row));
|
||||
t.true(typeof row[0] === "string");
|
||||
t.true(typeof row[1] === "number");
|
||||
}
|
||||
t.is(row.length, 2);
|
||||
t.is(row[0], "Alice");
|
||||
t.is(row[1], 42);
|
||||
|
||||
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);
|
||||
|
||||
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]);
|
||||
|
||||
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();
|
||||
|
||||
// test pluck()
|
||||
stmt = db.prepare("SELECT * FROM users").raw().pluck();
|
||||
for (const name of stmt.all()) {
|
||||
t.truthy(name);
|
||||
t.assert(typeof name === "string");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
for (const name of stmt.iterate()) {
|
||||
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);
|
||||
|
||||
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();
|
||||
|
||||
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 } });
|
||||
},
|
||||
);
|
||||
|
||||
// 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);
|
||||
|
||||
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");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
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").iterate();
|
||||
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.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 => {
|
||||
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,5 +1,5 @@
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import crypto from "crypto";
|
||||
import fs from "fs";
|
||||
import DualTest from "./dual-test.mjs";
|
||||
|
||||
const dualTest = new DualTest();
|
||||
@@ -17,23 +17,26 @@ dualTest.beforeEach(async (t) => {
|
||||
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')"
|
||||
"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')"
|
||||
"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'
|
||||
});
|
||||
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) => {
|
||||
@@ -61,8 +64,10 @@ dualTest.both("Statement.run() [positional]", async (t) => {
|
||||
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" });
|
||||
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);
|
||||
});
|
||||
@@ -81,7 +86,7 @@ dualTest.both("Statement.get() [no parameters]", async (t) => {
|
||||
|
||||
stmt = db.prepare("SELECT * FROM users");
|
||||
t.is(stmt.get().name, "Alice");
|
||||
t.deepEqual(stmt.raw().get(), [1, 'Alice', 'alice@example.org']);
|
||||
t.deepEqual(stmt.raw().get(), [1, "Alice", "alice@example.org"]);
|
||||
});
|
||||
|
||||
dualTest.both("Statement.get() [positional]", async (t) => {
|
||||
@@ -107,7 +112,7 @@ dualTest.both("Statement.get() [named]", async (t) => {
|
||||
var stmt = undefined;
|
||||
|
||||
stmt = db.prepare("SELECT :b, :a");
|
||||
t.deepEqual(stmt.raw().get({ a: 'a', b: 'b' }), ['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);
|
||||
@@ -132,7 +137,7 @@ dualTest.both("Statement.get() [raw]", async (t) => {
|
||||
t.deepEqual(stmt.raw().get(1), [1, "Alice", "alice@example.org"]);
|
||||
});
|
||||
|
||||
dualTest.both("Statement.iterate() [empty]", async (t) => {
|
||||
dualTest.onlySqlitePasses("Statement.iterate() [empty]", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users WHERE id = 0");
|
||||
@@ -141,7 +146,7 @@ dualTest.both("Statement.iterate() [empty]", async (t) => {
|
||||
t.is(stmt.iterate({}).next().done, true);
|
||||
});
|
||||
|
||||
dualTest.both("Statement.iterate()", async (t) => {
|
||||
dualTest.onlySqlitePasses("Statement.iterate()", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
@@ -178,10 +183,7 @@ dualTest.both("Statement.all() [pluck]", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
1,
|
||||
2,
|
||||
];
|
||||
const expected = [1, 2];
|
||||
t.deepEqual(stmt.pluck().all(), expected);
|
||||
});
|
||||
|
||||
@@ -243,70 +245,93 @@ dualTest.onlySqlitePasses(
|
||||
|
||||
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'
|
||||
});
|
||||
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;
|
||||
dualTest.onlySqlitePasses(
|
||||
"Statement.run() with array bind parameter",
|
||||
async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
db.exec(`
|
||||
db.exec(`
|
||||
DROP TABLE IF EXISTS t;
|
||||
CREATE TABLE t (value BLOB);
|
||||
`);
|
||||
|
||||
const array = [1, 2, 3];
|
||||
const array = [1, 2, 3];
|
||||
|
||||
const insertStmt = db.prepare("INSERT INTO t (value) VALUES (?)");
|
||||
await t.throws(() => {
|
||||
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]);
|
||||
}, {
|
||||
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));
|
||||
});
|
||||
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;
|
||||
dualTest.skip(
|
||||
"Statement.run() for vector feature with Float32Array bind parameter",
|
||||
async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
db.exec(`
|
||||
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 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));
|
||||
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`);
|
||||
});
|
||||
// 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;
|
||||
@@ -318,7 +343,7 @@ dualTest.onlySqlitePasses("Statement.columns()", async (t) => {
|
||||
{
|
||||
column: null,
|
||||
database: null,
|
||||
name: '1',
|
||||
name: "1",
|
||||
table: null,
|
||||
type: null,
|
||||
},
|
||||
@@ -354,7 +379,7 @@ dualTest.onlySqlitePasses("Database.transaction()", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const insert = db.prepare(
|
||||
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
||||
"INSERT INTO users(name, email) VALUES (:name, :email)",
|
||||
);
|
||||
|
||||
const insertMany = db.transaction((users) => {
|
||||
@@ -379,7 +404,7 @@ dualTest.onlySqlitePasses("Database.transaction()", async (t) => {
|
||||
dualTest.onlySqlitePasses("Database.transaction().immediate()", async (t) => {
|
||||
const db = t.context.db;
|
||||
const insert = db.prepare(
|
||||
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
||||
"INSERT INTO users(name, email) VALUES (:name, :email)",
|
||||
);
|
||||
const insertMany = db.transaction((users) => {
|
||||
t.is(db.inTransaction, true);
|
||||
@@ -408,83 +433,98 @@ dualTest.onlySqlitePasses("values", async (t) => {
|
||||
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 }]);
|
||||
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'
|
||||
});
|
||||
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)
|
||||
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"
|
||||
});
|
||||
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"
|
||||
});
|
||||
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`;
|
||||
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();
|
||||
});
|
||||
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();
|
||||
},
|
||||
);
|
||||
|
||||
35
bindings/javascript/index.d.ts
vendored
35
bindings/javascript/index.d.ts
vendored
@@ -1,20 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
export interface OpenDatabaseOptions {
|
||||
readonly?: boolean
|
||||
fileMustExist?: boolean
|
||||
timeout?: number
|
||||
}
|
||||
export interface PragmaOptions {
|
||||
simple: boolean
|
||||
}
|
||||
export interface RunResult {
|
||||
changes: number
|
||||
lastInsertRowid: number
|
||||
}
|
||||
/* eslint-disable */
|
||||
export declare class Database {
|
||||
memory: boolean
|
||||
readonly: boolean
|
||||
@@ -32,11 +17,11 @@ export declare class Database {
|
||||
exec(sql: string): void
|
||||
close(): void
|
||||
}
|
||||
|
||||
export declare class Statement {
|
||||
source: string
|
||||
get(args?: Array<unknown> | undefined | null): unknown
|
||||
run(args?: Array<unknown> | undefined | null): RunResult
|
||||
iterate(args?: Array<unknown> | undefined | null): IteratorStatement
|
||||
all(args?: Array<unknown> | undefined | null): unknown
|
||||
pluck(pluck?: boolean | undefined | null): void
|
||||
static expand(): void
|
||||
@@ -44,6 +29,18 @@ export declare class Statement {
|
||||
static columns(): void
|
||||
bind(args?: Array<unknown> | undefined | null): Statement
|
||||
}
|
||||
export declare class IteratorStatement {
|
||||
[Symbol.iterator](): Iterator<unknown, void, void>
|
||||
|
||||
export interface OpenDatabaseOptions {
|
||||
readonly?: boolean
|
||||
fileMustExist?: boolean
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
export interface PragmaOptions {
|
||||
simple: boolean
|
||||
}
|
||||
|
||||
export interface RunResult {
|
||||
changes: number
|
||||
lastInsertRowid: number
|
||||
}
|
||||
|
||||
@@ -1,317 +1,397 @@
|
||||
/* tslint:disable */
|
||||
// prettier-ignore
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
|
||||
// @ts-nocheck
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const { existsSync, readFileSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
|
||||
const { platform, arch } = process
|
||||
const { createRequire } = require('node:module')
|
||||
require = createRequire(__filename)
|
||||
|
||||
const { readFileSync } = require('node:fs')
|
||||
let nativeBinding = null
|
||||
let localFileExisted = false
|
||||
let loadError = null
|
||||
const loadErrors = []
|
||||
|
||||
function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
||||
return readFileSync(lddPath, 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true
|
||||
const isMusl = () => {
|
||||
let musl = false
|
||||
if (process.platform === 'linux') {
|
||||
musl = isMuslFromFilesystem()
|
||||
if (musl === null) {
|
||||
musl = isMuslFromReport()
|
||||
}
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
if (musl === null) {
|
||||
musl = isMuslFromChildProcess()
|
||||
}
|
||||
}
|
||||
return musl
|
||||
}
|
||||
|
||||
const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-')
|
||||
|
||||
const isMuslFromFilesystem = () => {
|
||||
try {
|
||||
return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'android':
|
||||
switch (arch) {
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(join(__dirname, 'turso.android-arm64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.android-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-android-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
localFileExisted = existsSync(join(__dirname, 'turso.android-arm-eabi.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.android-arm-eabi.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-android-arm-eabi')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Android ${arch}`)
|
||||
const isMuslFromReport = () => {
|
||||
let report = null
|
||||
if (typeof process.report?.getReport === 'function') {
|
||||
process.report.excludeNetwork = true
|
||||
report = process.report.getReport()
|
||||
}
|
||||
if (!report) {
|
||||
return null
|
||||
}
|
||||
if (report.header && report.header.glibcVersionRuntime) {
|
||||
return false
|
||||
}
|
||||
if (Array.isArray(report.sharedObjects)) {
|
||||
if (report.sharedObjects.some(isFileMusl)) {
|
||||
return true
|
||||
}
|
||||
break
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.win32-x64-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.win32-x64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-win32-x64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'ia32':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.win32-ia32-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.win32-ia32-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-win32-ia32-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.win32-arm64-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.win32-arm64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-win32-arm64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'darwin':
|
||||
localFileExisted = existsSync(join(__dirname, 'turso.darwin-universal.node'))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isMuslFromChildProcess = () => {
|
||||
try {
|
||||
return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')
|
||||
} catch (e) {
|
||||
// If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function requireNative() {
|
||||
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.darwin-universal.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-darwin-universal')
|
||||
nativeBinding = require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH);
|
||||
} catch (err) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
} else if (process.platform === 'android') {
|
||||
if (process.arch === 'arm64') {
|
||||
try {
|
||||
return require('./turso.android-arm64.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
break
|
||||
} catch {}
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
localFileExisted = existsSync(join(__dirname, 'turso.darwin-x64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.darwin-x64.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-darwin-x64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.darwin-arm64.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.darwin-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-darwin-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
||||
try {
|
||||
return require('@tursodatabase/turso-android-arm64')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else if (process.arch === 'arm') {
|
||||
try {
|
||||
return require('./turso.android-arm-eabi.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-android-arm-eabi')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else {
|
||||
loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`))
|
||||
}
|
||||
break
|
||||
case 'freebsd':
|
||||
if (arch !== 'x64') {
|
||||
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
|
||||
} else if (process.platform === 'win32') {
|
||||
if (process.arch === 'x64') {
|
||||
try {
|
||||
return require('./turso.win32-x64-msvc.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-win32-x64-msvc')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else if (process.arch === 'ia32') {
|
||||
try {
|
||||
return require('./turso.win32-ia32-msvc.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-win32-ia32-msvc')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else if (process.arch === 'arm64') {
|
||||
try {
|
||||
return require('./turso.win32-arm64-msvc.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-win32-arm64-msvc')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else {
|
||||
loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`))
|
||||
}
|
||||
localFileExisted = existsSync(join(__dirname, 'turso.freebsd-x64.node'))
|
||||
} else if (process.platform === 'darwin') {
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.freebsd-x64.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-freebsd-x64')
|
||||
}
|
||||
return require('./turso.darwin-universal.node')
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
loadErrors.push(e)
|
||||
}
|
||||
break
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.linux-x64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.linux-x64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-linux-x64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.linux-x64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.linux-x64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-linux-x64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.linux-arm64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.linux-arm64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-linux-arm64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.linux-arm64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.linux-arm64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-linux-arm64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.linux-arm-musleabihf.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.linux-arm-musleabihf.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-linux-arm-musleabihf')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.linux-arm-gnueabihf.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.linux-arm-gnueabihf.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-linux-arm-gnueabihf')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'riscv64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.linux-riscv64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.linux-riscv64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-linux-riscv64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.linux-riscv64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.linux-riscv64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-linux-riscv64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 's390x':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'turso.linux-s390x-gnu.node')
|
||||
)
|
||||
try {
|
||||
return require('@tursodatabase/turso-darwin-universal')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
if (process.arch === 'x64') {
|
||||
try {
|
||||
return require('./turso.darwin-x64.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-darwin-x64')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else if (process.arch === 'arm64') {
|
||||
try {
|
||||
return require('./turso.darwin-arm64.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-darwin-arm64')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else {
|
||||
loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`))
|
||||
}
|
||||
} else if (process.platform === 'freebsd') {
|
||||
if (process.arch === 'x64') {
|
||||
try {
|
||||
return require('./turso.freebsd-x64.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-freebsd-x64')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else if (process.arch === 'arm64') {
|
||||
try {
|
||||
return require('./turso.freebsd-arm64.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-freebsd-arm64')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else {
|
||||
loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`))
|
||||
}
|
||||
} else if (process.platform === 'linux') {
|
||||
if (process.arch === 'x64') {
|
||||
if (isMusl()) {
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./turso.linux-s390x-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('@tursodatabase/turso-linux-s390x-gnu')
|
||||
}
|
||||
return require('./turso.linux-x64-musl.node')
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
loadErrors.push(e)
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-x64-musl')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return require('./turso.linux-x64-gnu.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-x64-gnu')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
}
|
||||
} else if (process.arch === 'arm64') {
|
||||
if (isMusl()) {
|
||||
try {
|
||||
return require('./turso.linux-arm64-musl.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-arm64-musl')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return require('./turso.linux-arm64-gnu.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-arm64-gnu')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
}
|
||||
} else if (process.arch === 'arm') {
|
||||
if (isMusl()) {
|
||||
try {
|
||||
return require('./turso.linux-arm-musleabihf.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-arm-musleabihf')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return require('./turso.linux-arm-gnueabihf.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-arm-gnueabihf')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
}
|
||||
} else if (process.arch === 'riscv64') {
|
||||
if (isMusl()) {
|
||||
try {
|
||||
return require('./turso.linux-riscv64-musl.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-riscv64-musl')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return require('./turso.linux-riscv64-gnu.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-riscv64-gnu')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
}
|
||||
} else if (process.arch === 'ppc64') {
|
||||
try {
|
||||
return require('./turso.linux-ppc64-gnu.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-ppc64-gnu')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else if (process.arch === 's390x') {
|
||||
try {
|
||||
return require('./turso.linux-s390x-gnu.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-s390x-gnu')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else {
|
||||
loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`))
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
||||
} else if (process.platform === 'openharmony') {
|
||||
if (process.arch === 'arm64') {
|
||||
try {
|
||||
return require('./turso.linux-arm64-ohos.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-arm64-ohos')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else if (process.arch === 'x64') {
|
||||
try {
|
||||
return require('./turso.linux-x64-ohos.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-x64-ohos')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else if (process.arch === 'arm') {
|
||||
try {
|
||||
return require('./turso.linux-arm-ohos.node')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
try {
|
||||
return require('@tursodatabase/turso-linux-arm-ohos')
|
||||
} catch (e) {
|
||||
loadErrors.push(e)
|
||||
}
|
||||
} else {
|
||||
loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`))
|
||||
}
|
||||
} else {
|
||||
loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`))
|
||||
}
|
||||
}
|
||||
|
||||
nativeBinding = requireNative()
|
||||
|
||||
if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
|
||||
try {
|
||||
nativeBinding = require('./turso.wasi.cjs')
|
||||
} catch (err) {
|
||||
if (process.env.NAPI_RS_FORCE_WASI) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
}
|
||||
if (!nativeBinding) {
|
||||
try {
|
||||
nativeBinding = require('@tursodatabase/turso-wasm32-wasi')
|
||||
} catch (err) {
|
||||
if (process.env.NAPI_RS_FORCE_WASI) {
|
||||
loadErrors.push(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!nativeBinding) {
|
||||
if (loadError) {
|
||||
throw loadError
|
||||
if (loadErrors.length > 0) {
|
||||
throw new Error(
|
||||
`Cannot find native binding. ` +
|
||||
`npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` +
|
||||
'Please try `npm i` again after removing both package-lock.json and node_modules directory.',
|
||||
{ cause: loadErrors }
|
||||
)
|
||||
}
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { Database, Statement, IteratorStatement } = nativeBinding
|
||||
|
||||
module.exports.Database = Database
|
||||
module.exports.Statement = Statement
|
||||
module.exports.IteratorStatement = IteratorStatement
|
||||
module.exports = nativeBinding
|
||||
module.exports.Database = nativeBinding.Database
|
||||
module.exports.Statement = nativeBinding.Statement
|
||||
|
||||
978
bindings/javascript/package-lock.json
generated
978
bindings/javascript/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,20 +9,16 @@
|
||||
"main": "wrapper.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
"name": "turso",
|
||||
"release": false,
|
||||
"triples": {
|
||||
"defaults": false,
|
||||
"additional": [
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"universal-apple-darwin"
|
||||
]
|
||||
}
|
||||
"binaryName": "turso",
|
||||
"targets": [
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"universal-apple-darwin"
|
||||
]
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.18.4",
|
||||
"@napi-rs/cli": "^3.0.4",
|
||||
"ava": "^6.0.1",
|
||||
"better-sqlite3": "^11.9.1"
|
||||
},
|
||||
@@ -38,8 +34,8 @@
|
||||
"build:debug": "napi build --platform",
|
||||
"prepublishOnly": "napi prepublish -t npm",
|
||||
"test": "ava",
|
||||
"universal": "napi universal",
|
||||
"universal": "napi universalize",
|
||||
"version": "napi version"
|
||||
},
|
||||
"packageManager": "yarn@4.6.0"
|
||||
}
|
||||
"packageManager": "yarn@4.9.2"
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ use std::num::{NonZero, NonZeroUsize};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use napi::iterator::Generator;
|
||||
use napi::{bindgen_prelude::ObjectFinalize, Env, JsUnknown};
|
||||
use napi::bindgen_prelude::{JsObjectValue, Null, Object, ToNapiValue};
|
||||
use napi::{bindgen_prelude::ObjectFinalize, Env, JsValue, Unknown};
|
||||
use napi_derive::napi;
|
||||
use turso_core::{LimboError, StepResult};
|
||||
|
||||
@@ -107,12 +107,12 @@ impl Database {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn pragma(
|
||||
pub fn pragma<'env>(
|
||||
&self,
|
||||
env: Env,
|
||||
env: &'env Env,
|
||||
pragma_name: String,
|
||||
options: Option<PragmaOptions>,
|
||||
) -> napi::Result<JsUnknown> {
|
||||
) -> napi::Result<Unknown<'env>> {
|
||||
let sql = format!("PRAGMA {pragma_name}");
|
||||
let stmt = self.prepare(sql)?;
|
||||
match options {
|
||||
@@ -122,10 +122,10 @@ impl Database {
|
||||
match stmt.step().map_err(into_napi_error)? {
|
||||
turso_core::StepResult::Row => {
|
||||
let row: Vec<_> = stmt.row().unwrap().get_values().cloned().collect();
|
||||
return to_js_value(&env, &row[0]);
|
||||
return to_js_value(env, row[0].clone());
|
||||
}
|
||||
turso_core::StepResult::Done => {
|
||||
return Ok(env.get_undefined()?.into_unknown())
|
||||
return ToNapiValue::into_unknown((), env);
|
||||
}
|
||||
turso_core::StepResult::IO => {
|
||||
stmt.run_once().map_err(into_napi_error)?;
|
||||
@@ -141,7 +141,7 @@ impl Database {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => stmt.run_internal(env, None),
|
||||
_ => Ok(stmt.run_internal(env, None)?),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +266,11 @@ impl Statement {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get(&self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<JsUnknown> {
|
||||
pub fn get<'env>(
|
||||
&self,
|
||||
env: &'env Env,
|
||||
args: Option<Vec<Unknown>>,
|
||||
) -> napi::Result<Unknown<'env>> {
|
||||
let mut stmt = self.check_and_bind(env, args)?;
|
||||
|
||||
loop {
|
||||
@@ -279,12 +283,11 @@ impl Statement {
|
||||
PresentationMode::Raw => {
|
||||
let mut raw_obj = env.create_array(row.len() as u32)?;
|
||||
for (idx, value) in row.get_values().enumerate() {
|
||||
let js_value = to_js_value(&env, value);
|
||||
let js_value = to_js_value(env, value.clone());
|
||||
|
||||
raw_obj.set(idx as u32, js_value)?;
|
||||
}
|
||||
|
||||
return Ok(raw_obj.coerce_to_object()?.into_unknown());
|
||||
return Ok(raw_obj.coerce_to_object()?.to_unknown());
|
||||
}
|
||||
PresentationMode::Pluck => {
|
||||
let (_, value) =
|
||||
@@ -292,25 +295,25 @@ impl Statement {
|
||||
napi::Status::GenericFailure,
|
||||
"Pluck mode requires at least one column in the result",
|
||||
))?;
|
||||
let js_value = to_js_value(&env, value)?;
|
||||
|
||||
return Ok(js_value);
|
||||
let result = to_js_value(env, value.clone())?;
|
||||
return ToNapiValue::into_unknown(result, env);
|
||||
}
|
||||
PresentationMode::None => {
|
||||
let mut obj = env.create_object()?;
|
||||
let mut obj = Object::new(env)?;
|
||||
|
||||
for (idx, value) in row.get_values().enumerate() {
|
||||
let key = stmt.get_column_name(idx);
|
||||
let js_value = to_js_value(&env, value);
|
||||
let js_value = to_js_value(env, value.clone());
|
||||
|
||||
obj.set_named_property(&key, js_value)?;
|
||||
}
|
||||
|
||||
return Ok(obj.into_unknown());
|
||||
return Ok(obj.to_unknown());
|
||||
}
|
||||
}
|
||||
}
|
||||
turso_core::StepResult::Done => return Ok(env.get_undefined()?.into_unknown()),
|
||||
turso_core::StepResult::Done => return ToNapiValue::into_unknown((), env),
|
||||
turso_core::StepResult::IO => {
|
||||
stmt.run_once().map_err(into_napi_error)?;
|
||||
continue;
|
||||
@@ -326,11 +329,15 @@ impl Statement {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn run(&self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<RunResult> {
|
||||
self.run_and_build_info_object(|| self.run_internal(env, args))
|
||||
pub fn run(&self, env: Env, args: Option<Vec<Unknown>>) -> napi::Result<RunResult> {
|
||||
self.run_and_build_info_object(|| self.run_internal(&env, args))
|
||||
}
|
||||
|
||||
fn run_internal(&self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<JsUnknown> {
|
||||
fn run_internal<'env>(
|
||||
&self,
|
||||
env: &'env Env,
|
||||
args: Option<Vec<Unknown>>,
|
||||
) -> napi::Result<Unknown<'env>> {
|
||||
let stmt = self.check_and_bind(env, args)?;
|
||||
|
||||
self.internal_all(env, stmt)
|
||||
@@ -358,38 +365,22 @@ impl Statement {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn iterate(
|
||||
pub fn all<'env>(
|
||||
&self,
|
||||
env: Env,
|
||||
args: Option<Vec<JsUnknown>>,
|
||||
) -> napi::Result<IteratorStatement> {
|
||||
if let Some(some_args) = args.as_ref() {
|
||||
if some_args.iter().len() != 0 {
|
||||
self.check_and_bind(env, args)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(IteratorStatement {
|
||||
stmt: Rc::clone(&self.inner),
|
||||
_database: self.database.clone(),
|
||||
env,
|
||||
presentation_mode: self.presentation_mode.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn all(&self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<JsUnknown> {
|
||||
env: &'env Env,
|
||||
args: Option<Vec<Unknown>>,
|
||||
) -> napi::Result<Unknown<'env>> {
|
||||
let stmt = self.check_and_bind(env, args)?;
|
||||
|
||||
self.internal_all(env, stmt)
|
||||
}
|
||||
|
||||
fn internal_all(
|
||||
fn internal_all<'env>(
|
||||
&self,
|
||||
env: Env,
|
||||
env: &'env Env,
|
||||
mut stmt: RefMut<'_, turso_core::Statement>,
|
||||
) -> napi::Result<JsUnknown> {
|
||||
let mut results = env.create_empty_array()?;
|
||||
) -> napi::Result<Unknown<'env>> {
|
||||
let mut results = env.create_array(1)?;
|
||||
let mut index = 0;
|
||||
loop {
|
||||
match stmt.step().map_err(into_napi_error)? {
|
||||
@@ -400,7 +391,7 @@ impl Statement {
|
||||
PresentationMode::Raw => {
|
||||
let mut raw_array = env.create_array(row.len() as u32)?;
|
||||
for (idx, value) in row.get_values().enumerate() {
|
||||
let js_value = to_js_value(&env, value)?;
|
||||
let js_value = to_js_value(env, value.clone())?;
|
||||
raw_array.set(idx as u32, js_value)?;
|
||||
}
|
||||
results.set_element(index, raw_array.coerce_to_object()?)?;
|
||||
@@ -413,16 +404,16 @@ impl Statement {
|
||||
napi::Status::GenericFailure,
|
||||
"Pluck mode requires at least one column in the result",
|
||||
))?;
|
||||
let js_value = to_js_value(&env, value)?;
|
||||
let js_value = to_js_value(env, value.clone())?;
|
||||
results.set_element(index, js_value)?;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
PresentationMode::None => {
|
||||
let mut obj = env.create_object()?;
|
||||
let mut obj = Object::new(env)?;
|
||||
for (idx, value) in row.get_values().enumerate() {
|
||||
let key = stmt.get_column_name(idx);
|
||||
let js_value = to_js_value(&env, value);
|
||||
let js_value = to_js_value(env, value.clone());
|
||||
obj.set_named_property(&key, js_value)?;
|
||||
}
|
||||
results.set_element(index, obj)?;
|
||||
@@ -445,7 +436,7 @@ impl Statement {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results.into_unknown())
|
||||
Ok(results.to_unknown())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -475,8 +466,8 @@ impl Statement {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn bind(&mut self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<Self, String> {
|
||||
self.check_and_bind(env, args)
|
||||
pub fn bind(&mut self, env: Env, args: Option<Vec<Unknown>>) -> napi::Result<Self, String> {
|
||||
self.check_and_bind(&env, args)
|
||||
.map_err(with_sqlite_error_message)?;
|
||||
self.binded = true;
|
||||
|
||||
@@ -487,8 +478,8 @@ impl Statement {
|
||||
/// and bind values to variables.
|
||||
fn check_and_bind(
|
||||
&self,
|
||||
env: Env,
|
||||
args: Option<Vec<JsUnknown>>,
|
||||
env: &Env,
|
||||
args: Option<Vec<Unknown>>,
|
||||
) -> napi::Result<RefMut<'_, turso_core::Statement>> {
|
||||
let mut stmt = self.inner.borrow_mut();
|
||||
stmt.reset();
|
||||
@@ -507,8 +498,7 @@ impl Statement {
|
||||
|
||||
if args.len() == 1 {
|
||||
if matches!(args[0].get_type()?, napi::ValueType::Object) {
|
||||
let obj: napi::JsObject =
|
||||
args.into_iter().next().unwrap().coerce_to_object()?;
|
||||
let obj: Object = args.into_iter().next().unwrap().coerce_to_object()?;
|
||||
|
||||
if obj.is_array()? {
|
||||
bind_positional_param_array(&mut stmt, &obj)?;
|
||||
@@ -529,7 +519,7 @@ impl Statement {
|
||||
|
||||
fn bind_positional_params(
|
||||
stmt: &mut RefMut<'_, turso_core::Statement>,
|
||||
args: Vec<JsUnknown>,
|
||||
args: Vec<Unknown>,
|
||||
) -> Result<(), napi::Error> {
|
||||
for (i, elem) in args.into_iter().enumerate() {
|
||||
let value = from_js_value(elem)?;
|
||||
@@ -540,7 +530,7 @@ fn bind_positional_params(
|
||||
|
||||
fn bind_host_params(
|
||||
stmt: &mut RefMut<'_, turso_core::Statement>,
|
||||
obj: &napi::JsObject,
|
||||
obj: &Object,
|
||||
) -> Result<(), napi::Error> {
|
||||
if first_key_is_number(obj) {
|
||||
bind_numbered_params(stmt, obj)?;
|
||||
@@ -551,8 +541,8 @@ fn bind_host_params(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn first_key_is_number(obj: &napi::JsObject) -> bool {
|
||||
napi::JsObject::keys(obj)
|
||||
fn first_key_is_number(obj: &Object) -> bool {
|
||||
Object::keys(obj)
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|key| matches!(obj.has_own_property(key), Ok(result) if result))
|
||||
@@ -562,9 +552,9 @@ fn first_key_is_number(obj: &napi::JsObject) -> bool {
|
||||
|
||||
fn bind_numbered_params(
|
||||
stmt: &mut RefMut<'_, turso_core::Statement>,
|
||||
obj: &napi::JsObject,
|
||||
obj: &Object,
|
||||
) -> Result<(), napi::Error> {
|
||||
for key in napi::JsObject::keys(obj)?.iter() {
|
||||
for key in Object::keys(obj)?.iter() {
|
||||
let Ok(param_idx) = str::parse::<u32>(key) else {
|
||||
return Err(napi::Error::new(
|
||||
napi::Status::GenericFailure,
|
||||
@@ -585,7 +575,7 @@ fn bind_numbered_params(
|
||||
|
||||
fn bind_named_params(
|
||||
stmt: &mut RefMut<'_, turso_core::Statement>,
|
||||
obj: &napi::JsObject,
|
||||
obj: &Object,
|
||||
) -> Result<(), napi::Error> {
|
||||
for idx in 1..stmt.parameters_count() + 1 {
|
||||
let non_zero_idx = NonZero::new(idx).unwrap();
|
||||
@@ -597,7 +587,7 @@ fn bind_named_params(
|
||||
)));
|
||||
};
|
||||
|
||||
let value = obj.get_named_property::<napi::JsUnknown>(&name[1..])?;
|
||||
let value = obj.get_named_property::<napi::Unknown>(&name[1..])?;
|
||||
stmt.bind_at(non_zero_idx, from_js_value(value)?);
|
||||
}
|
||||
|
||||
@@ -606,7 +596,7 @@ fn bind_named_params(
|
||||
|
||||
fn bind_positional_param_array(
|
||||
stmt: &mut RefMut<'_, turso_core::Statement>,
|
||||
obj: &napi::JsObject,
|
||||
obj: &Object,
|
||||
) -> Result<(), napi::Error> {
|
||||
assert!(obj.is_array()?, "bind_array can only be called with arrays");
|
||||
|
||||
@@ -622,90 +612,29 @@ fn bind_positional_param_array(
|
||||
|
||||
fn bind_single_param(
|
||||
stmt: &mut RefMut<'_, turso_core::Statement>,
|
||||
obj: napi::JsUnknown,
|
||||
obj: napi::Unknown,
|
||||
) -> Result<(), napi::Error> {
|
||||
stmt.bind_at(NonZero::new(1).unwrap(), from_js_value(obj)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi(iterator)]
|
||||
pub struct IteratorStatement {
|
||||
stmt: Rc<RefCell<turso_core::Statement>>,
|
||||
_database: Database,
|
||||
env: Env,
|
||||
presentation_mode: PresentationMode,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl Generator for IteratorStatement {
|
||||
type Yield = JsUnknown;
|
||||
|
||||
type Next = ();
|
||||
|
||||
type Return = ();
|
||||
|
||||
fn next(&mut self, _: Option<Self::Next>) -> Option<Self::Yield> {
|
||||
let mut stmt = self.stmt.borrow_mut();
|
||||
|
||||
loop {
|
||||
match stmt.step().ok()? {
|
||||
turso_core::StepResult::Row => {
|
||||
let row = stmt.row().unwrap();
|
||||
|
||||
match self.presentation_mode {
|
||||
PresentationMode::Raw => {
|
||||
let mut raw_array = self.env.create_array(row.len() as u32).ok()?;
|
||||
for (idx, value) in row.get_values().enumerate() {
|
||||
let js_value = to_js_value(&self.env, value);
|
||||
raw_array.set(idx as u32, js_value).ok()?;
|
||||
}
|
||||
|
||||
return Some(raw_array.coerce_to_object().ok()?.into_unknown());
|
||||
}
|
||||
PresentationMode::Pluck => {
|
||||
let (_, value) = row.get_values().enumerate().next()?;
|
||||
return to_js_value(&self.env, value).ok();
|
||||
}
|
||||
PresentationMode::None => {
|
||||
let mut js_row = self.env.create_object().ok()?;
|
||||
for (idx, value) in row.get_values().enumerate() {
|
||||
let key = stmt.get_column_name(idx);
|
||||
let js_value = to_js_value(&self.env, value);
|
||||
js_row.set_named_property(&key, js_value).ok()?;
|
||||
}
|
||||
|
||||
return Some(js_row.into_unknown());
|
||||
}
|
||||
}
|
||||
}
|
||||
turso_core::StepResult::Done => return None,
|
||||
turso_core::StepResult::IO => {
|
||||
stmt.run_once().ok()?;
|
||||
continue;
|
||||
}
|
||||
turso_core::StepResult::Interrupt | turso_core::StepResult::Busy => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_js_value(env: &napi::Env, value: &turso_core::Value) -> napi::Result<JsUnknown> {
|
||||
fn to_js_value<'a>(env: &'a napi::Env, value: turso_core::Value) -> napi::Result<Unknown<'a>> {
|
||||
match value {
|
||||
turso_core::Value::Null => Ok(env.get_null()?.into_unknown()),
|
||||
turso_core::Value::Integer(i) => Ok(env.create_int64(*i)?.into_unknown()),
|
||||
turso_core::Value::Float(f) => Ok(env.create_double(*f)?.into_unknown()),
|
||||
turso_core::Value::Text(s) => Ok(env.create_string(s.as_str())?.into_unknown()),
|
||||
turso_core::Value::Blob(b) => Ok(env.create_buffer_copy(b.as_slice())?.into_unknown()),
|
||||
turso_core::Value::Null => ToNapiValue::into_unknown(Null, env),
|
||||
turso_core::Value::Integer(i) => ToNapiValue::into_unknown(i, env),
|
||||
turso_core::Value::Float(f) => ToNapiValue::into_unknown(f, env),
|
||||
turso_core::Value::Text(s) => ToNapiValue::into_unknown(s.as_str(), env),
|
||||
turso_core::Value::Blob(b) => ToNapiValue::into_unknown(b, env),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_js_value(value: JsUnknown) -> napi::Result<turso_core::Value> {
|
||||
fn from_js_value(value: Unknown<'_>) -> napi::Result<turso_core::Value> {
|
||||
match value.get_type()? {
|
||||
napi::ValueType::Undefined | napi::ValueType::Null | napi::ValueType::Unknown => {
|
||||
Ok(turso_core::Value::Null)
|
||||
}
|
||||
napi::ValueType::Boolean => {
|
||||
let b = value.coerce_to_bool()?.get_value()?;
|
||||
let b = value.coerce_to_bool()?;
|
||||
Ok(turso_core::Value::Integer(b as i64))
|
||||
}
|
||||
napi::ValueType::Number => {
|
||||
@@ -801,10 +730,10 @@ fn into_napi_error_with_message(
|
||||
|
||||
#[inline]
|
||||
fn with_sqlite_error_message(err: napi::Error) -> napi::Error<String> {
|
||||
napi::Error::new("SQLITE_ERROR".to_owned(), err.reason)
|
||||
napi::Error::new("SQLITE_ERROR".to_owned(), err.reason.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_convertible_type_error_message(error_type: &str) -> String {
|
||||
"[TURSO_CONVERT_TYPE]".to_owned() + error_type
|
||||
"[TURSO_CONVERT_TYPE] ".to_owned() + error_type
|
||||
}
|
||||
|
||||
@@ -5,11 +5,14 @@ const { Database: NativeDB } = require("./index.js");
|
||||
const SqliteError = require("./sqlite-error.js");
|
||||
|
||||
const convertibleErrorTypes = { TypeError };
|
||||
const CONVERTIBLE_ERROR_PREFIX = '[TURSO_CONVERT_TYPE]';
|
||||
const CONVERTIBLE_ERROR_PREFIX = "[TURSO_CONVERT_TYPE]";
|
||||
|
||||
function convertError(err) {
|
||||
if ((err.code ?? '').startsWith(CONVERTIBLE_ERROR_PREFIX)) {
|
||||
return createErrorByName(err.code.substring(CONVERTIBLE_ERROR_PREFIX.length), err.message);
|
||||
if ((err.code ?? "").startsWith(CONVERTIBLE_ERROR_PREFIX)) {
|
||||
return createErrorByName(
|
||||
err.code.substring(CONVERTIBLE_ERROR_PREFIX.length),
|
||||
err.message,
|
||||
);
|
||||
}
|
||||
|
||||
return new SqliteError(err.message, err.code, err.rawCode);
|
||||
@@ -40,7 +43,8 @@ class Database {
|
||||
*/
|
||||
constructor(path, opts = {}) {
|
||||
opts.readonly = opts.readonly === undefined ? false : opts.readonly;
|
||||
opts.fileMustExist = opts.fileMustExist === undefined ? false : opts.fileMustExist;
|
||||
opts.fileMustExist =
|
||||
opts.fileMustExist === undefined ? false : opts.fileMustExist;
|
||||
opts.timeout = opts.timeout === undefined ? 0 : opts.timeout;
|
||||
|
||||
this.db = new NativeDB(path, opts);
|
||||
@@ -66,8 +70,8 @@ class Database {
|
||||
open: {
|
||||
get() {
|
||||
return this.db.open;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,7 +82,7 @@ class Database {
|
||||
*/
|
||||
prepare(sql) {
|
||||
if (!sql) {
|
||||
throw new RangeError('The supplied SQL string contains no statements');
|
||||
throw new RangeError("The supplied SQL string contains no statements");
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -265,10 +269,7 @@ class Statement {
|
||||
* @param bindParameters - The bind parameters for executing the statement.
|
||||
*/
|
||||
*iterate(...bindParameters) {
|
||||
// revisit this solution when https://github.com/napi-rs/napi-rs/issues/2574 is fixed
|
||||
for (const row of this.stmt.iterate(bindParameters.flat())) {
|
||||
yield row;
|
||||
}
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user