@dolthub/doltlite 0.10.6 → 0.11.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/binding.gyp +4 -4
- package/index.d.ts +11 -3
- package/index.js +8 -2
- package/package.json +8 -2
- package/scripts/build-amalgamation.js +84 -0
- package/scripts/download.js +22 -8
- package/scripts/install.js +29 -0
- package/src/bun_compat.cpp +1 -1
- package/src/database.cpp +21 -2
- package/src/database.h +2 -0
- package/src/statement.cpp +43 -10
- package/src/statement.h +8 -0
- package/src/util.h +37 -14
- package/prebuilds/darwin-arm64/doltlite +0 -0
- package/prebuilds/darwin-arm64/doltlite.node +0 -0
- package/prebuilds/darwin-x64/doltlite.node +0 -0
- package/prebuilds/linux-arm64/doltlite.node +0 -0
- package/prebuilds/linux-x64/doltlite +0 -0
- package/prebuilds/linux-x64/doltlite.node +0 -0
- package/prebuilds/win32-x64/doltlite.exe +0 -0
- package/prebuilds/win32-x64/doltlite.node +0 -0
package/README.md
CHANGED
|
@@ -173,7 +173,7 @@ bun test # run the test suite
|
|
|
173
173
|
|
|
174
174
|
## Versioning
|
|
175
175
|
|
|
176
|
-
The package version tracks the DoltLite version. `@dolthub/doltlite@0.
|
|
176
|
+
The package version tracks the DoltLite version. `@dolthub/doltlite@0.11.8` ships the DoltLite 0.11.8 amalgamation.
|
|
177
177
|
|
|
178
178
|
## License
|
|
179
179
|
|
package/binding.gyp
CHANGED
|
@@ -6,9 +6,6 @@
|
|
|
6
6
|
"amalgamation/doltlite_orig.c",
|
|
7
7
|
"amalgamation/doltlite.c",
|
|
8
8
|
"amalgamation/blake3_sse2_impl.c",
|
|
9
|
-
"amalgamation/blake3_sse41_impl.c",
|
|
10
|
-
"amalgamation/blake3_avx2_impl.c",
|
|
11
|
-
"amalgamation/blake3_avx512_impl.c",
|
|
12
9
|
"amalgamation/blake3_neon_impl.c",
|
|
13
10
|
"src/addon.cpp",
|
|
14
11
|
"src/bun_compat.cpp",
|
|
@@ -26,7 +23,10 @@
|
|
|
26
23
|
"SQLITE_ENABLE_JSON1",
|
|
27
24
|
"SQLITE_ENABLE_RTREE",
|
|
28
25
|
"SQLITE_ENABLE_COLUMN_METADATA",
|
|
29
|
-
"DOLTLITE_PROLLY=1"
|
|
26
|
+
"DOLTLITE_PROLLY=1",
|
|
27
|
+
"BLAKE3_NO_SSE41=1",
|
|
28
|
+
"BLAKE3_NO_AVX2=1",
|
|
29
|
+
"BLAKE3_NO_AVX512=1"
|
|
30
30
|
],
|
|
31
31
|
"cflags": ["-std=c11", "-fvisibility=hidden"],
|
|
32
32
|
"cflags_cc": ["-std=c++17", "-fvisibility=hidden"],
|
package/index.d.ts
CHANGED
|
@@ -85,13 +85,21 @@ export class StatementSync {
|
|
|
85
85
|
/** Execute the statement and return change metadata. */
|
|
86
86
|
run(...params: unknown[]): RunResult
|
|
87
87
|
/** Return the first result row, or undefined. */
|
|
88
|
-
get(...params: unknown[]): Record<string, unknown> | undefined
|
|
88
|
+
get(...params: unknown[]): Record<string, unknown> | unknown[] | undefined
|
|
89
89
|
/** Return all result rows. */
|
|
90
|
-
all(...params: unknown[]): Record<string, unknown>[]
|
|
90
|
+
all(...params: unknown[]): Array<Record<string, unknown> | unknown[]>
|
|
91
91
|
/** Return an iterator over result rows. */
|
|
92
|
-
iterate(...params: unknown[]): IterableIterator<Record<string, unknown
|
|
92
|
+
iterate(...params: unknown[]): IterableIterator<Record<string, unknown> | unknown[]>
|
|
93
93
|
/** Return column metadata for the statement. */
|
|
94
94
|
columns(): ColumnInfo[]
|
|
95
|
+
/** Return result rows as arrays instead of objects. */
|
|
96
|
+
setReturnArrays(enabled: boolean): void
|
|
97
|
+
/** Return integer columns as BigInt values. */
|
|
98
|
+
setReadBigInts(enabled: boolean): void
|
|
99
|
+
/** Allow bare object keys for named parameters. */
|
|
100
|
+
setAllowBareNamedParameters(enabled: boolean): void
|
|
101
|
+
/** Ignore unknown named parameter keys. */
|
|
102
|
+
setAllowUnknownNamedParameters(enabled: boolean): void
|
|
95
103
|
/** The SQL source text of the statement. */
|
|
96
104
|
readonly sourceSQL: string
|
|
97
105
|
/** The SQL text with bound parameters expanded. */
|
package/index.js
CHANGED
|
@@ -10,15 +10,21 @@ function loadAddon() {
|
|
|
10
10
|
const arch = process.arch
|
|
11
11
|
const prebuilt = path.join(__dirname, "prebuilds", `${platform}-${arch}`, "doltlite.node")
|
|
12
12
|
const compiled = path.join(__dirname, "build", "Release", "doltlite.node")
|
|
13
|
+
const compiledObjTarget = path.join(__dirname, "build", "Release", "obj.target", "doltlite.node")
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
const errors = []
|
|
16
|
+
for (const candidate of [prebuilt, compiled, compiledObjTarget]) {
|
|
17
|
+
if (!fs.existsSync(candidate)) continue
|
|
15
18
|
try {
|
|
16
19
|
return require(candidate)
|
|
17
|
-
} catch {
|
|
20
|
+
} catch (err) {
|
|
21
|
+
errors.push(`${candidate}: ${err && err.message ? err.message : err}`)
|
|
22
|
+
}
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
throw new Error(
|
|
21
26
|
`@dolthub/doltlite: no native binary found for ${platform}-${arch}.\n` +
|
|
27
|
+
(errors.length ? `Tried:\n${errors.join("\n")}\n` : "") +
|
|
22
28
|
`Run \`npm install\` to build from source, or file an issue at ` +
|
|
23
29
|
`https://github.com/dolthub/doltlite-node/issues`
|
|
24
30
|
)
|
package/package.json
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dolthub/doltlite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.8",
|
|
4
4
|
"description": "Node.js bindings for DoltLite — a SQLite fork with Git-style version control",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "index.d.ts",
|
|
8
8
|
"type": "commonjs",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public",
|
|
11
|
+
"registry": "https://registry.npmjs.org/"
|
|
12
|
+
},
|
|
9
13
|
"files": [
|
|
10
14
|
"index.js",
|
|
11
15
|
"index.d.ts",
|
|
12
16
|
"src/",
|
|
13
17
|
"binding.gyp",
|
|
18
|
+
"scripts/install.js",
|
|
14
19
|
"scripts/download.js",
|
|
20
|
+
"scripts/build-amalgamation.js",
|
|
15
21
|
"prebuilds/"
|
|
16
22
|
],
|
|
17
23
|
"scripts": {
|
|
18
|
-
"install": "node scripts/
|
|
24
|
+
"install": "node scripts/install.js",
|
|
19
25
|
"build": "node-gyp rebuild",
|
|
20
26
|
"build:debug": "node-gyp rebuild --debug",
|
|
21
27
|
"test": "bun test test/",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Prepares the DoltLite release amalgamation for the native Node addon.
|
|
3
|
+
|
|
4
|
+
"use strict"
|
|
5
|
+
|
|
6
|
+
const fs = require("fs")
|
|
7
|
+
const path = require("path")
|
|
8
|
+
const pkg = require("../package.json")
|
|
9
|
+
|
|
10
|
+
const outDir = path.join(__dirname, "../amalgamation")
|
|
11
|
+
const srcRoot = path.join(outDir, ".source")
|
|
12
|
+
const srcDir = path.join(srcRoot, "src")
|
|
13
|
+
const blake3Dir = path.join(srcRoot, "ext", "blake3")
|
|
14
|
+
|
|
15
|
+
const inC = path.join(srcRoot, "sqlite3.c")
|
|
16
|
+
const inH = path.join(srcRoot, "sqlite3.h")
|
|
17
|
+
const outC = path.join(outDir, "doltlite.c")
|
|
18
|
+
const outH = path.join(outDir, "doltlite.h")
|
|
19
|
+
const outOrig = path.join(outDir, "doltlite_orig.c")
|
|
20
|
+
const versionMarker = path.join(outDir, ".doltlite-source-version")
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(inC)) {
|
|
23
|
+
console.error(`build-amalgamation: ${inC} not found - run download.js first`)
|
|
24
|
+
process.exit(1)
|
|
25
|
+
}
|
|
26
|
+
if (!fs.existsSync(inH)) {
|
|
27
|
+
console.error(`build-amalgamation: ${inH} not found - run download.js first`)
|
|
28
|
+
process.exit(1)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log("Preparing released doltlite amalgamation...")
|
|
32
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
33
|
+
let amalgamation = fs.readFileSync(inC, "utf8")
|
|
34
|
+
amalgamation += `
|
|
35
|
+
|
|
36
|
+
#if defined(DOLTLITE_PROLLY) && defined(SQLITE_USE_SEH) && !defined(SQLITE_OMIT_WAL)
|
|
37
|
+
SQLITE_PRIVATE int sqlite3PagerWalSystemErrno(Pager *pPager){
|
|
38
|
+
(void)pPager;
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
#endif
|
|
42
|
+
`
|
|
43
|
+
fs.writeFileSync(outC, amalgamation)
|
|
44
|
+
fs.copyFileSync(inH, outH)
|
|
45
|
+
fs.writeFileSync(versionMarker, `${pkg.version}\n`)
|
|
46
|
+
|
|
47
|
+
// Kept for binding.gyp compatibility. DoltLite release amalgamations are now
|
|
48
|
+
// self-contained and include the prefixed original SQLite implementation.
|
|
49
|
+
fs.writeFileSync(outOrig, [
|
|
50
|
+
"/* Generated by build-amalgamation.js - intentionally empty.",
|
|
51
|
+
"** The released doltlite.c amalgamation is self-contained. */",
|
|
52
|
+
"",
|
|
53
|
+
].join("\n"))
|
|
54
|
+
|
|
55
|
+
// Copy headers included by addon sources and by the BLAKE3 SIMD wrappers.
|
|
56
|
+
for (const hdrDir of [srcRoot, srcDir, blake3Dir]) {
|
|
57
|
+
if (!fs.existsSync(hdrDir)) continue
|
|
58
|
+
for (const f of fs.readdirSync(hdrDir)) {
|
|
59
|
+
if (f.endsWith(".h")) {
|
|
60
|
+
fs.copyFileSync(path.join(hdrDir, f), path.join(outDir, f))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const simdWrappers = [
|
|
66
|
+
["blake3_sse2_impl.c", "blake3_sse2.c", "defined(__x86_64__)||defined(_M_X64)||defined(__i386__)"],
|
|
67
|
+
["blake3_neon_impl.c", "blake3_neon.c", "defined(__aarch64__)||defined(__arm__)"],
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
for (const [wrapName, srcName, archGuard] of simdWrappers) {
|
|
71
|
+
const srcPath = path.join(blake3Dir, srcName)
|
|
72
|
+
if (!fs.existsSync(srcPath)) continue
|
|
73
|
+
const relSrc = path.relative(outDir, srcPath).replace(/\\/g, "/")
|
|
74
|
+
fs.writeFileSync(path.join(outDir, wrapName), [
|
|
75
|
+
"/* Generated by build-amalgamation.js - DO NOT EDIT */",
|
|
76
|
+
`#if ${archGuard}`,
|
|
77
|
+
`#include "${relSrc}"`,
|
|
78
|
+
"#endif",
|
|
79
|
+
"",
|
|
80
|
+
].join("\n"))
|
|
81
|
+
console.log(`SIMD wrapper ${wrapName} written`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(`Amalgamation written to ${outC}`)
|
package/scripts/download.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Downloads the doltlite autoconf source tarball from GitHub releases
|
|
3
|
-
//
|
|
2
|
+
// Downloads the doltlite autoconf source tarball from GitHub releases,
|
|
3
|
+
// prepares its released amalgamation for node-gyp, and bundles the matching doltlite
|
|
4
4
|
// CLI binary for the current platform into prebuilds/${platform}-${arch}/
|
|
5
5
|
// so that binPath() can return an absolute path.
|
|
6
6
|
//
|
|
7
|
-
// The autoconf tarball contains
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// when compiled with -DDOLTLITE_PROLLY=1, has full version-control support.
|
|
7
|
+
// The autoconf tarball contains a self-contained sqlite3.c with DoltLite's
|
|
8
|
+
// prolly tree and SQL function sources already included. We copy it to
|
|
9
|
+
// amalgamation/doltlite.c so the addon builds the exact released source.
|
|
11
10
|
|
|
12
11
|
"use strict"
|
|
13
12
|
|
|
@@ -22,7 +21,22 @@ const version = pkg.version
|
|
|
22
21
|
const amalgDir = path.join(__dirname, "../amalgamation")
|
|
23
22
|
const outC = path.join(amalgDir, "doltlite.c")
|
|
24
23
|
const outH = path.join(amalgDir, "doltlite.h")
|
|
25
|
-
const
|
|
24
|
+
const versionMarker = path.join(amalgDir, ".doltlite-source-version")
|
|
25
|
+
const srcRoot = path.join(amalgDir, ".source")
|
|
26
|
+
|
|
27
|
+
function normalizeVersion(v) {
|
|
28
|
+
return String(v || "").trim().replace(/^v/, "")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sourceReady() {
|
|
32
|
+
if (!fs.existsSync(outC) || !fs.existsSync(outH) || !fs.existsSync(versionMarker)) return false
|
|
33
|
+
try {
|
|
34
|
+
const marker = fs.readFileSync(versionMarker, "utf8")
|
|
35
|
+
return normalizeVersion(marker) === normalizeVersion(version)
|
|
36
|
+
} catch (_) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
26
40
|
|
|
27
41
|
// Upstream dolthub/doltlite ships the CLI for these platform-arch pairs;
|
|
28
42
|
// place the binary next to the .node addon so binPath() can return it.
|
|
@@ -51,7 +65,7 @@ function download(url, dest, cb) {
|
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
function fetchSource(cb) {
|
|
54
|
-
if (
|
|
68
|
+
if (sourceReady()) return cb()
|
|
55
69
|
|
|
56
70
|
const tarballUrl = `https://github.com/dolthub/doltlite/releases/download/v${version}/doltlite-autoconf-${version}.tar.gz`
|
|
57
71
|
const tarballPath = path.join(__dirname, `../doltlite-autoconf-${version}.tar.gz`)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Uses a packaged native prebuild when available, otherwise prepares source
|
|
3
|
+
// and builds the addon locally.
|
|
4
|
+
|
|
5
|
+
"use strict"
|
|
6
|
+
|
|
7
|
+
const fs = require("fs")
|
|
8
|
+
const path = require("path")
|
|
9
|
+
const { spawnSync } = require("child_process")
|
|
10
|
+
|
|
11
|
+
const root = path.join(__dirname, "..")
|
|
12
|
+
const prebuilt = path.join(root, "prebuilds", `${process.platform}-${process.arch}`, "doltlite.node")
|
|
13
|
+
|
|
14
|
+
function run(command, args) {
|
|
15
|
+
const result = spawnSync(command, args, { cwd: root, stdio: "inherit", shell: process.platform === "win32" })
|
|
16
|
+
if (result.error) {
|
|
17
|
+
console.error(result.error.message)
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
if (result.status !== 0) process.exit(result.status || 1)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(prebuilt)) {
|
|
24
|
+
console.log(`@dolthub/doltlite: using prebuilt addon ${path.relative(root, prebuilt)}`)
|
|
25
|
+
process.exit(0)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
run(process.execPath, [path.join(__dirname, "download.js")])
|
|
29
|
+
run("node-gyp", ["rebuild"])
|
package/src/bun_compat.cpp
CHANGED
|
@@ -10,7 +10,7 @@ extern "C" napi_value _doltlite_init(napi_env env, napi_value exports);
|
|
|
10
10
|
// this weak definition. MSVC doesn't support weak functions; skip it there
|
|
11
11
|
// since Windows builds target Node 22+ which supplies the symbol via NAPI_MODULE.
|
|
12
12
|
#if !defined(_MSC_VER)
|
|
13
|
-
extern "C" __attribute__((weak)) napi_value napi_register_module_v1(napi_env env, napi_value exports) {
|
|
13
|
+
extern "C" __attribute__((weak, visibility("default"))) napi_value napi_register_module_v1(napi_env env, napi_value exports) {
|
|
14
14
|
return _doltlite_init(env, exports);
|
|
15
15
|
}
|
|
16
16
|
#endif
|
package/src/database.cpp
CHANGED
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
#include <vector>
|
|
5
5
|
#include <string>
|
|
6
6
|
|
|
7
|
+
static std::string SqliteOpenPath(const std::string& path) {
|
|
8
|
+
#ifdef _WIN32
|
|
9
|
+
if (path != ":memory:" && path.rfind("file:", 0) != 0) {
|
|
10
|
+
std::string normalized = path;
|
|
11
|
+
for (char& c : normalized) {
|
|
12
|
+
if (c == '\\') c = '/';
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
}
|
|
16
|
+
#endif
|
|
17
|
+
return path;
|
|
18
|
+
}
|
|
19
|
+
|
|
7
20
|
// ── Constructor ──────────────────────────────────────────────────────────────
|
|
8
21
|
|
|
9
22
|
Database::Database(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Database>(info) {
|
|
@@ -32,13 +45,15 @@ Database::Database(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Database>(
|
|
|
32
45
|
: (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
|
|
33
46
|
flags |= SQLITE_OPEN_URI;
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
std::string openPath = SqliteOpenPath(path);
|
|
49
|
+
int rc = sqlite3_open_v2(openPath.c_str(), &db_, flags, nullptr);
|
|
36
50
|
if (rc != SQLITE_OK) {
|
|
37
51
|
std::string msg = db_ ? sqlite3_errmsg(db_) : "Failed to open database";
|
|
38
52
|
if (db_) { sqlite3_close(db_); db_ = nullptr; }
|
|
39
53
|
Napi::Error::New(env, msg).ThrowAsJavaScriptException();
|
|
40
54
|
return;
|
|
41
55
|
}
|
|
56
|
+
if (path != ":memory:") path_ = path;
|
|
42
57
|
|
|
43
58
|
sqlite3_busy_timeout(db_, 5000);
|
|
44
59
|
sqlite3_exec(db_, "PRAGMA foreign_keys = ON", nullptr, nullptr, nullptr);
|
|
@@ -65,7 +80,8 @@ Napi::Value Database::Open(const Napi::CallbackInfo& info) {
|
|
|
65
80
|
return env.Undefined();
|
|
66
81
|
}
|
|
67
82
|
std::string path = info[0].As<Napi::String>().Utf8Value();
|
|
68
|
-
|
|
83
|
+
std::string openPath = SqliteOpenPath(path);
|
|
84
|
+
int rc = sqlite3_open_v2(openPath.c_str(), &db_,
|
|
69
85
|
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
|
|
70
86
|
nullptr);
|
|
71
87
|
if (rc != SQLITE_OK) {
|
|
@@ -73,11 +89,13 @@ Napi::Value Database::Open(const Napi::CallbackInfo& info) {
|
|
|
73
89
|
if (db_) { sqlite3_close(db_); db_ = nullptr; }
|
|
74
90
|
Napi::Error::New(env, msg).ThrowAsJavaScriptException();
|
|
75
91
|
}
|
|
92
|
+
if (rc == SQLITE_OK && path != ":memory:") path_ = path;
|
|
76
93
|
return env.Undefined();
|
|
77
94
|
}
|
|
78
95
|
|
|
79
96
|
Napi::Value Database::Close(const Napi::CallbackInfo& info) {
|
|
80
97
|
if (db_) { sqlite3_close_v2(db_); db_ = nullptr; }
|
|
98
|
+
path_.clear();
|
|
81
99
|
return info.Env().Undefined();
|
|
82
100
|
}
|
|
83
101
|
|
|
@@ -124,6 +142,7 @@ Napi::Value Database::IsTransactionGetter(const Napi::CallbackInfo& info) {
|
|
|
124
142
|
Napi::Value Database::Location(const Napi::CallbackInfo& info) {
|
|
125
143
|
Napi::Env env = info.Env();
|
|
126
144
|
if (!db_) return env.Null();
|
|
145
|
+
if (!path_.empty()) return Napi::String::New(env, path_);
|
|
127
146
|
const char* loc = sqlite3_db_filename(db_, "main");
|
|
128
147
|
if (!loc || loc[0] == '\0' || strcmp(loc, ":memory:") == 0) return env.Null();
|
|
129
148
|
return Napi::String::New(env, loc);
|
package/src/database.h
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
#include <napi.h>
|
|
3
3
|
#include "doltlite.h"
|
|
4
|
+
#include <string>
|
|
4
5
|
|
|
5
6
|
class Database : public Napi::ObjectWrap<Database> {
|
|
6
7
|
public:
|
|
@@ -13,6 +14,7 @@ public:
|
|
|
13
14
|
|
|
14
15
|
private:
|
|
15
16
|
sqlite3* db_ = nullptr;
|
|
17
|
+
std::string path_;
|
|
16
18
|
|
|
17
19
|
// node:sqlite-compatible methods
|
|
18
20
|
Napi::Value Exec(const Napi::CallbackInfo& info);
|
package/src/statement.cpp
CHANGED
|
@@ -39,7 +39,8 @@ Statement::~Statement() {
|
|
|
39
39
|
Napi::Value Statement::Run(const Napi::CallbackInfo& info) {
|
|
40
40
|
Napi::Env env = info.Env();
|
|
41
41
|
if (!stmt_) { Napi::Error::New(env, "Statement is finalised").ThrowAsJavaScriptException(); return env.Undefined(); }
|
|
42
|
-
if (!BindArgs(env, stmt_, info
|
|
42
|
+
if (!BindArgs(env, stmt_, info, 0, allowBareNamedParameters_,
|
|
43
|
+
allowUnknownNamedParameters_)) return env.Undefined();
|
|
43
44
|
|
|
44
45
|
int rc = sqlite3_step(stmt_);
|
|
45
46
|
sqlite3_reset(stmt_);
|
|
@@ -60,11 +61,15 @@ Napi::Value Statement::Run(const Napi::CallbackInfo& info) {
|
|
|
60
61
|
Napi::Value Statement::Get(const Napi::CallbackInfo& info) {
|
|
61
62
|
Napi::Env env = info.Env();
|
|
62
63
|
if (!stmt_) { Napi::Error::New(env, "Statement is finalised").ThrowAsJavaScriptException(); return env.Undefined(); }
|
|
63
|
-
if (!BindArgs(env, stmt_, info
|
|
64
|
+
if (!BindArgs(env, stmt_, info, 0, allowBareNamedParameters_,
|
|
65
|
+
allowUnknownNamedParameters_)) return env.Undefined();
|
|
64
66
|
|
|
65
67
|
int rc = sqlite3_step(stmt_);
|
|
66
68
|
Napi::Value result = env.Undefined();
|
|
67
|
-
if (rc == SQLITE_ROW)
|
|
69
|
+
if (rc == SQLITE_ROW) {
|
|
70
|
+
result = returnArrays_ ? (Napi::Value)RowToArray(env, stmt_, readBigInts_)
|
|
71
|
+
: (Napi::Value)RowToObject(env, stmt_, readBigInts_);
|
|
72
|
+
}
|
|
68
73
|
else if (rc != SQLITE_DONE) ThrowSQLiteError(env, db_->Handle(), "get");
|
|
69
74
|
sqlite3_reset(stmt_);
|
|
70
75
|
return result;
|
|
@@ -75,18 +80,42 @@ Napi::Value Statement::Get(const Napi::CallbackInfo& info) {
|
|
|
75
80
|
Napi::Value Statement::All(const Napi::CallbackInfo& info) {
|
|
76
81
|
Napi::Env env = info.Env();
|
|
77
82
|
if (!stmt_) { Napi::Error::New(env, "Statement is finalised").ThrowAsJavaScriptException(); return env.Undefined(); }
|
|
78
|
-
if (!BindArgs(env, stmt_, info
|
|
83
|
+
if (!BindArgs(env, stmt_, info, 0, allowBareNamedParameters_,
|
|
84
|
+
allowUnknownNamedParameters_)) return env.Undefined();
|
|
79
85
|
|
|
80
86
|
auto result = Napi::Array::New(env);
|
|
81
87
|
uint32_t idx = 0;
|
|
82
88
|
int rc;
|
|
83
89
|
while ((rc = sqlite3_step(stmt_)) == SQLITE_ROW)
|
|
84
|
-
result.Set(idx++,
|
|
90
|
+
result.Set(idx++, returnArrays_ ? (Napi::Value)RowToArray(env, stmt_, readBigInts_)
|
|
91
|
+
: (Napi::Value)RowToObject(env, stmt_, readBigInts_));
|
|
85
92
|
sqlite3_reset(stmt_);
|
|
86
93
|
if (rc != SQLITE_DONE) ThrowSQLiteError(env, db_->Handle(), "all");
|
|
87
94
|
return result;
|
|
88
95
|
}
|
|
89
96
|
|
|
97
|
+
// ── node:sqlite compatibility flags ──────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
Napi::Value Statement::SetReturnArrays(const Napi::CallbackInfo& info) {
|
|
100
|
+
returnArrays_ = info.Length() > 0 && info[0].ToBoolean().Value();
|
|
101
|
+
return info.Env().Undefined();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Napi::Value Statement::SetReadBigInts(const Napi::CallbackInfo& info) {
|
|
105
|
+
readBigInts_ = info.Length() > 0 && info[0].ToBoolean().Value();
|
|
106
|
+
return info.Env().Undefined();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
Napi::Value Statement::SetAllowBareNamedParameters(const Napi::CallbackInfo& info) {
|
|
110
|
+
allowBareNamedParameters_ = info.Length() > 0 && info[0].ToBoolean().Value();
|
|
111
|
+
return info.Env().Undefined();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Napi::Value Statement::SetAllowUnknownNamedParameters(const Napi::CallbackInfo& info) {
|
|
115
|
+
allowUnknownNamedParameters_ = info.Length() > 0 && info[0].ToBoolean().Value();
|
|
116
|
+
return info.Env().Undefined();
|
|
117
|
+
}
|
|
118
|
+
|
|
90
119
|
// ── iterate() → IterableIterator ─────────────────────────────────────────────
|
|
91
120
|
// Returns a plain JS iterator object: { next() { return {value, done} } }
|
|
92
121
|
// A full Symbol.iterator implementation requires a persistent Napi::Reference
|
|
@@ -174,11 +203,15 @@ Napi::Value Statement::ExpandedSQLGetter(const Napi::CallbackInfo& info) {
|
|
|
174
203
|
|
|
175
204
|
Napi::Object Statement::Init(Napi::Env env, Napi::Object exports) {
|
|
176
205
|
Napi::Function ctor = DefineClass(env, "StatementSync", {
|
|
177
|
-
InstanceMethod("run",
|
|
178
|
-
InstanceMethod("get",
|
|
179
|
-
InstanceMethod("all",
|
|
180
|
-
InstanceMethod("iterate",
|
|
181
|
-
InstanceMethod("columns",
|
|
206
|
+
InstanceMethod("run", &Statement::Run),
|
|
207
|
+
InstanceMethod("get", &Statement::Get),
|
|
208
|
+
InstanceMethod("all", &Statement::All),
|
|
209
|
+
InstanceMethod("iterate", &Statement::Iterate),
|
|
210
|
+
InstanceMethod("columns", &Statement::Columns),
|
|
211
|
+
InstanceMethod("setReturnArrays", &Statement::SetReturnArrays),
|
|
212
|
+
InstanceMethod("setReadBigInts", &Statement::SetReadBigInts),
|
|
213
|
+
InstanceMethod("setAllowBareNamedParameters", &Statement::SetAllowBareNamedParameters),
|
|
214
|
+
InstanceMethod("setAllowUnknownNamedParameters", &Statement::SetAllowUnknownNamedParameters),
|
|
182
215
|
InstanceAccessor("sourceSQL", &Statement::SourceSQLGetter, nullptr),
|
|
183
216
|
InstanceAccessor("expandedSQL", &Statement::ExpandedSQLGetter, nullptr),
|
|
184
217
|
});
|
package/src/statement.h
CHANGED
|
@@ -16,12 +16,20 @@ private:
|
|
|
16
16
|
sqlite3_stmt* stmt_ = nullptr;
|
|
17
17
|
Database* db_ = nullptr;
|
|
18
18
|
std::string source_;
|
|
19
|
+
bool returnArrays_ = false;
|
|
20
|
+
bool readBigInts_ = false;
|
|
21
|
+
bool allowBareNamedParameters_ = true;
|
|
22
|
+
bool allowUnknownNamedParameters_ = false;
|
|
19
23
|
|
|
20
24
|
Napi::Value Run(const Napi::CallbackInfo& info);
|
|
21
25
|
Napi::Value Get(const Napi::CallbackInfo& info);
|
|
22
26
|
Napi::Value All(const Napi::CallbackInfo& info);
|
|
23
27
|
Napi::Value Iterate(const Napi::CallbackInfo& info);
|
|
24
28
|
Napi::Value Columns(const Napi::CallbackInfo& info);
|
|
29
|
+
Napi::Value SetReturnArrays(const Napi::CallbackInfo& info);
|
|
30
|
+
Napi::Value SetReadBigInts(const Napi::CallbackInfo& info);
|
|
31
|
+
Napi::Value SetAllowBareNamedParameters(const Napi::CallbackInfo& info);
|
|
32
|
+
Napi::Value SetAllowUnknownNamedParameters(const Napi::CallbackInfo& info);
|
|
25
33
|
Napi::Value SourceSQLGetter(const Napi::CallbackInfo& info);
|
|
26
34
|
Napi::Value ExpandedSQLGetter(const Napi::CallbackInfo& info);
|
|
27
35
|
|
package/src/util.h
CHANGED
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
#include <string>
|
|
5
5
|
|
|
6
6
|
// Converts a SQLite column value to a Napi::Value.
|
|
7
|
-
inline Napi::Value ColumnToNapi(Napi::Env env, sqlite3_stmt* stmt, int col
|
|
7
|
+
inline Napi::Value ColumnToNapi(Napi::Env env, sqlite3_stmt* stmt, int col,
|
|
8
|
+
bool readBigInts = false) {
|
|
8
9
|
switch (sqlite3_column_type(stmt, col)) {
|
|
9
|
-
case SQLITE_INTEGER:
|
|
10
|
-
|
|
10
|
+
case SQLITE_INTEGER: {
|
|
11
|
+
sqlite3_int64 i = sqlite3_column_int64(stmt, col);
|
|
12
|
+
if (readBigInts) return Napi::BigInt::New(env, (int64_t)i);
|
|
13
|
+
return Napi::Number::New(env, (double)i);
|
|
14
|
+
}
|
|
11
15
|
case SQLITE_FLOAT:
|
|
12
16
|
return Napi::Number::New(env, sqlite3_column_double(stmt, col));
|
|
13
17
|
case SQLITE_TEXT: {
|
|
@@ -28,16 +32,28 @@ inline Napi::Value ColumnToNapi(Napi::Env env, sqlite3_stmt* stmt, int col) {
|
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
// Builds a JS object from the current row of a prepared statement.
|
|
31
|
-
inline Napi::Object RowToObject(Napi::Env env, sqlite3_stmt* stmt
|
|
35
|
+
inline Napi::Object RowToObject(Napi::Env env, sqlite3_stmt* stmt,
|
|
36
|
+
bool readBigInts = false) {
|
|
32
37
|
auto obj = Napi::Object::New(env);
|
|
33
38
|
int n = sqlite3_column_count(stmt);
|
|
34
39
|
for (int i = 0; i < n; i++) {
|
|
35
40
|
const char* name = sqlite3_column_name(stmt, i);
|
|
36
|
-
obj.Set(name, ColumnToNapi(env, stmt, i));
|
|
41
|
+
obj.Set(name, ColumnToNapi(env, stmt, i, readBigInts));
|
|
37
42
|
}
|
|
38
43
|
return obj;
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
// Builds a JS array from the current row of a prepared statement.
|
|
47
|
+
inline Napi::Array RowToArray(Napi::Env env, sqlite3_stmt* stmt,
|
|
48
|
+
bool readBigInts = false) {
|
|
49
|
+
int n = sqlite3_column_count(stmt);
|
|
50
|
+
auto arr = Napi::Array::New(env, n);
|
|
51
|
+
for (int i = 0; i < n; i++) {
|
|
52
|
+
arr.Set((uint32_t)i, ColumnToNapi(env, stmt, i, readBigInts));
|
|
53
|
+
}
|
|
54
|
+
return arr;
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
// Binds a JS value to a prepared statement parameter (1-based index).
|
|
42
58
|
inline bool BindNapi(Napi::Env env, sqlite3_stmt* stmt, int idx, Napi::Value val) {
|
|
43
59
|
if (val.IsNull() || val.IsUndefined()) {
|
|
@@ -74,9 +90,9 @@ inline bool BindNapi(Napi::Env env, sqlite3_stmt* stmt, int idx, Napi::Value val
|
|
|
74
90
|
// named: bind({$name: val, ...}) or bare names {name: val} if allowBare=true
|
|
75
91
|
inline bool BindArgs(Napi::Env env, sqlite3_stmt* stmt,
|
|
76
92
|
const Napi::CallbackInfo& info, size_t startIdx = 0,
|
|
77
|
-
bool allowBare = true) {
|
|
93
|
+
bool allowBare = true, bool allowUnknown = false) {
|
|
78
94
|
sqlite3_clear_bindings(stmt);
|
|
79
|
-
if (info.Length() <=
|
|
95
|
+
if (info.Length() <= startIdx) return true;
|
|
80
96
|
|
|
81
97
|
// Named parameters via a plain object
|
|
82
98
|
if (info.Length() == startIdx + 1 && info[startIdx].IsObject()
|
|
@@ -85,15 +101,22 @@ inline bool BindArgs(Napi::Env env, sqlite3_stmt* stmt,
|
|
|
85
101
|
auto keys = obj.GetPropertyNames();
|
|
86
102
|
for (uint32_t i = 0; i < keys.Length(); i++) {
|
|
87
103
|
std::string key = keys.Get(i).As<Napi::String>().Utf8Value();
|
|
88
|
-
// Try $name, :name, @name prefixes, then bare if allowBare
|
|
89
104
|
int idx = 0;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
105
|
+
if (!key.empty() && (key[0] == '$' || key[0] == ':' || key[0] == '@')) {
|
|
106
|
+
idx = sqlite3_bind_parameter_index(stmt, key.c_str());
|
|
107
|
+
} else if (allowBare) {
|
|
108
|
+
for (const char* prefix : {"$", ":", "@", ""}) {
|
|
109
|
+
std::string k = std::string(prefix) + key;
|
|
110
|
+
idx = sqlite3_bind_parameter_index(stmt, k.c_str());
|
|
111
|
+
if (idx > 0) break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (idx == 0) {
|
|
115
|
+
if (allowUnknown) continue;
|
|
116
|
+
Napi::Error::New(env, "Unknown named parameter '" + key + "'")
|
|
117
|
+
.ThrowAsJavaScriptException();
|
|
118
|
+
return false;
|
|
94
119
|
}
|
|
95
|
-
if (idx == 0 && !allowBare) continue;
|
|
96
|
-
if (idx == 0) continue;
|
|
97
120
|
if (!BindNapi(env, stmt, idx, obj.Get(keys.Get(i)))) return false;
|
|
98
121
|
}
|
|
99
122
|
return true;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|