@photostructure/sqlite 0.0.1
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 +43 -0
- package/LICENSE +21 -0
- package/README.md +522 -0
- package/SECURITY.md +114 -0
- package/binding.gyp +94 -0
- package/dist/index.cjs +134 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +408 -0
- package/dist/index.d.mts +408 -0
- package/dist/index.d.ts +408 -0
- package/dist/index.mjs +103 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +144 -0
- package/prebuilds/darwin-arm64/@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/win32-x64/@photostructure+sqlite.glibc.node +0 -0
- package/scripts/post-build.mjs +21 -0
- package/scripts/prebuild-linux-glibc.sh +108 -0
- package/src/aggregate_function.cpp +417 -0
- package/src/aggregate_function.h +116 -0
- package/src/binding.cpp +160 -0
- package/src/dirname.ts +13 -0
- package/src/index.ts +465 -0
- package/src/shims/base_object-inl.h +8 -0
- package/src/shims/base_object.h +50 -0
- package/src/shims/debug_utils-inl.h +23 -0
- package/src/shims/env-inl.h +19 -0
- package/src/shims/memory_tracker-inl.h +17 -0
- package/src/shims/napi_extensions.h +73 -0
- package/src/shims/node.h +16 -0
- package/src/shims/node_errors.h +66 -0
- package/src/shims/node_mem-inl.h +8 -0
- package/src/shims/node_mem.h +31 -0
- package/src/shims/node_url.h +23 -0
- package/src/shims/promise_resolver.h +31 -0
- package/src/shims/util-inl.h +18 -0
- package/src/shims/util.h +101 -0
- package/src/sqlite_impl.cpp +2440 -0
- package/src/sqlite_impl.h +314 -0
- package/src/stack_path.ts +64 -0
- package/src/types/node-gyp-build.d.ts +4 -0
- package/src/upstream/node_sqlite.cc +2706 -0
- package/src/upstream/node_sqlite.h +234 -0
- package/src/upstream/sqlite.gyp +38 -0
- package/src/upstream/sqlite.js +19 -0
- package/src/upstream/sqlite3.c +262809 -0
- package/src/upstream/sqlite3.h +13773 -0
- package/src/upstream/sqlite3ext.h +723 -0
- package/src/user_function.cpp +225 -0
- package/src/user_function.h +40 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#ifndef SRC_SQLITE_IMPL_H_
|
|
2
|
+
#define SRC_SQLITE_IMPL_H_
|
|
3
|
+
|
|
4
|
+
#include <napi.h>
|
|
5
|
+
#include <sqlite3.h>
|
|
6
|
+
|
|
7
|
+
#include <atomic>
|
|
8
|
+
#include <climits>
|
|
9
|
+
#include <map>
|
|
10
|
+
#include <memory>
|
|
11
|
+
#include <mutex>
|
|
12
|
+
#include <optional>
|
|
13
|
+
#include <set>
|
|
14
|
+
#include <stdexcept>
|
|
15
|
+
#include <string>
|
|
16
|
+
#include <thread>
|
|
17
|
+
|
|
18
|
+
// Include our shims
|
|
19
|
+
#include "shims/base_object.h"
|
|
20
|
+
#include "shims/napi_extensions.h"
|
|
21
|
+
#include "shims/node_errors.h"
|
|
22
|
+
#include "shims/promise_resolver.h"
|
|
23
|
+
// Removed threadpoolwork-inl.h - using Napi::AsyncWorker instead
|
|
24
|
+
#include "shims/util.h"
|
|
25
|
+
|
|
26
|
+
namespace photostructure {
|
|
27
|
+
namespace sqlite {
|
|
28
|
+
|
|
29
|
+
// Forward declarations
|
|
30
|
+
class DatabaseSync;
|
|
31
|
+
class StatementSync;
|
|
32
|
+
class StatementSyncIterator;
|
|
33
|
+
class Session;
|
|
34
|
+
|
|
35
|
+
// Per-worker instance data
|
|
36
|
+
struct AddonData {
|
|
37
|
+
std::mutex mutex;
|
|
38
|
+
// Track all database instances for proper cleanup
|
|
39
|
+
std::set<DatabaseSync *> databases;
|
|
40
|
+
|
|
41
|
+
// Store constructors per-instance instead of globally
|
|
42
|
+
Napi::FunctionReference databaseSyncConstructor;
|
|
43
|
+
Napi::FunctionReference statementSyncConstructor;
|
|
44
|
+
Napi::FunctionReference statementSyncIteratorConstructor;
|
|
45
|
+
Napi::FunctionReference sessionConstructor;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Worker thread support functions
|
|
49
|
+
void RegisterDatabaseInstance(Napi::Env env, DatabaseSync *database);
|
|
50
|
+
void UnregisterDatabaseInstance(Napi::Env env, DatabaseSync *database);
|
|
51
|
+
AddonData *GetAddonData(napi_env env);
|
|
52
|
+
|
|
53
|
+
// Path validation function
|
|
54
|
+
std::optional<std::string> ValidateDatabasePath(Napi::Env env, Napi::Value path,
|
|
55
|
+
const std::string &field_name);
|
|
56
|
+
|
|
57
|
+
// Safe integer cast with bounds checking
|
|
58
|
+
inline int SafeCastToInt(size_t value) {
|
|
59
|
+
if (value > static_cast<size_t>(INT_MAX)) {
|
|
60
|
+
throw std::overflow_error("Value too large to safely cast to int");
|
|
61
|
+
}
|
|
62
|
+
return static_cast<int>(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Database configuration
|
|
66
|
+
class DatabaseOpenConfiguration {
|
|
67
|
+
public:
|
|
68
|
+
explicit DatabaseOpenConfiguration(std::string &&location)
|
|
69
|
+
: location_(std::move(location)) {}
|
|
70
|
+
|
|
71
|
+
const std::string &location() const { return location_; }
|
|
72
|
+
|
|
73
|
+
bool get_read_only() const { return read_only_; }
|
|
74
|
+
void set_read_only(bool flag) { read_only_ = flag; }
|
|
75
|
+
|
|
76
|
+
bool get_enable_foreign_keys() const { return enable_foreign_keys_; }
|
|
77
|
+
void set_enable_foreign_keys(bool flag) { enable_foreign_keys_ = flag; }
|
|
78
|
+
|
|
79
|
+
bool get_enable_dqs() const { return enable_dqs_; }
|
|
80
|
+
void set_enable_dqs(bool flag) { enable_dqs_ = flag; }
|
|
81
|
+
|
|
82
|
+
void set_timeout(int timeout) { timeout_ = timeout; }
|
|
83
|
+
int get_timeout() const { return timeout_; }
|
|
84
|
+
|
|
85
|
+
private:
|
|
86
|
+
std::string location_;
|
|
87
|
+
bool read_only_ = false;
|
|
88
|
+
bool enable_foreign_keys_ = true;
|
|
89
|
+
bool enable_dqs_ = false;
|
|
90
|
+
int timeout_ = 0;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Main database class
|
|
94
|
+
class DatabaseSync : public Napi::ObjectWrap<DatabaseSync> {
|
|
95
|
+
public:
|
|
96
|
+
static constexpr int kInternalFieldCount = 1;
|
|
97
|
+
|
|
98
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
99
|
+
|
|
100
|
+
explicit DatabaseSync(const Napi::CallbackInfo &info);
|
|
101
|
+
virtual ~DatabaseSync();
|
|
102
|
+
|
|
103
|
+
// Database operations
|
|
104
|
+
Napi::Value Open(const Napi::CallbackInfo &info);
|
|
105
|
+
Napi::Value Close(const Napi::CallbackInfo &info);
|
|
106
|
+
Napi::Value Prepare(const Napi::CallbackInfo &info);
|
|
107
|
+
Napi::Value Exec(const Napi::CallbackInfo &info);
|
|
108
|
+
|
|
109
|
+
// Properties
|
|
110
|
+
Napi::Value LocationMethod(const Napi::CallbackInfo &info);
|
|
111
|
+
Napi::Value IsOpenGetter(const Napi::CallbackInfo &info);
|
|
112
|
+
Napi::Value IsTransactionGetter(const Napi::CallbackInfo &info);
|
|
113
|
+
|
|
114
|
+
// SQLite handle access
|
|
115
|
+
sqlite3 *connection() const { return connection_; }
|
|
116
|
+
bool IsOpen() const { return connection_ != nullptr; }
|
|
117
|
+
|
|
118
|
+
// User-defined functions
|
|
119
|
+
Napi::Value CustomFunction(const Napi::CallbackInfo &info);
|
|
120
|
+
|
|
121
|
+
// Aggregate functions
|
|
122
|
+
Napi::Value AggregateFunction(const Napi::CallbackInfo &info);
|
|
123
|
+
|
|
124
|
+
// Extension loading
|
|
125
|
+
Napi::Value EnableLoadExtension(const Napi::CallbackInfo &info);
|
|
126
|
+
Napi::Value LoadExtension(const Napi::CallbackInfo &info);
|
|
127
|
+
|
|
128
|
+
// Session support
|
|
129
|
+
Napi::Value CreateSession(const Napi::CallbackInfo &info);
|
|
130
|
+
Napi::Value ApplyChangeset(const Napi::CallbackInfo &info);
|
|
131
|
+
|
|
132
|
+
// Backup support
|
|
133
|
+
Napi::Value Backup(const Napi::CallbackInfo &info);
|
|
134
|
+
|
|
135
|
+
// Session management
|
|
136
|
+
void AddSession(Session *session);
|
|
137
|
+
void RemoveSession(Session *session);
|
|
138
|
+
void DeleteAllSessions();
|
|
139
|
+
|
|
140
|
+
private:
|
|
141
|
+
void InternalOpen(DatabaseOpenConfiguration config);
|
|
142
|
+
void InternalClose();
|
|
143
|
+
|
|
144
|
+
sqlite3 *connection_ = nullptr;
|
|
145
|
+
std::string location_;
|
|
146
|
+
bool read_only_ = false;
|
|
147
|
+
bool allow_load_extension_ = false;
|
|
148
|
+
bool enable_load_extension_ = false;
|
|
149
|
+
std::map<std::string, std::unique_ptr<StatementSync>> prepared_statements_;
|
|
150
|
+
std::set<Session *> sessions_; // Track all active sessions
|
|
151
|
+
mutable std::mutex sessions_mutex_; // Protect sessions_ for thread safety
|
|
152
|
+
std::thread::id creation_thread_;
|
|
153
|
+
napi_env env_; // Store for cleanup purposes
|
|
154
|
+
|
|
155
|
+
bool ValidateThread(Napi::Env env) const;
|
|
156
|
+
friend class Session;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Statement class
|
|
160
|
+
class StatementSync : public Napi::ObjectWrap<StatementSync> {
|
|
161
|
+
public:
|
|
162
|
+
static constexpr int kInternalFieldCount = 1;
|
|
163
|
+
|
|
164
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
165
|
+
|
|
166
|
+
explicit StatementSync(const Napi::CallbackInfo &info);
|
|
167
|
+
virtual ~StatementSync();
|
|
168
|
+
|
|
169
|
+
// Internal constructor for DatabaseSync to use
|
|
170
|
+
void InitStatement(DatabaseSync *database, const std::string &sql);
|
|
171
|
+
|
|
172
|
+
// Statement operations
|
|
173
|
+
Napi::Value Run(const Napi::CallbackInfo &info);
|
|
174
|
+
Napi::Value Get(const Napi::CallbackInfo &info);
|
|
175
|
+
Napi::Value All(const Napi::CallbackInfo &info);
|
|
176
|
+
Napi::Value Iterate(const Napi::CallbackInfo &info);
|
|
177
|
+
Napi::Value FinalizeStatement(const Napi::CallbackInfo &info);
|
|
178
|
+
|
|
179
|
+
// Properties
|
|
180
|
+
Napi::Value SourceSQLGetter(const Napi::CallbackInfo &info);
|
|
181
|
+
Napi::Value ExpandedSQLGetter(const Napi::CallbackInfo &info);
|
|
182
|
+
|
|
183
|
+
// Configuration methods
|
|
184
|
+
Napi::Value SetReadBigInts(const Napi::CallbackInfo &info);
|
|
185
|
+
Napi::Value SetReturnArrays(const Napi::CallbackInfo &info);
|
|
186
|
+
Napi::Value SetAllowBareNamedParameters(const Napi::CallbackInfo &info);
|
|
187
|
+
|
|
188
|
+
// Metadata methods
|
|
189
|
+
Napi::Value Columns(const Napi::CallbackInfo &info);
|
|
190
|
+
|
|
191
|
+
private:
|
|
192
|
+
void BindParameters(const Napi::CallbackInfo &info, size_t start_index = 0);
|
|
193
|
+
void BindSingleParameter(int param_index, Napi::Value param);
|
|
194
|
+
Napi::Value CreateResult();
|
|
195
|
+
void Reset();
|
|
196
|
+
|
|
197
|
+
DatabaseSync *database_;
|
|
198
|
+
sqlite3_stmt *statement_ = nullptr;
|
|
199
|
+
std::string source_sql_;
|
|
200
|
+
bool finalized_ = false;
|
|
201
|
+
std::thread::id creation_thread_;
|
|
202
|
+
|
|
203
|
+
// Configuration options
|
|
204
|
+
bool use_big_ints_ = false;
|
|
205
|
+
bool return_arrays_ = false;
|
|
206
|
+
bool allow_bare_named_params_ = false;
|
|
207
|
+
|
|
208
|
+
// Bare named parameters mapping (bare name -> full name with prefix)
|
|
209
|
+
std::optional<std::map<std::string, std::string>> bare_named_params_;
|
|
210
|
+
|
|
211
|
+
bool ValidateThread(Napi::Env env) const;
|
|
212
|
+
friend class StatementSyncIterator;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Iterator class for StatementSync
|
|
216
|
+
class StatementSyncIterator : public Napi::ObjectWrap<StatementSyncIterator> {
|
|
217
|
+
public:
|
|
218
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
219
|
+
static Napi::Object Create(Napi::Env env, StatementSync *stmt);
|
|
220
|
+
|
|
221
|
+
explicit StatementSyncIterator(const Napi::CallbackInfo &info);
|
|
222
|
+
virtual ~StatementSyncIterator();
|
|
223
|
+
|
|
224
|
+
// Iterator methods
|
|
225
|
+
Napi::Value Next(const Napi::CallbackInfo &info);
|
|
226
|
+
Napi::Value Return(const Napi::CallbackInfo &info);
|
|
227
|
+
|
|
228
|
+
private:
|
|
229
|
+
void SetStatement(StatementSync *stmt);
|
|
230
|
+
|
|
231
|
+
StatementSync *stmt_;
|
|
232
|
+
bool done_;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Session class for SQLite changesets
|
|
236
|
+
class Session : public Napi::ObjectWrap<Session> {
|
|
237
|
+
public:
|
|
238
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
239
|
+
static Napi::Object Create(Napi::Env env, DatabaseSync *database,
|
|
240
|
+
sqlite3_session *session);
|
|
241
|
+
|
|
242
|
+
explicit Session(const Napi::CallbackInfo &info);
|
|
243
|
+
virtual ~Session();
|
|
244
|
+
|
|
245
|
+
// Session methods
|
|
246
|
+
Napi::Value Changeset(const Napi::CallbackInfo &info);
|
|
247
|
+
Napi::Value Patchset(const Napi::CallbackInfo &info);
|
|
248
|
+
Napi::Value Close(const Napi::CallbackInfo &info);
|
|
249
|
+
|
|
250
|
+
// Get the underlying SQLite session
|
|
251
|
+
sqlite3_session *GetSession() const { return session_; }
|
|
252
|
+
|
|
253
|
+
private:
|
|
254
|
+
void SetSession(DatabaseSync *database, sqlite3_session *session);
|
|
255
|
+
void Delete();
|
|
256
|
+
|
|
257
|
+
template <int (*sqliteChangesetFunc)(sqlite3_session *, int *, void **)>
|
|
258
|
+
Napi::Value GenericChangeset(const Napi::CallbackInfo &info);
|
|
259
|
+
|
|
260
|
+
sqlite3_session *session_ = nullptr;
|
|
261
|
+
DatabaseSync *database_ = nullptr; // Direct pointer to database
|
|
262
|
+
|
|
263
|
+
friend class DatabaseSync;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// Progress data structure for backup progress updates
|
|
267
|
+
struct BackupProgress {
|
|
268
|
+
int current;
|
|
269
|
+
int total;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Backup job for asynchronous database backup
|
|
273
|
+
class BackupJob : public Napi::AsyncProgressWorker<BackupProgress> {
|
|
274
|
+
public:
|
|
275
|
+
BackupJob(Napi::Env env, DatabaseSync *source,
|
|
276
|
+
const std::string &destination_path, const std::string &source_db,
|
|
277
|
+
const std::string &dest_db, int pages, Napi::Function progress_func,
|
|
278
|
+
Napi::Promise::Deferred deferred);
|
|
279
|
+
~BackupJob();
|
|
280
|
+
|
|
281
|
+
void Execute(const ExecutionProgress &progress) override;
|
|
282
|
+
void OnOK() override;
|
|
283
|
+
void OnError(const Napi::Error &error) override;
|
|
284
|
+
void OnProgress(const BackupProgress *data, size_t count) override;
|
|
285
|
+
|
|
286
|
+
Napi::Promise GetPromise() { return deferred_.Promise(); }
|
|
287
|
+
|
|
288
|
+
private:
|
|
289
|
+
void Cleanup();
|
|
290
|
+
|
|
291
|
+
DatabaseSync *source_;
|
|
292
|
+
std::string destination_path_;
|
|
293
|
+
std::string source_db_;
|
|
294
|
+
std::string dest_db_;
|
|
295
|
+
int pages_;
|
|
296
|
+
|
|
297
|
+
// These are only accessed in Execute() on worker thread
|
|
298
|
+
int backup_status_ = SQLITE_OK;
|
|
299
|
+
sqlite3 *dest_ = nullptr;
|
|
300
|
+
sqlite3_backup *backup_ = nullptr;
|
|
301
|
+
int total_pages_ = 0;
|
|
302
|
+
|
|
303
|
+
Napi::FunctionReference progress_func_;
|
|
304
|
+
Napi::Promise::Deferred deferred_;
|
|
305
|
+
|
|
306
|
+
static std::atomic<int> active_jobs_;
|
|
307
|
+
static std::mutex active_jobs_mutex_;
|
|
308
|
+
static std::set<BackupJob *> active_job_instances_;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
} // namespace sqlite
|
|
312
|
+
} // namespace photostructure
|
|
313
|
+
|
|
314
|
+
#endif // SRC_SQLITE_IMPL_H_
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
|
|
3
|
+
export function getCallerDirname(): string {
|
|
4
|
+
const e = new Error();
|
|
5
|
+
if (e.stack == null) {
|
|
6
|
+
Error.captureStackTrace(e);
|
|
7
|
+
}
|
|
8
|
+
return dirname(extractCallerPath(e.stack as string));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Comprehensive regex patterns for different stack frame formats
|
|
12
|
+
const patterns =
|
|
13
|
+
process.platform === "win32"
|
|
14
|
+
? [
|
|
15
|
+
// Standard: "at functionName (C:\path\file.js:1:1)"
|
|
16
|
+
/\bat\s.+?\((?<path>[A-Z]:\\.+):\d+:\d+\)$/,
|
|
17
|
+
// direct: "at C:\path\file.js:1:1"
|
|
18
|
+
/\bat\s(?<path>[A-Z]:\\.+):\d+:\d+$/,
|
|
19
|
+
// UNC: "at functionName (\\server\share\path\file.js:1:1)"
|
|
20
|
+
/\bat\s.+?\((?<path>\\\\.+):\d+:\d+\)$/,
|
|
21
|
+
// direct: "at \\server\share\path\file.js:1:1"
|
|
22
|
+
/\bat\s(?<path>\\\\.+):\d+:\d+$/,
|
|
23
|
+
]
|
|
24
|
+
: [
|
|
25
|
+
// Standard: "at functionName (/path/file.js:1:1)"
|
|
26
|
+
/\bat\s.+?\((?<path>\/.+?):\d+:\d+\)$/,
|
|
27
|
+
// Anonymous or direct: "at /path/file.js:1:1"
|
|
28
|
+
/\bat\s(.+[^/]\s)?(?<path>\/.+?):\d+:\d+$/,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const MaybeUrlRE = /^[a-z]{2,5}:\/\//i;
|
|
32
|
+
|
|
33
|
+
// only exposed for tests:
|
|
34
|
+
export function extractCallerPath(stack: string): string {
|
|
35
|
+
const frames = stack.split("\n").filter(Boolean);
|
|
36
|
+
|
|
37
|
+
// First find getCallerDirname() in the stack:
|
|
38
|
+
const callerFrame = frames.findIndex((frame) =>
|
|
39
|
+
frame.includes("getCallerDirname"),
|
|
40
|
+
);
|
|
41
|
+
if (callerFrame === -1) {
|
|
42
|
+
throw new Error("Invalid stack trace format: missing caller frame");
|
|
43
|
+
}
|
|
44
|
+
for (let i = callerFrame + 1; i < frames.length; i++) {
|
|
45
|
+
const frame = frames[i];
|
|
46
|
+
for (const pattern of patterns) {
|
|
47
|
+
const g = frame?.trim().match(pattern)?.groups;
|
|
48
|
+
if (g != null && g["path"]) {
|
|
49
|
+
const path = g["path"];
|
|
50
|
+
// Windows requires us to check if it's a reasonable URL, as URL accepts
|
|
51
|
+
// "C:\\path\\file.txt" as valid (!!)
|
|
52
|
+
if (MaybeUrlRE.test(path)) {
|
|
53
|
+
try {
|
|
54
|
+
return new URL(path).pathname;
|
|
55
|
+
} catch {
|
|
56
|
+
// ignore
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return path;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
throw new Error("Invalid stack trace format: no parsable frames");
|
|
64
|
+
}
|