@photostructure/sqlite 0.0.1 → 0.2.0
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/CHANGELOG.md +36 -2
- package/README.md +45 -484
- package/SECURITY.md +27 -84
- package/binding.gyp +69 -22
- package/dist/index.cjs +185 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +552 -100
- package/dist/index.d.mts +552 -100
- package/dist/index.d.ts +552 -100
- package/dist/index.mjs +183 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +51 -41
- package/prebuilds/darwin-arm64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/darwin-x64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+sqlite.musl.node +0 -0
- package/prebuilds/linux-x64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/linux-x64/@photostructure+sqlite.musl.node +0 -0
- package/prebuilds/test_extension.so +0 -0
- package/prebuilds/win32-x64/@photostructure+sqlite.glibc.node +0 -0
- package/src/aggregate_function.cpp +503 -235
- package/src/aggregate_function.h +57 -42
- package/src/binding.cpp +117 -14
- package/src/dirname.ts +1 -1
- package/src/index.ts +122 -332
- package/src/lru-cache.ts +84 -0
- package/src/shims/env-inl.h +6 -15
- package/src/shims/node_errors.h +4 -0
- package/src/shims/sqlite_errors.h +162 -0
- package/src/shims/util.h +29 -4
- package/src/sql-tag-store.ts +140 -0
- package/src/sqlite_exception.h +49 -0
- package/src/sqlite_impl.cpp +711 -127
- package/src/sqlite_impl.h +84 -6
- package/src/{stack_path.ts → stack-path.ts} +7 -1
- package/src/types/aggregate-options.ts +22 -0
- package/src/types/changeset-apply-options.ts +18 -0
- package/src/types/database-sync-instance.ts +203 -0
- package/src/types/database-sync-options.ts +69 -0
- package/src/types/session-options.ts +10 -0
- package/src/types/sql-tag-store-instance.ts +51 -0
- package/src/types/sqlite-authorization-actions.ts +77 -0
- package/src/types/sqlite-authorization-results.ts +15 -0
- package/src/types/sqlite-changeset-conflict-types.ts +19 -0
- package/src/types/sqlite-changeset-resolution.ts +15 -0
- package/src/types/sqlite-open-flags.ts +50 -0
- package/src/types/statement-sync-instance.ts +73 -0
- package/src/types/user-functions-options.ts +14 -0
- package/src/upstream/node_sqlite.cc +960 -259
- package/src/upstream/node_sqlite.h +127 -2
- package/src/upstream/sqlite.js +1 -14
- package/src/upstream/sqlite3.c +4510 -1411
- package/src/upstream/sqlite3.h +390 -195
- package/src/upstream/sqlite3ext.h +7 -0
- package/src/user_function.cpp +88 -36
- package/src/user_function.h +2 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#ifndef SRC_SHIMS_SQLITE_ERRORS_H_
|
|
2
|
+
#define SRC_SHIMS_SQLITE_ERRORS_H_
|
|
3
|
+
|
|
4
|
+
#include "../sqlite_exception.h"
|
|
5
|
+
#include <napi.h>
|
|
6
|
+
#include <sqlite3.h>
|
|
7
|
+
#include <string>
|
|
8
|
+
|
|
9
|
+
namespace node {
|
|
10
|
+
|
|
11
|
+
// Helper function to get SQLite error code name
|
|
12
|
+
inline const char *GetSqliteErrorCodeName(int code) {
|
|
13
|
+
switch (code) {
|
|
14
|
+
case SQLITE_OK:
|
|
15
|
+
return "SQLITE_OK";
|
|
16
|
+
case SQLITE_ERROR:
|
|
17
|
+
return "SQLITE_ERROR";
|
|
18
|
+
case SQLITE_INTERNAL:
|
|
19
|
+
return "SQLITE_INTERNAL";
|
|
20
|
+
case SQLITE_PERM:
|
|
21
|
+
return "SQLITE_PERM";
|
|
22
|
+
case SQLITE_ABORT:
|
|
23
|
+
return "SQLITE_ABORT";
|
|
24
|
+
case SQLITE_BUSY:
|
|
25
|
+
return "SQLITE_BUSY";
|
|
26
|
+
case SQLITE_LOCKED:
|
|
27
|
+
return "SQLITE_LOCKED";
|
|
28
|
+
case SQLITE_NOMEM:
|
|
29
|
+
return "SQLITE_NOMEM";
|
|
30
|
+
case SQLITE_READONLY:
|
|
31
|
+
return "SQLITE_READONLY";
|
|
32
|
+
case SQLITE_INTERRUPT:
|
|
33
|
+
return "SQLITE_INTERRUPT";
|
|
34
|
+
case SQLITE_IOERR:
|
|
35
|
+
return "SQLITE_IOERR";
|
|
36
|
+
case SQLITE_CORRUPT:
|
|
37
|
+
return "SQLITE_CORRUPT";
|
|
38
|
+
case SQLITE_NOTFOUND:
|
|
39
|
+
return "SQLITE_NOTFOUND";
|
|
40
|
+
case SQLITE_FULL:
|
|
41
|
+
return "SQLITE_FULL";
|
|
42
|
+
case SQLITE_CANTOPEN:
|
|
43
|
+
return "SQLITE_CANTOPEN";
|
|
44
|
+
case SQLITE_PROTOCOL:
|
|
45
|
+
return "SQLITE_PROTOCOL";
|
|
46
|
+
case SQLITE_EMPTY:
|
|
47
|
+
return "SQLITE_EMPTY";
|
|
48
|
+
case SQLITE_SCHEMA:
|
|
49
|
+
return "SQLITE_SCHEMA";
|
|
50
|
+
case SQLITE_TOOBIG:
|
|
51
|
+
return "SQLITE_TOOBIG";
|
|
52
|
+
case SQLITE_CONSTRAINT:
|
|
53
|
+
return "SQLITE_CONSTRAINT";
|
|
54
|
+
case SQLITE_MISMATCH:
|
|
55
|
+
return "SQLITE_MISMATCH";
|
|
56
|
+
case SQLITE_MISUSE:
|
|
57
|
+
return "SQLITE_MISUSE";
|
|
58
|
+
case SQLITE_NOLFS:
|
|
59
|
+
return "SQLITE_NOLFS";
|
|
60
|
+
case SQLITE_AUTH:
|
|
61
|
+
return "SQLITE_AUTH";
|
|
62
|
+
case SQLITE_FORMAT:
|
|
63
|
+
return "SQLITE_FORMAT";
|
|
64
|
+
case SQLITE_RANGE:
|
|
65
|
+
return "SQLITE_RANGE";
|
|
66
|
+
case SQLITE_NOTADB:
|
|
67
|
+
return "SQLITE_NOTADB";
|
|
68
|
+
case SQLITE_NOTICE:
|
|
69
|
+
return "SQLITE_NOTICE";
|
|
70
|
+
case SQLITE_WARNING:
|
|
71
|
+
return "SQLITE_WARNING";
|
|
72
|
+
case SQLITE_ROW:
|
|
73
|
+
return "SQLITE_ROW";
|
|
74
|
+
case SQLITE_DONE:
|
|
75
|
+
return "SQLITE_DONE";
|
|
76
|
+
default:
|
|
77
|
+
// For extended error codes, get the base error code
|
|
78
|
+
int baseCode = code & 0xFF;
|
|
79
|
+
return GetSqliteErrorCodeName(baseCode);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Enhanced SQLite error that includes system errno information
|
|
84
|
+
inline void ThrowEnhancedSqliteError(Napi::Env env, sqlite3 *db,
|
|
85
|
+
int sqlite_code,
|
|
86
|
+
const std::string &message) {
|
|
87
|
+
Napi::Error error = Napi::Error::New(env, message);
|
|
88
|
+
|
|
89
|
+
// Add SQLite error code information
|
|
90
|
+
error.Set("sqliteCode", Napi::Number::New(env, sqlite_code));
|
|
91
|
+
|
|
92
|
+
if (db) {
|
|
93
|
+
// Get extended error code (more specific than basic error code)
|
|
94
|
+
int extended_code = sqlite3_extended_errcode(db);
|
|
95
|
+
error.Set("sqliteExtendedCode", Napi::Number::New(env, extended_code));
|
|
96
|
+
|
|
97
|
+
// Get system errno if available (for I/O errors)
|
|
98
|
+
int sys_errno = sqlite3_system_errno(db);
|
|
99
|
+
if (sys_errno != 0) {
|
|
100
|
+
error.Set("systemErrno", Napi::Number::New(env, sys_errno));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Set a standard error code property for compatibility
|
|
105
|
+
const char *code_name = GetSqliteErrorCodeName(sqlite_code);
|
|
106
|
+
error.Set("code", Napi::String::New(env, code_name));
|
|
107
|
+
|
|
108
|
+
// Also set the human-readable error string
|
|
109
|
+
const char *err_str = sqlite3_errstr(sqlite_code);
|
|
110
|
+
if (err_str) {
|
|
111
|
+
error.Set("sqliteErrorString", Napi::String::New(env, err_str));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
error.ThrowAsJavaScriptException();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Database-aware version available when sqlite_impl.h is included
|
|
118
|
+
// This will be specialized in sqlite_impl.cpp to avoid forward declaration
|
|
119
|
+
// issues
|
|
120
|
+
|
|
121
|
+
// Helper to create enhanced error from just a message (when we have the db
|
|
122
|
+
// handle)
|
|
123
|
+
inline void ThrowSqliteError(Napi::Env env, sqlite3 *db,
|
|
124
|
+
const std::string &message) {
|
|
125
|
+
if (db) {
|
|
126
|
+
int errcode = sqlite3_errcode(db);
|
|
127
|
+
ThrowEnhancedSqliteError(env, db, errcode, message);
|
|
128
|
+
} else {
|
|
129
|
+
// Fallback to simple error when no db handle available
|
|
130
|
+
Napi::Error::New(env, message).ThrowAsJavaScriptException();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Helper to throw from a SqliteException with captured error info
|
|
135
|
+
inline void
|
|
136
|
+
ThrowFromSqliteException(Napi::Env env,
|
|
137
|
+
const photostructure::sqlite::SqliteException &ex) {
|
|
138
|
+
Napi::Error error = Napi::Error::New(env, ex.what());
|
|
139
|
+
|
|
140
|
+
// Add all captured error information
|
|
141
|
+
error.Set("sqliteCode", Napi::Number::New(env, ex.sqlite_code()));
|
|
142
|
+
error.Set("sqliteExtendedCode", Napi::Number::New(env, ex.extended_code()));
|
|
143
|
+
|
|
144
|
+
if (ex.system_errno() != 0) {
|
|
145
|
+
error.Set("systemErrno", Napi::Number::New(env, ex.system_errno()));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Set the error code name
|
|
149
|
+
const char *code_name = GetSqliteErrorCodeName(ex.sqlite_code());
|
|
150
|
+
error.Set("code", Napi::String::New(env, code_name));
|
|
151
|
+
|
|
152
|
+
// Also set the human-readable error string
|
|
153
|
+
if (!ex.error_string().empty()) {
|
|
154
|
+
error.Set("sqliteErrorString", Napi::String::New(env, ex.error_string()));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
error.ThrowAsJavaScriptException();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
} // namespace node
|
|
161
|
+
|
|
162
|
+
#endif // SRC_SHIMS_SQLITE_ERRORS_H_
|
package/src/shims/util.h
CHANGED
|
@@ -10,16 +10,41 @@
|
|
|
10
10
|
namespace node {
|
|
11
11
|
|
|
12
12
|
// Environment class for Node.js context
|
|
13
|
+
// Note: This class is a shim for upstream Node.js code compatibility.
|
|
14
|
+
// It's not actively used in our implementation but provides the interface.
|
|
13
15
|
class Environment {
|
|
16
|
+
private:
|
|
17
|
+
using InstanceMap =
|
|
18
|
+
std::unordered_map<napi_env, std::unique_ptr<Environment>>;
|
|
19
|
+
|
|
20
|
+
// Thread-local storage accessor - the thread_local is on the variable, not
|
|
21
|
+
// the return type
|
|
22
|
+
static InstanceMap &GetInstances() {
|
|
23
|
+
static thread_local InstanceMap instances;
|
|
24
|
+
return instances;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Cleanup hook called when napi_env is destroyed
|
|
28
|
+
static void CleanupHook(void *arg) {
|
|
29
|
+
napi_env env = static_cast<napi_env>(arg);
|
|
30
|
+
InstanceMap &instances = GetInstances();
|
|
31
|
+
auto it = instances.find(env);
|
|
32
|
+
if (it != instances.end()) {
|
|
33
|
+
instances.erase(it);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
14
37
|
public:
|
|
15
38
|
static Environment *GetCurrent(Napi::Env env) {
|
|
16
|
-
|
|
17
|
-
static thread_local std::unordered_map<napi_env,
|
|
18
|
-
std::unique_ptr<Environment>>
|
|
19
|
-
instances;
|
|
39
|
+
InstanceMap &instances = GetInstances();
|
|
20
40
|
auto it = instances.find(env);
|
|
21
41
|
if (it == instances.end()) {
|
|
22
42
|
auto result = instances.emplace(env, std::make_unique<Environment>(env));
|
|
43
|
+
|
|
44
|
+
// Register cleanup hook to remove this entry when env is destroyed
|
|
45
|
+
// This prevents memory leaks when worker threads terminate
|
|
46
|
+
napi_add_env_cleanup_hook(env, CleanupHook, static_cast<void *>(env));
|
|
47
|
+
|
|
23
48
|
return result.first->second.get();
|
|
24
49
|
}
|
|
25
50
|
return it->second.get();
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { LRUCache } from "./lru-cache";
|
|
2
|
+
import { DatabaseSyncInstance } from "./types/database-sync-instance";
|
|
3
|
+
import type { StatementSyncInstance } from "./types/statement-sync-instance";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default capacity for the statement cache.
|
|
7
|
+
* Matches Node.js SQLTagStore default.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_CAPACITY = 1000;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SQLTagStore provides cached prepared statements via tagged template syntax.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```js
|
|
16
|
+
* const sql = db.createTagStore();
|
|
17
|
+
* sql.run`INSERT INTO users VALUES (${id}, ${name})`;
|
|
18
|
+
* const user = sql.get`SELECT * FROM users WHERE id = ${id}`;
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export class SQLTagStore {
|
|
22
|
+
private readonly database: DatabaseSyncInstance;
|
|
23
|
+
private readonly cache: LRUCache<string, StatementSyncInstance>;
|
|
24
|
+
private readonly maxCapacity: number;
|
|
25
|
+
|
|
26
|
+
constructor(db: DatabaseSyncInstance, capacity: number = DEFAULT_CAPACITY) {
|
|
27
|
+
if (!db.isOpen) {
|
|
28
|
+
throw new Error("Database is not open");
|
|
29
|
+
}
|
|
30
|
+
this.database = db;
|
|
31
|
+
this.maxCapacity = capacity;
|
|
32
|
+
this.cache = new LRUCache(capacity);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns the associated database instance.
|
|
37
|
+
*/
|
|
38
|
+
get db(): DatabaseSyncInstance {
|
|
39
|
+
return this.database;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns the maximum capacity of the statement cache.
|
|
44
|
+
*/
|
|
45
|
+
get capacity(): number {
|
|
46
|
+
return this.maxCapacity;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns the current number of cached statements.
|
|
51
|
+
*/
|
|
52
|
+
size(): number {
|
|
53
|
+
return this.cache.size();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Clears all cached statements.
|
|
58
|
+
*/
|
|
59
|
+
clear(): void {
|
|
60
|
+
this.cache.clear();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Execute an INSERT, UPDATE, DELETE or other statement that doesn't return rows.
|
|
65
|
+
* Returns an object with `changes` and `lastInsertRowid`.
|
|
66
|
+
*/
|
|
67
|
+
run(
|
|
68
|
+
strings: TemplateStringsArray,
|
|
69
|
+
...values: unknown[]
|
|
70
|
+
): { changes: number; lastInsertRowid: number | bigint } {
|
|
71
|
+
const stmt = this.getOrPrepare(strings);
|
|
72
|
+
return stmt.run(...values);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Execute a query and return the first row, or undefined if no rows.
|
|
77
|
+
*/
|
|
78
|
+
get(strings: TemplateStringsArray, ...values: unknown[]): unknown {
|
|
79
|
+
const stmt = this.getOrPrepare(strings);
|
|
80
|
+
return stmt.get(...values);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Execute a query and return all rows as an array.
|
|
85
|
+
*/
|
|
86
|
+
all(strings: TemplateStringsArray, ...values: unknown[]): unknown[] {
|
|
87
|
+
const stmt = this.getOrPrepare(strings);
|
|
88
|
+
return stmt.all(...values);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Execute a query and return an iterator over the rows.
|
|
93
|
+
*/
|
|
94
|
+
iterate(
|
|
95
|
+
strings: TemplateStringsArray,
|
|
96
|
+
...values: unknown[]
|
|
97
|
+
): IterableIterator<unknown> {
|
|
98
|
+
const stmt = this.getOrPrepare(strings);
|
|
99
|
+
return stmt.iterate(...values);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get a cached statement or prepare a new one.
|
|
104
|
+
* If a cached statement has been finalized, it's evicted and a new one is prepared.
|
|
105
|
+
*/
|
|
106
|
+
private getOrPrepare(strings: TemplateStringsArray): StatementSyncInstance {
|
|
107
|
+
if (!this.database.isOpen) {
|
|
108
|
+
throw new Error("Database is not open");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const sql = this.buildSQL(strings);
|
|
112
|
+
|
|
113
|
+
// Check cache - evict if finalized
|
|
114
|
+
const cached = this.cache.get(sql);
|
|
115
|
+
if (cached) {
|
|
116
|
+
if (!cached.finalized) {
|
|
117
|
+
return cached;
|
|
118
|
+
}
|
|
119
|
+
// Statement was finalized externally - remove from cache
|
|
120
|
+
this.cache.delete(sql);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Prepare new statement and cache it
|
|
124
|
+
const stmt = this.database.prepare(sql);
|
|
125
|
+
this.cache.set(sql, stmt);
|
|
126
|
+
return stmt;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Build the SQL string by joining template parts with `?` placeholders.
|
|
131
|
+
*/
|
|
132
|
+
private buildSQL(strings: TemplateStringsArray): string {
|
|
133
|
+
let sql = strings[0] ?? "";
|
|
134
|
+
for (let i = 1; i < strings.length; i++) {
|
|
135
|
+
// eslint-disable-next-line security/detect-object-injection -- Index is from controlled for-loop
|
|
136
|
+
sql += "?" + (strings[i] ?? "");
|
|
137
|
+
}
|
|
138
|
+
return sql;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#ifndef SRC_SQLITE_EXCEPTION_H_
|
|
2
|
+
#define SRC_SQLITE_EXCEPTION_H_
|
|
3
|
+
|
|
4
|
+
#include <exception>
|
|
5
|
+
#include <sqlite3.h>
|
|
6
|
+
#include <string>
|
|
7
|
+
|
|
8
|
+
namespace photostructure {
|
|
9
|
+
namespace sqlite {
|
|
10
|
+
|
|
11
|
+
// Custom exception that captures SQLite error information at the point of error
|
|
12
|
+
class SqliteException : public std::exception {
|
|
13
|
+
public:
|
|
14
|
+
SqliteException(sqlite3 *db, int result_code, const std::string &message)
|
|
15
|
+
: message_(message), sqlite_code_(result_code), extended_code_(0),
|
|
16
|
+
system_errno_(0) {
|
|
17
|
+
|
|
18
|
+
if (db) {
|
|
19
|
+
// Capture error codes immediately while they're still valid
|
|
20
|
+
extended_code_ = sqlite3_extended_errcode(db);
|
|
21
|
+
system_errno_ = sqlite3_system_errno(db);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Get the error string for the code
|
|
25
|
+
const char *err_str = sqlite3_errstr(result_code);
|
|
26
|
+
if (err_str) {
|
|
27
|
+
error_string_ = err_str;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const char *what() const noexcept override { return message_.c_str(); }
|
|
32
|
+
|
|
33
|
+
int sqlite_code() const { return sqlite_code_; }
|
|
34
|
+
int extended_code() const { return extended_code_; }
|
|
35
|
+
int system_errno() const { return system_errno_; }
|
|
36
|
+
const std::string &error_string() const { return error_string_; }
|
|
37
|
+
|
|
38
|
+
private:
|
|
39
|
+
std::string message_;
|
|
40
|
+
std::string error_string_;
|
|
41
|
+
int sqlite_code_;
|
|
42
|
+
int extended_code_;
|
|
43
|
+
int system_errno_;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
} // namespace sqlite
|
|
47
|
+
} // namespace photostructure
|
|
48
|
+
|
|
49
|
+
#endif // SRC_SQLITE_EXCEPTION_H_
|