mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-21 16:05:17 +01:00
Merge 'bindings/javascript: Improve error handling compatibility with better-sqlite3' from Mikaël Francoeur
This PR brings the error handling of the js bindings one step closer to better-sqlite3. There is still some work left for the error handling to be 100% compatible. This is my first non-trivial Rust PR, so if you have any comments that can help me improve, please leave them on the PR. ----- as part of https://github.com/tursodatabase/turso/issues/1900 Reviewed-by: Diego Reis (@el-yawd) Closes #2009
This commit is contained in:
@@ -13,7 +13,7 @@ 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 = false }
|
||||
napi-derive = { version = "2.16.13", default-features = true }
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.2.0"
|
||||
|
||||
@@ -32,7 +32,7 @@ const genDatabaseFilename = () => {
|
||||
return `test-${crypto.randomBytes(8).toString('hex')}.db`;
|
||||
};
|
||||
|
||||
new DualTest().onlySqlitePasses("opening a read-only database fails if the file doesn't exist", async (t) => {
|
||||
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,
|
||||
@@ -104,7 +104,21 @@ inMemoryTest.both("Empty prepared statement should throw", async (t) => {
|
||||
() => {
|
||||
db.prepare("");
|
||||
},
|
||||
{ instanceOf: Error },
|
||||
{ any: true }
|
||||
);
|
||||
});
|
||||
|
||||
inMemoryTest.onlySqlitePasses("Empty prepared statement should throw the correct error", async (t) => {
|
||||
// the previous test can be removed once this one passes in Turso
|
||||
const db = t.context.db;
|
||||
t.throws(
|
||||
() => {
|
||||
db.prepare("");
|
||||
},
|
||||
{
|
||||
instanceOf: RangeError,
|
||||
message: "The supplied SQL string contains no statements",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -156,9 +170,12 @@ inMemoryTest.both("Statement shouldn't bind twice with bind()", async (t) => {
|
||||
|
||||
t.throws(
|
||||
() => {
|
||||
db.bind("Bob");
|
||||
stmt.bind("Bob");
|
||||
},
|
||||
{
|
||||
instanceOf: TypeError,
|
||||
message: 'The bind() method can only be invoked once per statement object',
|
||||
},
|
||||
{ instanceOf: Error },
|
||||
);
|
||||
});
|
||||
|
||||
@@ -372,3 +389,4 @@ inMemoryTest.both("Test Statement.source", async t => {
|
||||
t.is(stmt.source, sql);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -377,7 +377,7 @@ dualTest.both("Database.pragma()", async (t) => {
|
||||
t.deepEqual(db.pragma("cache_size"), [{ "cache_size": 2000 }]);
|
||||
});
|
||||
|
||||
dualTest.onlySqlitePasses("errors", async (t) => {
|
||||
dualTest.both("errors", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const syntaxError = await t.throws(() => {
|
||||
@@ -385,7 +385,7 @@ dualTest.onlySqlitePasses("errors", async (t) => {
|
||||
}, {
|
||||
any: true,
|
||||
instanceOf: t.context.errorType,
|
||||
message: 'near "SYNTAX": syntax error',
|
||||
message: /near "SYNTAX": syntax error/,
|
||||
code: 'SQLITE_ERROR'
|
||||
});
|
||||
const noTableError = await t.throws(() => {
|
||||
@@ -393,7 +393,7 @@ dualTest.onlySqlitePasses("errors", async (t) => {
|
||||
}, {
|
||||
any: true,
|
||||
instanceOf: t.context.errorType,
|
||||
message: "no such table: missing_table",
|
||||
message: /(Parse error: Table missing_table not found|no such table: missing_table)/,
|
||||
code: 'SQLITE_ERROR'
|
||||
});
|
||||
|
||||
|
||||
40
bindings/javascript/index.d.ts
vendored
40
bindings/javascript/index.d.ts
vendored
@@ -3,41 +3,41 @@
|
||||
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
export interface Options {
|
||||
readonly: boolean
|
||||
fileMustExist: boolean
|
||||
timeout: number
|
||||
export interface OpenDatabaseOptions {
|
||||
readonly?: boolean
|
||||
fileMustExist?: boolean
|
||||
timeout?: number
|
||||
}
|
||||
export interface PragmaOptions {
|
||||
simple: boolean
|
||||
}
|
||||
export declare class Database {
|
||||
memory: boolean
|
||||
readonly: boolean
|
||||
inTransaction: boolean
|
||||
open: boolean
|
||||
name: string
|
||||
constructor(path: string, options?: Options | undefined | null)
|
||||
constructor(path: string, options?: OpenDatabaseOptions | undefined | null)
|
||||
prepare(sql: string): Statement
|
||||
transaction(): void
|
||||
pragma(): void
|
||||
pragma(pragmaName: string, options?: PragmaOptions | undefined | null): unknown
|
||||
backup(): void
|
||||
serialize(): void
|
||||
function(): void
|
||||
aggregate(): void
|
||||
table(): void
|
||||
loadExtension(): void
|
||||
loadExtension(path: string): void
|
||||
exec(sql: string): void
|
||||
close(): void
|
||||
}
|
||||
export declare class Statement {
|
||||
database: Database
|
||||
source: string
|
||||
reader: boolean
|
||||
readonly: boolean
|
||||
busy: boolean
|
||||
get(): unknown
|
||||
all(): NapiResult
|
||||
run(args: Array<unknown>): void
|
||||
static iterate(): void
|
||||
static pluck(): void
|
||||
get(args?: Array<unknown> | undefined | null): unknown
|
||||
run(args?: Array<unknown> | undefined | null): unknown
|
||||
iterate(args?: Array<unknown> | undefined | null): IteratorStatement
|
||||
all(args?: Array<unknown> | undefined | null): unknown
|
||||
pluck(pluck?: boolean | undefined | null): void
|
||||
static expand(): void
|
||||
static raw(): void
|
||||
raw(raw?: boolean | undefined | null): void
|
||||
static columns(): void
|
||||
static bind(): void
|
||||
bind(args?: Array<unknown> | undefined | null): Statement
|
||||
}
|
||||
export declare class IteratorStatement { }
|
||||
|
||||
@@ -5,325 +5,313 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const { existsSync, readFileSync } = require('fs')
|
||||
const { join } = require("path");
|
||||
const { join } = require('path')
|
||||
|
||||
const { platform, arch } = process;
|
||||
const { platform, arch } = process
|
||||
|
||||
let nativeBinding = null;
|
||||
let localFileExisted = false;
|
||||
let loadError = null;
|
||||
let nativeBinding = null
|
||||
let localFileExisted = false
|
||||
let loadError = null
|
||||
|
||||
function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== "function") {
|
||||
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");
|
||||
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
||||
return readFileSync(lddPath, 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header;
|
||||
return !glibcVersionRuntime;
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
}
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case "android":
|
||||
case 'android':
|
||||
switch (arch) {
|
||||
case "arm64":
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.android-arm64.node"),
|
||||
);
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(join(__dirname, 'turso.android-arm64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.android-arm64.node");
|
||||
nativeBinding = require('./turso.android-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-android-arm64");
|
||||
nativeBinding = require('@tursodatabase/turso-android-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
break;
|
||||
case "arm":
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.android-arm-eabi.node"),
|
||||
);
|
||||
break
|
||||
case 'arm':
|
||||
localFileExisted = existsSync(join(__dirname, 'turso.android-arm-eabi.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.android-arm-eabi.node");
|
||||
nativeBinding = require('./turso.android-arm-eabi.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-android-arm-eabi");
|
||||
nativeBinding = require('@tursodatabase/turso-android-arm-eabi')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
break;
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Android ${arch}`);
|
||||
throw new Error(`Unsupported architecture on Android ${arch}`)
|
||||
}
|
||||
break;
|
||||
case "win32":
|
||||
break
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case "x64":
|
||||
case 'x64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.win32-x64-msvc.node"),
|
||||
);
|
||||
join(__dirname, 'turso.win32-x64-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.win32-x64-msvc.node");
|
||||
nativeBinding = require('./turso.win32-x64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-win32-x64-msvc");
|
||||
nativeBinding = require('@tursodatabase/turso-win32-x64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
break;
|
||||
case "ia32":
|
||||
break
|
||||
case 'ia32':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.win32-ia32-msvc.node"),
|
||||
);
|
||||
join(__dirname, 'turso.win32-ia32-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.win32-ia32-msvc.node");
|
||||
nativeBinding = require('./turso.win32-ia32-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-win32-ia32-msvc");
|
||||
nativeBinding = require('@tursodatabase/turso-win32-ia32-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
break;
|
||||
case "arm64":
|
||||
break
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.win32-arm64-msvc.node"),
|
||||
);
|
||||
join(__dirname, 'turso.win32-arm64-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.win32-arm64-msvc.node");
|
||||
nativeBinding = require('./turso.win32-arm64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-win32-arm64-msvc");
|
||||
nativeBinding = require('@tursodatabase/turso-win32-arm64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
break;
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`);
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
||||
}
|
||||
break;
|
||||
case "darwin":
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.darwin-universal.node"),
|
||||
);
|
||||
break
|
||||
case 'darwin':
|
||||
localFileExisted = existsSync(join(__dirname, 'turso.darwin-universal.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.darwin-universal.node");
|
||||
nativeBinding = require('./turso.darwin-universal.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-darwin-universal");
|
||||
nativeBinding = require('@tursodatabase/turso-darwin-universal')
|
||||
}
|
||||
break;
|
||||
break
|
||||
} catch {}
|
||||
switch (arch) {
|
||||
case "x64":
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.darwin-x64.node"),
|
||||
);
|
||||
case 'x64':
|
||||
localFileExisted = existsSync(join(__dirname, 'turso.darwin-x64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.darwin-x64.node");
|
||||
nativeBinding = require('./turso.darwin-x64.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-darwin-x64");
|
||||
nativeBinding = require('@tursodatabase/turso-darwin-x64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
break;
|
||||
case "arm64":
|
||||
break
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.darwin-arm64.node"),
|
||||
);
|
||||
join(__dirname, 'turso.darwin-arm64.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.darwin-arm64.node");
|
||||
nativeBinding = require('./turso.darwin-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-darwin-arm64");
|
||||
nativeBinding = require('@tursodatabase/turso-darwin-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
break;
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`);
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
||||
}
|
||||
break;
|
||||
case "freebsd":
|
||||
if (arch !== "x64") {
|
||||
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`);
|
||||
break
|
||||
case 'freebsd':
|
||||
if (arch !== 'x64') {
|
||||
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
|
||||
}
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.freebsd-x64.node"),
|
||||
);
|
||||
localFileExisted = existsSync(join(__dirname, 'turso.freebsd-x64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.freebsd-x64.node");
|
||||
nativeBinding = require('./turso.freebsd-x64.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-freebsd-x64");
|
||||
nativeBinding = require('@tursodatabase/turso-freebsd-x64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
break;
|
||||
case "linux":
|
||||
break
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case "x64":
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.linux-x64-musl.node"),
|
||||
);
|
||||
join(__dirname, 'turso.linux-x64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.linux-x64-musl.node");
|
||||
nativeBinding = require('./turso.linux-x64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-linux-x64-musl");
|
||||
nativeBinding = require('@tursodatabase/turso-linux-x64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.linux-x64-gnu.node"),
|
||||
);
|
||||
join(__dirname, 'turso.linux-x64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.linux-x64-gnu.node");
|
||||
nativeBinding = require('./turso.linux-x64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-linux-x64-gnu");
|
||||
nativeBinding = require('@tursodatabase/turso-linux-x64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "arm64":
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.linux-arm64-musl.node"),
|
||||
);
|
||||
join(__dirname, 'turso.linux-arm64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.linux-arm64-musl.node");
|
||||
nativeBinding = require('./turso.linux-arm64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-linux-arm64-musl");
|
||||
nativeBinding = require('@tursodatabase/turso-linux-arm64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.linux-arm64-gnu.node"),
|
||||
);
|
||||
join(__dirname, 'turso.linux-arm64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.linux-arm64-gnu.node");
|
||||
nativeBinding = require('./turso.linux-arm64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-linux-arm64-gnu");
|
||||
nativeBinding = require('@tursodatabase/turso-linux-arm64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "arm":
|
||||
break
|
||||
case 'arm':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.linux-arm-musleabihf.node"),
|
||||
);
|
||||
join(__dirname, 'turso.linux-arm-musleabihf.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.linux-arm-musleabihf.node");
|
||||
nativeBinding = require('./turso.linux-arm-musleabihf.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-linux-arm-musleabihf");
|
||||
nativeBinding = require('@tursodatabase/turso-linux-arm-musleabihf')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.linux-arm-gnueabihf.node"),
|
||||
);
|
||||
join(__dirname, 'turso.linux-arm-gnueabihf.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.linux-arm-gnueabihf.node");
|
||||
nativeBinding = require('./turso.linux-arm-gnueabihf.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-linux-arm-gnueabihf");
|
||||
nativeBinding = require('@tursodatabase/turso-linux-arm-gnueabihf')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "riscv64":
|
||||
break
|
||||
case 'riscv64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.linux-riscv64-musl.node"),
|
||||
);
|
||||
join(__dirname, 'turso.linux-riscv64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.linux-riscv64-musl.node");
|
||||
nativeBinding = require('./turso.linux-riscv64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-linux-riscv64-musl");
|
||||
nativeBinding = require('@tursodatabase/turso-linux-riscv64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.linux-riscv64-gnu.node"),
|
||||
);
|
||||
join(__dirname, 'turso.linux-riscv64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.linux-riscv64-gnu.node");
|
||||
nativeBinding = require('./turso.linux-riscv64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-linux-riscv64-gnu");
|
||||
nativeBinding = require('@tursodatabase/turso-linux-riscv64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "s390x":
|
||||
break
|
||||
case 's390x':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, "turso.linux-s390x-gnu.node"),
|
||||
);
|
||||
join(__dirname, 'turso.linux-s390x-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require("./turso.linux-s390x-gnu.node");
|
||||
nativeBinding = require('./turso.linux-s390x-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require("@tursodatabase/turso-linux-s390x-gnu");
|
||||
nativeBinding = require('@tursodatabase/turso-linux-s390x-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e;
|
||||
loadError = e
|
||||
}
|
||||
break;
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`);
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
||||
}
|
||||
break;
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`);
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
||||
}
|
||||
|
||||
if (!nativeBinding) {
|
||||
if (loadError) {
|
||||
throw loadError;
|
||||
throw loadError
|
||||
}
|
||||
throw new Error(`Failed to load native binding`);
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { Database, Statement } = nativeBinding;
|
||||
const { Database, Statement, IteratorStatement } = nativeBinding
|
||||
|
||||
module.exports.Database = Database;
|
||||
module.exports.Statement = Statement;
|
||||
module.exports.Database = Database
|
||||
module.exports.Statement = Statement
|
||||
module.exports.IteratorStatement = IteratorStatement
|
||||
|
||||
22
bindings/javascript/sqlite-error.js
Normal file
22
bindings/javascript/sqlite-error.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
const descriptor = { value: 'SqliteError', writable: true, enumerable: false, configurable: true };
|
||||
|
||||
function SqliteError(message, code, rawCode) {
|
||||
if (new.target !== SqliteError) {
|
||||
return new SqliteError(message, code);
|
||||
}
|
||||
if (typeof code !== 'string') {
|
||||
throw new TypeError('Expected second argument to be a string');
|
||||
}
|
||||
Error.call(this, message);
|
||||
descriptor.value = '' + message;
|
||||
Object.defineProperty(this, 'message', descriptor);
|
||||
Error.captureStackTrace(this, SqliteError);
|
||||
this.code = code;
|
||||
this.rawCode = rawCode
|
||||
}
|
||||
Object.setPrototypeOf(SqliteError, Error);
|
||||
Object.setPrototypeOf(SqliteError.prototype, Error.prototype);
|
||||
Object.defineProperty(SqliteError.prototype, 'name', descriptor);
|
||||
module.exports = SqliteError;
|
||||
|
||||
@@ -14,12 +14,18 @@ use turso_core::{LimboError, StepResult};
|
||||
#[derive(Default)]
|
||||
#[napi(object)]
|
||||
pub struct OpenDatabaseOptions {
|
||||
pub readonly: bool,
|
||||
pub file_must_exist: bool,
|
||||
pub timeout: u32,
|
||||
pub readonly: Option<bool>,
|
||||
pub file_must_exist: Option<bool>,
|
||||
pub timeout: Option<u32>,
|
||||
// verbose => Callback,
|
||||
}
|
||||
|
||||
impl OpenDatabaseOptions {
|
||||
fn readonly(&self) -> bool {
|
||||
self.readonly.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct PragmaOptions {
|
||||
pub simple: bool,
|
||||
@@ -55,28 +61,30 @@ impl ObjectFinalize for Database {
|
||||
#[napi]
|
||||
impl Database {
|
||||
#[napi(constructor)]
|
||||
pub fn new(path: String, options: Option<OpenDatabaseOptions>) -> napi::Result<Self> {
|
||||
pub fn new(path: String, options: Option<OpenDatabaseOptions>) -> napi::Result<Self, String> {
|
||||
let memory = path == ":memory:";
|
||||
let io: Arc<dyn turso_core::IO> = if memory {
|
||||
Arc::new(turso_core::MemoryIO::new())
|
||||
} else {
|
||||
Arc::new(turso_core::PlatformIO::new().map_err(into_napi_error)?)
|
||||
Arc::new(turso_core::PlatformIO::new().map_err(into_napi_sqlite_error)?)
|
||||
};
|
||||
let opts = options.unwrap_or_default();
|
||||
let flag = if opts.readonly {
|
||||
let flag = if opts.readonly() {
|
||||
turso_core::OpenFlags::ReadOnly
|
||||
} else {
|
||||
turso_core::OpenFlags::Create
|
||||
};
|
||||
let file = io.open_file(&path, flag, false).map_err(into_napi_error)?;
|
||||
let file = io
|
||||
.open_file(&path, flag, false)
|
||||
.map_err(|err| into_napi_error_with_message("SQLITE_CANTOPEN".to_owned(), err))?;
|
||||
|
||||
let db_file = Arc::new(DatabaseFile::new(file));
|
||||
let db = turso_core::Database::open(io.clone(), &path, db_file, false, false)
|
||||
.map_err(into_napi_error)?;
|
||||
let conn = db.connect().map_err(into_napi_error)?;
|
||||
.map_err(into_napi_sqlite_error)?;
|
||||
let conn = db.connect().map_err(into_napi_sqlite_error)?;
|
||||
|
||||
Ok(Self {
|
||||
readonly: opts.readonly,
|
||||
readonly: opts.readonly(),
|
||||
memory,
|
||||
_db: db,
|
||||
conn,
|
||||
@@ -131,16 +139,6 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn readonly(&self) -> bool {
|
||||
self.readonly
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn open(&self) -> bool {
|
||||
self.open
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn backup(&self) {
|
||||
todo!()
|
||||
@@ -176,7 +174,7 @@ impl Database {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn exec(&self, sql: String) -> napi::Result<()> {
|
||||
pub fn exec(&self, sql: String) -> napi::Result<(), String> {
|
||||
let query_runner = self.conn.query_runner(sql.as_bytes());
|
||||
|
||||
// Since exec doesn't return any values, we can just iterate over the results
|
||||
@@ -185,17 +183,17 @@ impl Database {
|
||||
Ok(Some(mut stmt)) => loop {
|
||||
match stmt.step() {
|
||||
Ok(StepResult::Row) => continue,
|
||||
Ok(StepResult::IO) => stmt.run_once().map_err(into_napi_error)?,
|
||||
Ok(StepResult::IO) => stmt.run_once().map_err(into_napi_sqlite_error)?,
|
||||
Ok(StepResult::Done) => break,
|
||||
Ok(StepResult::Interrupt | StepResult::Busy) => {
|
||||
return Err(napi::Error::new(
|
||||
napi::Status::GenericFailure,
|
||||
"SQLITE_ERROR".to_owned(),
|
||||
"Statement execution interrupted or busy".to_string(),
|
||||
));
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(napi::Error::new(
|
||||
napi::Status::GenericFailure,
|
||||
"SQLITE_ERROR".to_owned(),
|
||||
format!("Error executing SQL: {}", err),
|
||||
));
|
||||
}
|
||||
@@ -204,7 +202,7 @@ impl Database {
|
||||
Ok(None) => continue,
|
||||
Err(err) => {
|
||||
return Err(napi::Error::new(
|
||||
napi::Status::GenericFailure,
|
||||
"SQLITE_ERROR".to_owned(),
|
||||
format!("Error executing SQL: {}", err),
|
||||
));
|
||||
}
|
||||
@@ -263,7 +261,7 @@ impl Statement {
|
||||
|
||||
#[napi]
|
||||
pub fn get(&self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<JsUnknown> {
|
||||
let mut stmt = self.check_and_bind(args)?;
|
||||
let mut stmt = self.check_and_bind(env, args)?;
|
||||
|
||||
loop {
|
||||
let step = stmt.step().map_err(into_napi_error)?;
|
||||
@@ -324,7 +322,7 @@ impl Statement {
|
||||
// TODO: Return Info object (https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#runbindparameters---object)
|
||||
#[napi]
|
||||
pub fn run(&self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<JsUnknown> {
|
||||
let stmt = self.check_and_bind(args)?;
|
||||
let stmt = self.check_and_bind(env, args)?;
|
||||
|
||||
self.internal_all(env, stmt)
|
||||
}
|
||||
@@ -335,7 +333,12 @@ impl Statement {
|
||||
env: Env,
|
||||
args: Option<Vec<JsUnknown>>,
|
||||
) -> napi::Result<IteratorStatement> {
|
||||
self.check_and_bind(args)?;
|
||||
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(),
|
||||
@@ -346,7 +349,7 @@ impl Statement {
|
||||
|
||||
#[napi]
|
||||
pub fn all(&self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<JsUnknown> {
|
||||
let stmt = self.check_and_bind(args)?;
|
||||
let stmt = self.check_and_bind(env, args)?;
|
||||
|
||||
self.internal_all(env, stmt)
|
||||
}
|
||||
@@ -444,8 +447,9 @@ impl Statement {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn bind(&mut self, args: Option<Vec<JsUnknown>>) -> napi::Result<Self> {
|
||||
self.check_and_bind(args)?;
|
||||
pub fn bind(&mut self, env: Env, args: Option<Vec<JsUnknown>>) -> napi::Result<Self, String> {
|
||||
self.check_and_bind(env, args)
|
||||
.map_err(with_sqlite_error_message)?;
|
||||
self.binded = true;
|
||||
|
||||
Ok(self.clone())
|
||||
@@ -455,16 +459,22 @@ impl Statement {
|
||||
/// and bind values do variables. The expected type for args is `Option<Vec<JsUnknown>>`
|
||||
fn check_and_bind(
|
||||
&self,
|
||||
env: Env,
|
||||
args: Option<Vec<JsUnknown>>,
|
||||
) -> napi::Result<RefMut<'_, turso_core::Statement>> {
|
||||
let mut stmt = self.inner.borrow_mut();
|
||||
stmt.reset();
|
||||
if let Some(args) = args {
|
||||
if self.binded {
|
||||
return Err(napi::Error::new(
|
||||
napi::Status::InvalidArg,
|
||||
"This statement already has bound parameters",
|
||||
));
|
||||
let err = napi::Error::new(
|
||||
into_convertible_type_error_message("TypeError"),
|
||||
"The bind() method can only be invoked once per statement object",
|
||||
);
|
||||
unsafe {
|
||||
napi::JsTypeError::from(err).throw_into(env.raw());
|
||||
}
|
||||
|
||||
return Err(napi::Error::from_status(napi::Status::PendingException));
|
||||
}
|
||||
|
||||
for (i, elem) in args.into_iter().enumerate() {
|
||||
@@ -630,6 +640,29 @@ impl turso_core::DatabaseStorage for DatabaseFile {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_napi_error(limbo_error: LimboError) -> napi::Error {
|
||||
fn into_napi_error(limbo_error: LimboError) -> napi::Error {
|
||||
napi::Error::new(napi::Status::GenericFailure, format!("{limbo_error}"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_napi_sqlite_error(limbo_error: LimboError) -> napi::Error<String> {
|
||||
napi::Error::new(String::from("SQLITE_ERROR"), format!("{limbo_error}"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_napi_error_with_message(
|
||||
error_code: String,
|
||||
limbo_error: LimboError,
|
||||
) -> napi::Error<String> {
|
||||
napi::Error::new(error_code, format!("{limbo_error}"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_sqlite_error_message(err: napi::Error) -> napi::Error<String> {
|
||||
napi::Error::new("SQLITE_ERROR".to_owned(), err.reason)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_convertible_type_error_message(error_type: &str) -> String {
|
||||
"[TURSO_CONVERT_TYPE]".to_owned() + error_type
|
||||
}
|
||||
|
||||
@@ -2,6 +2,28 @@
|
||||
|
||||
const { Database: NativeDB } = require("./index.js");
|
||||
|
||||
const SqliteError = require("./sqlite-error.js");
|
||||
|
||||
const convertibleErrorTypes = { TypeError };
|
||||
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);
|
||||
}
|
||||
|
||||
return new SqliteError(err.message, err.code, err.rawCode);
|
||||
}
|
||||
|
||||
function createErrorByName(name, message) {
|
||||
const ErrorConstructor = convertibleErrorTypes[name];
|
||||
if (!ErrorConstructor) {
|
||||
throw new Error(`unknown error type ${name} from Turso`);
|
||||
}
|
||||
|
||||
return new ErrorConstructor(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Database represents a connection that can prepare and execute SQL statements.
|
||||
*/
|
||||
@@ -145,7 +167,11 @@ class Database {
|
||||
* @param {string} sql - The SQL statement string to execute.
|
||||
*/
|
||||
exec(sql) {
|
||||
this.db.exec(sql);
|
||||
try {
|
||||
this.db.exec(sql);
|
||||
} catch (err) {
|
||||
throw convertError(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,8 +290,13 @@ class Statement {
|
||||
* @returns this - Statement with binded parameters
|
||||
*/
|
||||
bind(...bindParameters) {
|
||||
return this.stmt.bind(bindParameters.flat());
|
||||
try {
|
||||
return new Statement(this.stmt.bind(bindParameters.flat()), this.db);
|
||||
} catch (err) {
|
||||
throw convertError(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Database;
|
||||
module.exports.SqliteError = SqliteError;
|
||||
|
||||
Reference in New Issue
Block a user