@dolthub/doltlite 0.10.8 → 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 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.10.0` ships the DoltLite 0.10.0 amalgamation.
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/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
- for (const candidate of [prebuilt, compiled]) {
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,22 +1,27 @@
1
1
  {
2
2
  "name": "@dolthub/doltlite",
3
- "version": "0.10.8",
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",
15
20
  "scripts/build-amalgamation.js",
16
21
  "prebuilds/"
17
22
  ],
18
23
  "scripts": {
19
- "install": "node scripts/download.js && node-gyp rebuild",
24
+ "install": "node scripts/install.js",
20
25
  "build": "node-gyp rebuild",
21
26
  "build:debug": "node-gyp rebuild --debug",
22
27
  "test": "bun test test/",
@@ -1,523 +1,84 @@
1
1
  #!/usr/bin/env node
2
- // Builds a complete doltlite amalgamation from the autoconf source tree.
3
- //
4
- // Generates two C files:
5
- //
6
- // amalgamation/doltlite_orig.c — the original SQLite btree/pager/wal/backup code
7
- // compiled with btree_orig_prefix.h so every exported symbol gets an orig_
8
- // prefix. This keeps the original implementation available alongside the
9
- // prolly-tree replacements. Compiled as a separate translation unit so its
10
- // struct definitions (original BtShared, Pager, etc.) don't clash with the
11
- // prolly versions in doltlite.c.
12
- //
13
- // amalgamation/doltlite.c — the base sqlite3.c amalgamation (with btree/pager/
14
- // wal/btmutex/backup sections wrapped in #ifndef DOLTLITE_PROLLY), followed
15
- // by the blake3, prolly-tree, and doltlite SQL-function source files.
16
- //
17
- // When both files are compiled with -DDOLTLITE_PROLLY=1, the full version-
18
- // control stack is active. Without that flag doltlite.c degrades to plain SQLite
19
- // and doltlite_orig.c is a harmless dead-code object.
2
+ // Prepares the DoltLite release amalgamation for the native Node addon.
20
3
 
21
4
  "use strict"
22
5
 
23
6
  const fs = require("fs")
24
7
  const path = require("path")
25
-
26
- const { version } = require("../package.json")
27
-
28
- const srcRoot = path.join(__dirname, "../doltlite-src")
29
- const baseSqlite = path.join(srcRoot, "sqlite3.c")
30
- const srcDir = path.join(srcRoot, "src")
31
- const blake3Dir = path.join(srcRoot, "ext", "blake3")
32
- const outDir = path.join(__dirname, "../amalgamation")
33
- const outFile = path.join(outDir, "doltlite.c")
34
- const outOrig = path.join(outDir, "doltlite_orig.c")
35
- const outHeader = path.join(outDir, "doltlite.h")
36
-
37
- // Files in the sqlite3.c amalgamation whose symbols are replaced by prolly_btree.c / pager_shim.c.
38
- // They are wrapped in #ifndef DOLTLITE_PROLLY so the compiler only sees one definition.
39
- const GUARDED = ["btree.c", "pager.c", "wal.c", "btmutex.c", "backup.c"]
40
-
41
- // Source files to append to doltlite.c.
42
- // Each entry is either a filename relative to srcDir, or an absolute path.
43
- // Order matters: lower-level files before the code that calls them.
44
- const EXTRA = [
45
- // blake3 hash library (required by prolly_hash.c, lives in ext/blake3/)
46
- // blake3_dispatch.c replaced blake3_dispatch_portable.c in 0.10.6.
47
- path.join(blake3Dir, "blake3.c"),
48
- path.join(blake3Dir, "blake3_portable.c"),
49
- path.join(blake3Dir, fs.existsSync(path.join(blake3Dir, "blake3_dispatch.c"))
50
- ? "blake3_dispatch.c" : "blake3_dispatch_portable.c"),
51
- // prolly tree
52
- "prolly_hash.c",
53
- "prolly_xxhash.c",
54
- "prolly_hashset.c",
55
- "prolly_node.c",
56
- "prolly_cache.c",
57
- // chunk store subsystems (split out of chunk_store.c in 0.10.8)
58
- "chunk_file.c",
59
- "chunk_refs.c",
60
- "chunk_index.c",
61
- "chunk_staging.c",
62
- "chunk_wal.c",
63
- "chunk_store.c",
64
- "prolly_cursor.c",
65
- "prolly_mutmap.c",
66
- "prolly_chunker.c",
67
- "prolly_mutate.c",
68
- "prolly_diff.c",
69
- "prolly_three_way_diff.c",
70
- "prolly_three_way_merge.c",
71
- "prolly_btree.c",
72
- "pager_shim.c",
73
- "sortkey.c",
74
- // dolt SQL functions
75
- "doltlite.c",
76
- "doltlite_commit.c",
77
- "doltlite_ref.c",
78
- "doltlite_log.c",
79
- "doltlite_status.c",
80
- "doltlite_diff.c",
81
- "doltlite_diff_table.c",
82
- "doltlite_branch.c",
83
- "doltlite_tag.c",
84
- "doltlite_ancestor.c",
85
- "doltlite_commit_ancestors.c",
86
- "doltlite_merge.c",
87
- "doltlite_schema_merge.c",
88
- "doltlite_conflicts.c",
89
- "doltlite_gc.c",
90
- "doltlite_chunk_walk.c",
91
- "doltlite_history.c",
92
- "doltlite_at.c",
93
- "doltlite_blame.c",
94
- "doltlite_schema_diff.c",
95
- "doltlite_schemas.c",
96
- "doltlite_diff_stat.c",
97
- "doltlite_record.c",
98
- "doltlite_ignore.c",
99
- "doltlite_hashof.c",
100
- "doltlite_constraint_violations.c",
101
- "doltlite_merge_constraints.c",
102
- "doltlite_dbpage.c",
103
- "doltlite_remote.c",
104
- "doltlite_remote_sql.c",
105
- "doltlite_http_remote.c",
106
- "doltlite_remotesrv.c",
107
- ]
108
-
109
- // Per-file symbol renames to resolve conflicts when multiple source files are
110
- // merged into a single translation unit.
111
- // Each entry: { file: basename, renames: [[oldWord, newWord], ...] }
112
- // Replacements are applied as whole-word substitutions (word-boundary regex).
113
- const FILE_PATCHES = [
114
- {
115
- // prolly_btree.c defines struct TableEntry, struct SchemaEntry, and
116
- // tableEntryNameCmp/buildSchemaCatalogRecord — rename the duplicates that
117
- // appear later in doltlite_merge.c so they don't collide.
118
- file: "doltlite_merge.c",
119
- renames: [
120
- // Rename the local MergeFieldValue (used only by buildSchemaCatalogRecord)
121
- // so it doesn't clash with doltlite_merge_constraints.c's MergeFieldValue.
122
- ["mergeCatalogSerialType", "dlMergeCatalogSerialType"],
123
- ["mergeCatalogSerialPut", "dlMergeCatalogSerialPut"],
124
- ["MergeFieldValue", "DlMergeSchemaMFV"],
125
- // buildSchemaCatalogRecord is also defined in prolly_btree.c.
126
- ["buildSchemaCatalogRecord","dlMergeBuildSchemaRecord"],
127
- // ConflictRow is embedded in RowMergeCtx here; doltlite_conflicts.c also
128
- // embeds an identically-named struct → rename the merge.c version.
129
- ["ConflictRow", "DlMergeConflictRow"],
130
- // freeConflictTables has a different signature in doltlite_conflicts.c.
131
- ["freeConflictTables", "dlMergeFreeConflictTables"],
132
- ],
133
- },
134
- {
135
- // doltlite_merge_constraints.c defines its own MergeFieldValue (which has
136
- // an extra `double r` field vs the one in doltlite_merge.c).
137
- file: "doltlite_merge_constraints.c",
138
- renames: [
139
- ["mergeSerialType", "dlConstraintMergeSerialType"],
140
- ["mergeSerialPut", "dlConstraintMergeSerialPut"],
141
- ["MergeFieldValue", "DlConstraintMFV"],
142
- ],
143
- },
144
- {
145
- // writeAll is also defined (identically) in doltlite_http_remote.c which
146
- // comes earlier in the EXTRA list.
147
- file: "doltlite_remotesrv.c",
148
- renames: [
149
- ["writeAll", "srvWriteAll"],
150
- ],
151
- },
152
- ]
153
-
154
- // Per-file exact text patches (verbatim old → new, applied after renames).
155
- // Each entry: { file: basename, patches: [[oldText, newText], ...] }
156
- const FILE_TEXT_PATCHES = [
157
- {
158
- // os_win.c does not implement SQLITE_FCNTL_HAS_MOVED; sqlite3OsFileControl
159
- // returns SQLITE_NOTFOUND ("unknown operation") for that opcode.
160
- // csDetectExternalChanges previously propagated that error, so every write
161
- // transaction on a file-based database failed on Windows. Treat NOTFOUND
162
- // the same way the standard pager does: assume the file has not moved.
163
- file: "chunk_store.c",
164
- patches: [
165
- // 0.10.5 used 2-space indent; 0.10.6 uses 4-space inside the hasMovedChecked guard.
166
- [
167
- ` rc = sqlite3OsFileControl(cs->file.pFile, SQLITE_FCNTL_HAS_MOVED, &bMoved);\n if( rc!=SQLITE_OK ) return rc;\n if( bMoved ){`,
168
- ` rc = sqlite3OsFileControl(cs->file.pFile, SQLITE_FCNTL_HAS_MOVED, &bMoved);\n if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK; /* not supported by this VFS */\n if( rc!=SQLITE_OK ) return rc;\n if( bMoved ){`,
169
- ],
170
- // On Windows, csFileLock / csFileLockNB opened the database file itself
171
- // and held an exclusive byte-range lock over ALL bytes. sqlite3OsWrite
172
- // then tried to write via cs->pFile (a different HANDLE to the same path).
173
- // Windows exclusive byte-range locks block WriteFile from other handles —
174
- // even handles in the same process — causing SQLITE_IOERR_WRITE after the
175
- // winRetryIoerr retry loop. Fix: use a separate "<path>.lock" file so
176
- // the lock fd and the I/O fd never conflict.
177
- [
178
- `#ifdef _WIN32\n# include <io.h>\n# include <windows.h>\n static int csFileLock(const char *path, int *pFd){\n int fd = _open(path, _O_BINARY | _O_RDWR | _O_CREAT, 0644);\n if( fd < 0 ) return -1;\n {\n HANDLE h = (HANDLE)_get_osfhandle(fd);\n OVERLAPPED ov = {0};\n if( !LockFileEx(h, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &ov) ){\n _close(fd);\n return -1;\n }\n }\n *pFd = fd;\n return 0;\n }\n static void csFileUnlock(int fd){\n if( fd >= 0 ){\n HANDLE h = (HANDLE)_get_osfhandle(fd);\n OVERLAPPED ov = {0};\n UnlockFileEx(h, 0, MAXDWORD, MAXDWORD, &ov);\n _close(fd);\n }\n }\n static int csFileLockNB(const char *path, int *pFd){\n int fd = _open(path, _O_BINARY | _O_RDWR | _O_CREAT, 0644);\n if( fd < 0 ) return -1;\n {\n HANDLE h = (HANDLE)_get_osfhandle(fd);\n OVERLAPPED ov = {0};\n if( !LockFileEx(h, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,\n 0, MAXDWORD, MAXDWORD, &ov) ){\n _close(fd);\n return -1;\n }\n }\n *pFd = fd;\n return 0;\n }\n#else`,
179
- `#ifdef _WIN32\n# include <io.h>\n# include <windows.h>\n /* Use a separate "<path>.lock" file for byte-range locking so that\n ** LockFileEx on the lock fd does not conflict with WriteFile on\n ** cs->pFile (a different HANDLE to the same path). On Windows,\n ** exclusive byte-range locks block writes from other handles to the\n ** locked region even within the same process. */\n static int csMakeLockPath(const char *path, char **ppLock){\n int n = (int)strlen(path);\n char *p = (char *)malloc(n + 6);\n if( !p ) return -1;\n memcpy(p, path, n);\n memcpy(p + n, ".lock", 6);\n *ppLock = p;\n return 0;\n }\n static int csFileLock(const char *path, int *pFd){\n char *lockPath = 0;\n int fd;\n if( csMakeLockPath(path, &lockPath) ) return -1;\n fd = _open(lockPath, _O_BINARY | _O_RDWR | _O_CREAT, 0644);\n free(lockPath);\n if( fd < 0 ) return -1;\n {\n HANDLE h = (HANDLE)_get_osfhandle(fd);\n OVERLAPPED ov = {0};\n if( !LockFileEx(h, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &ov) ){\n _close(fd);\n return -1;\n }\n }\n *pFd = fd;\n return 0;\n }\n static void csFileUnlock(int fd){\n if( fd >= 0 ){\n HANDLE h = (HANDLE)_get_osfhandle(fd);\n OVERLAPPED ov = {0};\n UnlockFileEx(h, 0, MAXDWORD, MAXDWORD, &ov);\n _close(fd);\n }\n }\n static int csFileLockNB(const char *path, int *pFd){\n char *lockPath = 0;\n int fd;\n if( csMakeLockPath(path, &lockPath) ) return -1;\n fd = _open(lockPath, _O_BINARY | _O_RDWR | _O_CREAT, 0644);\n free(lockPath);\n if( fd < 0 ) return -1;\n {\n HANDLE h = (HANDLE)_get_osfhandle(fd);\n OVERLAPPED ov = {0};\n if( !LockFileEx(h, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,\n 0, MAXDWORD, MAXDWORD, &ov) ){\n _close(fd);\n return -1;\n }\n }\n *pFd = fd;\n return 0;\n }\n#else`,
180
- ],
181
- ],
182
- },
183
- ]
184
-
185
- // Build a map from basename → rename pairs for fast lookup.
186
- const patchMap = new Map()
187
- for (const { file, renames } of FILE_PATCHES) {
188
- patchMap.set(file, renames)
189
- }
190
-
191
- // Build a map from basename → text patches for fast lookup.
192
- const textPatchMap = new Map()
193
- for (const { file, patches } of FILE_TEXT_PATCHES) {
194
- textPatchMap.set(file, patches)
195
- }
196
-
197
- function applyRenames(src, renames) {
198
- let out = src
199
- for (const [from, to] of renames) {
200
- // Word-boundary replacement: only replace complete identifiers.
201
- const re = new RegExp(`\\b${from}\\b`, "g")
202
- out = out.replace(re, to)
203
- }
204
- return out
205
- }
206
-
207
- function applyTextPatches(src, patches) {
208
- let out = src
209
- for (const [from, to] of patches) {
210
- if (!out.includes(from)) {
211
- process.stderr.write(`build-amalgamation: warning — text patch not found in source\n expected: ${JSON.stringify(from.slice(0, 60))}\n`)
212
- }
213
- out = out.split(from).join(to)
214
- }
215
- return out
216
- }
217
-
218
- if (!fs.existsSync(baseSqlite)) {
219
- console.error(`build-amalgamation: ${baseSqlite} not found — run download.js first`)
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`)
220
24
  process.exit(1)
221
25
  }
222
-
223
- console.log("Building full doltlite amalgamation...")
224
-
225
- // Extract generated headers (parse.h, opcodes.h) from the sqlite3.c amalgamation.
226
- // These files are produced by the SQLite build system and are not shipped in the tarball.
227
- {
228
- const base = fs.readFileSync(baseSqlite, "utf8")
229
- for (const hdr of ["parse.h", "opcodes.h"]) {
230
- const hdrPath = path.join(srcDir, hdr)
231
- if (fs.existsSync(hdrPath)) continue
232
- const beginMark = `/************** Begin file ${hdr}`
233
- const endMark = `/************** End of ${hdr}`
234
- const bi = base.indexOf(beginMark)
235
- const ei = base.indexOf(endMark)
236
- if (bi !== -1 && ei !== -1) {
237
- const startOfContent = base.indexOf("\n", bi) + 1
238
- const endOfContent = base.lastIndexOf("\n", ei) + 1
239
- fs.writeFileSync(hdrPath, base.slice(startOfContent, endOfContent))
240
- console.log(`Extracted ${hdr} to ${hdrPath}`)
241
- } else {
242
- process.stderr.write(`build-amalgamation: warning — could not find ${hdr} in sqlite3.c\n`)
243
- }
244
- }
245
- }
246
-
247
- // Patch btreeInt.h to add include guards.
248
- // The original file has none; when multiple _orig.c files (each of which does
249
- // #include "btmutex.c" / "backup.c" / etc.) are merged into a single translation
250
- // unit, the compiler re-includes btreeInt.h through each of them, triggering
251
- // struct redefinition errors.
252
- {
253
- const btreeIntPath = path.join(srcDir, "btreeInt.h")
254
- if (fs.existsSync(btreeIntPath)) {
255
- const src = fs.readFileSync(btreeIntPath, "utf8")
256
- if (!src.includes("#ifndef BTREEINT_H")) {
257
- fs.writeFileSync(btreeIntPath,
258
- "#ifndef BTREEINT_H\n#define BTREEINT_H\n" + src + "\n#endif /* BTREEINT_H */\n"
259
- )
260
- console.log("Patched btreeInt.h with include guards")
261
- }
262
- }
263
- }
264
-
265
- // ── doltlite_orig.c ────────────────────────────────────────────────────────
266
- // Compile the original SQLite btree/pager/wal/backup with orig_ prefix symbols.
267
- // This must be a separate TU because it defines the original BtShared / Pager
268
- // structs, which conflict with prolly_btree.c's replacements in doltlite.c.
269
- {
270
- const origFiles = [
271
- "btmutex_orig.c",
272
- "pager_orig.c",
273
- "wal_orig.c",
274
- "backup_orig.c",
275
- "btree_orig.c",
276
- "btree_orig_api.c",
277
- ]
278
-
279
- // Variables defined in the original pager/btree code that pager_shim.c in
280
- // doltlite.c also defines. We rename them here so the linker sees two
281
- // different symbols and doesn't complain about duplicate definitions.
282
- // (These are test/statistics counters, only incremented under #ifdef SQLITE_TEST,
283
- // so renaming them away from their original names has no runtime effect in
284
- // release builds.)
285
- const conflictingGlobals = [
286
- "sqlite3_pager_readdb_count",
287
- "sqlite3_pager_writedb_count",
288
- "sqlite3_pager_writej_count",
289
- "sqlite3_opentemp_count",
290
- "sqlite3SharedCacheList",
291
- ]
292
-
293
- const origLines = [
294
- "/* Generated by build-amalgamation.js — DO NOT EDIT */",
295
- "/* Original SQLite btree/pager/wal/backup compiled with orig_ prefix symbols.",
296
- "** Compiled as a separate translation unit from doltlite.c. */",
297
- "",
298
- "/* btree_orig_api.c needs DOLTLITE_PROLLY */",
299
- "#ifndef DOLTLITE_PROLLY",
300
- "#define DOLTLITE_PROLLY 1",
301
- "#endif",
302
- "",
303
- "/* Rename globals that pager_shim.c in doltlite.c also defines, to avoid",
304
- "** duplicate-symbol linker errors in release (non-SQLITE_TEST) builds. */",
305
- ...conflictingGlobals.map(g => `#define ${g} dlt_orig_${g.replace(/^sqlite3_?/, "")}`),
306
- "",
307
- ]
308
-
309
- for (const f of origFiles) {
310
- const p = path.join(srcDir, f)
311
- if (!fs.existsSync(p)) {
312
- process.stderr.write(`build-amalgamation: warning — ${f} not found, skipping\n`)
313
- continue
314
- }
315
- origLines.push(`/************** Begin doltlite_orig file ${f} **************/`)
316
- origLines.push(fs.readFileSync(p, "utf8"))
317
- origLines.push(`/************** End of doltlite_orig file ${f} ******************/`)
318
- origLines.push("")
319
- }
320
-
321
- fs.mkdirSync(outDir, { recursive: true })
322
- fs.writeFileSync(outOrig, origLines.join("\n"))
323
- console.log(`doltlite_orig.c written to ${outOrig}`)
324
- }
325
-
326
- // ── doltlite.c ────────────────────────────────────────────────────────────
327
- let content = fs.readFileSync(baseSqlite, "utf8")
328
-
329
- // Make SQLITE_PRIVATE symbols non-static so doltlite_orig.c (a separate TU
330
- // that contains the original pager/btree/wal code) can link against them.
331
- // The amalgamation defines SQLITE_PRIVATE as `static`, which gives every
332
- // internal function internal linkage — invisible to other TUs. Removing
333
- // the `static` makes them hidden-visibility globals (via -fvisibility=hidden)
334
- // that are linkable within the final .node binary but not exported from it.
335
- content = content.replace(/#\s*define\s+SQLITE_PRIVATE\s+static\b/, '# define SQLITE_PRIVATE')
336
-
337
- // Wrap each guarded section.
338
- // The amalgamation uses markers of the form:
339
- // /************** Begin file btree.c **************/
340
- // /************** End of btree.c ******************/
341
- for (const file of GUARDED) {
342
- const escapedFile = file.replace(".", "\\.")
343
- const beginRe = new RegExp(`(/\\*{6,}\\s*Begin file ${escapedFile}\\s*\\*{6,}/)`)
344
- const endRe = new RegExp(`(/\\*{6,}\\s*End of ${escapedFile}\\s*\\*{6,}/)`)
345
- const bm = beginRe.exec(content)
346
- const em = endRe.exec(content)
347
- if (!bm || !em) {
348
- process.stderr.write(`build-amalgamation: warning — markers not found for ${file}\n`)
349
- continue
350
- }
351
- const before = content.slice(0, bm.index)
352
- const section = content.slice(bm.index, em.index + em[0].length)
353
- const after = content.slice(em.index + em[0].length)
354
- content = `${before}#ifndef DOLTLITE_PROLLY\n${section}\n#endif /* !DOLTLITE_PROLLY */\n${after}`
355
- }
356
-
357
- // Append prolly + doltlite source files.
358
- const parts = [content]
359
- for (const file of EXTRA) {
360
- const p = path.isAbsolute(file) ? file : path.join(srcDir, file)
361
- const name = path.basename(p)
362
- if (!fs.existsSync(p)) {
363
- process.stderr.write(`build-amalgamation: warning — ${name} not found, skipping\n`)
364
- continue
365
- }
366
- let src = fs.readFileSync(p, "utf8")
367
- if (patchMap.has(name)) {
368
- src = applyRenames(src, patchMap.get(name))
369
- }
370
- if (textPatchMap.has(name)) {
371
- src = applyTextPatches(src, textPatchMap.get(name))
372
- }
373
- parts.push(`\n/************** Begin DOLTLITE-EXTRA file ${name} **************/\n`)
374
- parts.push(src)
375
- parts.push(`\n/************** End of DOLTLITE-EXTRA ${name} ******************/\n`)
376
- }
377
-
378
- // Prepend the DOLTLITE_VERSION define into the extra section so that
379
- // prolly_btree.c and doltlite.c can use it without compiler flag quoting issues.
380
- parts.splice(1, 0, `\n/* Injected by build-amalgamation.js */\n#ifndef DOLTLITE_VERSION\n#define DOLTLITE_VERSION "${version}"\n#endif\n`)
381
-
382
- // sqlite3PagerWalSystemErrno is defined in pager.c, which is skipped in
383
- // DOLTLITE_PROLLY builds. pager_shim.c does not implement it. Provide a
384
- // stub: shim-pagers have no WAL (return 0); plain-sqlite Pagers (from ATTACH)
385
- // dispatch to the orig_ version compiled in doltlite_orig.c.
386
- // strncasecmp is a POSIX extension absent from MSVC — map it to _strnicmp.
387
- parts.push(`
388
- #ifdef DOLTLITE_PROLLY
389
- #ifdef _WIN32
390
- # ifndef strncasecmp
391
- # define strncasecmp _strnicmp
392
- # endif
393
- #endif
394
- extern int orig_sqlite3PagerWalSystemErrno(Pager *pPager);
395
- int sqlite3PagerWalSystemErrno(Pager *pPager){
396
- if( !pPager ) return 0;
397
- /* Shim pagers start with PAGER_SHIM_MAGIC (0x50534D31) at offset 0. */
398
- if( ((const unsigned int*)pPager)[0] == 0x50534D31u ) return 0;
399
- return orig_sqlite3PagerWalSystemErrno(pPager);
26
+ if (!fs.existsSync(inH)) {
27
+ console.error(`build-amalgamation: ${inH} not found - run download.js first`)
28
+ process.exit(1)
400
29
  }
401
- #endif /* DOLTLITE_PROLLY */
402
- `)
403
30
 
31
+ console.log("Preparing released doltlite amalgamation...")
404
32
  fs.mkdirSync(outDir, { recursive: true })
405
- fs.writeFileSync(outFile, parts.join(""))
406
- console.log(`Amalgamation written to ${outFile}`)
407
-
408
- // Generate a shim doltlite_internal.h in the output directory.
409
- // Since amalgamation/ is listed first in include_dirs, this takes priority
410
- // over the one in doltlite-src/src/. It wraps the definitions that
411
- // prolly_btree.c already provides so they don't get redefined.
412
- const origInternal = fs.readFileSync(
413
- path.join(srcDir, "doltlite_internal.h"), "utf8"
414
- )
415
- // prolly_btree.c (compiled earlier in the same TU) already defines:
416
- // struct TableEntry, typedef/struct SchemaEntry, tableEntryNameCmp
417
- // Wrap those in #ifndef DOLTLITE_PROLLY so they're skipped in prolly mode.
418
- let shimInternal = origInternal
419
-
420
- // Guard `struct TableEntry { ... };`
421
- shimInternal = shimInternal.replace(
422
- /(struct TableEntry \{[^}]*\};)/s,
423
- "#ifndef DOLTLITE_PROLLY\n$1\n#endif /* !DOLTLITE_PROLLY */"
424
- )
425
-
426
- // Guard `typedef struct SchemaEntry SchemaEntry;`
427
- shimInternal = shimInternal.replace(
428
- /typedef struct SchemaEntry SchemaEntry;/,
429
- "#ifndef DOLTLITE_PROLLY\ntypedef struct SchemaEntry SchemaEntry;\n#endif /* !DOLTLITE_PROLLY */"
430
- )
33
+ let amalgamation = fs.readFileSync(inC, "utf8")
34
+ amalgamation += `
431
35
 
432
- // Guard `struct SchemaEntry { ... };` (at end of file)
433
- shimInternal = shimInternal.replace(
434
- /(struct SchemaEntry \{[^}]*\};)/s,
435
- "#ifndef DOLTLITE_PROLLY\n$1\n#endif /* !DOLTLITE_PROLLY */"
436
- )
437
-
438
- // Guard `tableEntryNameCmp` static inline function
439
- shimInternal = shimInternal.replace(
440
- /(static SQLITE_INLINE int tableEntryNameCmp\([\s\S]*?\n\})/,
441
- "#ifndef DOLTLITE_PROLLY\n$1\n#endif /* !DOLTLITE_PROLLY */"
442
- )
443
-
444
- const shimPath = path.join(outDir, "doltlite_internal.h")
445
- fs.writeFileSync(shimPath, shimInternal)
446
-
447
- // Copy the header alongside.
448
- const baseHeader = path.join(srcRoot, "sqlite3.h")
449
- if (fs.existsSync(baseHeader) && !fs.existsSync(outHeader)) {
450
- fs.copyFileSync(baseHeader, outHeader)
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;
451
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"))
452
54
 
453
- console.log(`Shim doltlite_internal.h written to ${shimPath}`)
454
-
455
- // Copy all .h files from doltlite-src/src/ and doltlite-src/ext/blake3/ into
456
- // amalgamation/ so the compiler can find them via the existing "amalgamation"
457
- // include_dir without needing doltlite-src in the C include path.
458
- // Skip files that already have custom shims (btreeInt.h, doltlite_internal.h).
459
- const SKIP_HEADERS = new Set(["btreeInt.h", "doltlite_internal.h"])
55
+ // Copy headers included by addon sources and by the BLAKE3 SIMD wrappers.
460
56
  for (const hdrDir of [srcRoot, srcDir, blake3Dir]) {
461
57
  if (!fs.existsSync(hdrDir)) continue
462
58
  for (const f of fs.readdirSync(hdrDir)) {
463
- if (!f.endsWith(".h") || SKIP_HEADERS.has(f)) continue
464
- const dest = path.join(outDir, f)
465
- if (!fs.existsSync(dest)) {
466
- fs.copyFileSync(path.join(hdrDir, f), dest)
59
+ if (f.endsWith(".h")) {
60
+ fs.copyFileSync(path.join(hdrDir, f), path.join(outDir, f))
467
61
  }
468
62
  }
469
63
  }
470
64
 
471
- // The _orig.c wrapper files each textually #include their base SQLite .c file
472
- // (e.g. btmutex_orig.c does #include "btmutex.c"). When those wrappers are
473
- // inlined into doltlite_orig.c the compiler resolves the include relative to
474
- // amalgamation/, so the base .c files must be present there too.
475
- for (const f of ["btmutex.c", "pager.c", "wal.c", "backup.c", "btree.c"]) {
476
- const src = path.join(srcDir, f)
477
- const dest = path.join(outDir, f)
478
- if (fs.existsSync(src) && !fs.existsSync(dest)) {
479
- fs.copyFileSync(src, dest)
480
- }
481
- }
482
-
483
- // Generate amalgamation/btreeInt.h — adds include guards that the original lacks.
484
- // Without guards, each _orig.c file in doltlite_orig.c re-includes btreeInt.h via
485
- // sqliteInt.h, causing struct redefinition errors in a single translation unit.
486
- // The shim must NOT have its own #ifndef BTREEINT_H guard: we already patch the
487
- // original btreeInt.h to add that guard, so if the shim defined it first the
488
- // original's body would be skipped and struct Btree would never be declared.
489
- const btreeIntShim = [
490
- "/* Generated by build-amalgamation.js — DO NOT EDIT */",
491
- "/* Redirects to doltlite-src/src/btreeInt.h (which has its own include guard). */",
492
- `#include "../doltlite-src/src/btreeInt.h"`,
493
- "",
494
- ].join("\n")
495
- fs.writeFileSync(path.join(outDir, "btreeInt.h"), btreeIntShim)
496
- console.log(`Shim btreeInt.h written to ${path.join(outDir, "btreeInt.h")}`)
497
-
498
- // Generate SIMD wrapper files for BLAKE3.
499
- //
500
- // SSE4.1, AVX2, and AVX-512 are disabled via BLAKE3_NO_* defines in binding.gyp
501
- // because they require per-file compiler flags that GYP can't express portably.
502
- // SSE2 (baseline on x86_64) and NEON (baseline on aarch64) need no special flags
503
- // and are compiled here as separate translation units.
504
65
  const simdWrappers = [
505
- // name in amalgamation/ source file arch guard
506
- ["blake3_sse2_impl.c", "blake3_sse2.c", "defined(__x86_64__)||defined(_M_X64)||defined(__i386__)"],
507
- ["blake3_neon_impl.c", "blake3_neon.c", "defined(__aarch64__)||defined(__arm__)"],
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__)"],
508
68
  ]
509
69
 
510
70
  for (const [wrapName, srcName, archGuard] of simdWrappers) {
511
71
  const srcPath = path.join(blake3Dir, srcName)
512
72
  if (!fs.existsSync(srcPath)) continue
513
73
  const relSrc = path.relative(outDir, srcPath).replace(/\\/g, "/")
514
- const lines = [
515
- "/* Generated by build-amalgamation.js DO NOT EDIT */",
74
+ fs.writeFileSync(path.join(outDir, wrapName), [
75
+ "/* Generated by build-amalgamation.js - DO NOT EDIT */",
516
76
  `#if ${archGuard}`,
517
77
  `#include "${relSrc}"`,
518
78
  "#endif",
519
79
  "",
520
- ]
521
- fs.writeFileSync(path.join(outDir, wrapName), lines.join("\n"))
80
+ ].join("\n"))
522
81
  console.log(`SIMD wrapper ${wrapName} written`)
523
82
  }
83
+
84
+ console.log(`Amalgamation written to ${outC}`)
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env node
2
- // Downloads the doltlite autoconf source tarball from GitHub releases and
3
- // builds a complete amalgamation from it, and bundles the matching doltlite
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 both sqlite3.c (the base SQLite amalgamation
8
- // with doltlite's storage patches) and the prolly tree + dolt SQL function
9
- // source files under src/. We stitch them into a single doltlite.c that,
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 srcRoot = path.join(__dirname, "../doltlite-src")
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 (fs.existsSync(outC) && fs.existsSync(outH)) return cb()
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"])
@@ -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
- int rc = sqlite3_open_v2(path.c_str(), &db_, flags, nullptr);
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
- int rc = sqlite3_open_v2(path.c_str(), &db_,
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)) return env.Undefined();
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)) return env.Undefined();
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) result = RowToObject(env, stmt_);
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)) return env.Undefined();
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++, RowToObject(env, stmt_));
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", &Statement::Run),
178
- InstanceMethod("get", &Statement::Get),
179
- InstanceMethod("all", &Statement::All),
180
- InstanceMethod("iterate", &Statement::Iterate),
181
- InstanceMethod("columns", &Statement::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
- return Napi::Number::New(env, (double)sqlite3_column_int64(stmt, col));
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() <= (int)startIdx) return true;
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
- for (const char* prefix : {"", "$", ":", "@"}) {
91
- std::string k = std::string(prefix) + key;
92
- idx = sqlite3_bind_parameter_index(stmt, k.c_str());
93
- if (idx > 0) break;
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