@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
package/src/aggregate_function.h
CHANGED
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
#include <napi.h>
|
|
5
5
|
#include <sqlite3.h>
|
|
6
6
|
|
|
7
|
+
#include <atomic>
|
|
7
8
|
#include <memory>
|
|
9
|
+
#include <mutex>
|
|
8
10
|
#include <string>
|
|
11
|
+
#include <unordered_map>
|
|
9
12
|
|
|
10
13
|
namespace photostructure {
|
|
11
14
|
namespace sqlite {
|
|
@@ -13,6 +16,20 @@ namespace sqlite {
|
|
|
13
16
|
// Forward declarations
|
|
14
17
|
class DatabaseSync;
|
|
15
18
|
|
|
19
|
+
// Thread-safe external storage for N-API values
|
|
20
|
+
// This solves the problem of storing N-API objects in SQLite-allocated memory
|
|
21
|
+
class ValueStorage {
|
|
22
|
+
private:
|
|
23
|
+
static std::unordered_map<int32_t, Napi::Reference<Napi::Value>> storage_;
|
|
24
|
+
static std::mutex mutex_;
|
|
25
|
+
static std::atomic<int32_t> next_id_;
|
|
26
|
+
|
|
27
|
+
public:
|
|
28
|
+
static int32_t Store(Napi::Env env, Napi::Value value);
|
|
29
|
+
static Napi::Value Get(Napi::Env env, int32_t id);
|
|
30
|
+
static void Remove(int32_t id);
|
|
31
|
+
};
|
|
32
|
+
|
|
16
33
|
class CustomAggregate {
|
|
17
34
|
public:
|
|
18
35
|
explicit CustomAggregate(Napi::Env env, DatabaseSync *db,
|
|
@@ -29,63 +46,57 @@ public:
|
|
|
29
46
|
static void xDestroy(void *self);
|
|
30
47
|
|
|
31
48
|
private:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
enum
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
49
|
+
// Comprehensive aggregate value storage (no Napi::Reference)
|
|
50
|
+
struct AggregateValue {
|
|
51
|
+
enum Type {
|
|
52
|
+
NUMBER,
|
|
53
|
+
STRING,
|
|
54
|
+
BIGINT,
|
|
55
|
+
BOOLEAN,
|
|
56
|
+
NULL_VAL,
|
|
57
|
+
OBJECT_JSON,
|
|
58
|
+
BUFFER
|
|
59
|
+
};
|
|
60
|
+
Type type;
|
|
61
|
+
bool is_initialized;
|
|
44
62
|
union {
|
|
45
|
-
double
|
|
46
|
-
bool
|
|
47
|
-
int64_t
|
|
63
|
+
double number_value;
|
|
64
|
+
bool bool_value;
|
|
65
|
+
int64_t bigint_value;
|
|
48
66
|
};
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
char string_buffer[4096]; // Fixed-size buffer for string data (POD) -
|
|
68
|
+
// increased for complex JSON
|
|
69
|
+
size_t string_length; // Length of string data
|
|
70
|
+
|
|
71
|
+
// No constructor needed - this must be POD for SQLite context
|
|
72
|
+
};
|
|
51
73
|
|
|
74
|
+
// Simplified aggregate data structure matching Node.js pattern
|
|
75
|
+
// Uses external storage to avoid N-API object lifetime issues
|
|
76
|
+
struct AggregateData {
|
|
77
|
+
int32_t value_id; // Reference to external storage
|
|
52
78
|
bool initialized;
|
|
53
79
|
bool is_window;
|
|
54
|
-
bool first_call; // True if this is the first call and we need to
|
|
55
|
-
// initialize with start value
|
|
56
|
-
|
|
57
|
-
// Default constructor
|
|
58
|
-
AggregateData()
|
|
59
|
-
: value_type(TYPE_NULL), number_val(0.0), initialized(false),
|
|
60
|
-
is_window(false), first_call(false) {}
|
|
61
|
-
|
|
62
|
-
// Destructor to properly clean up Napi::Reference
|
|
63
|
-
~AggregateData() {
|
|
64
|
-
if (value_type == TYPE_OBJECT && !object_ref.IsEmpty()) {
|
|
65
|
-
object_ref.Reset();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
80
|
};
|
|
69
81
|
|
|
70
|
-
// Helper methods
|
|
82
|
+
// Helper methods - mirroring Node.js implementation exactly
|
|
71
83
|
static void xStepBase(sqlite3_context *ctx, int argc, sqlite3_value **argv,
|
|
72
|
-
|
|
73
|
-
static void xValueBase(sqlite3_context *ctx, bool
|
|
84
|
+
Napi::Reference<Napi::Function> CustomAggregate::*mptr);
|
|
85
|
+
static void xValueBase(sqlite3_context *ctx, bool is_final);
|
|
86
|
+
|
|
87
|
+
// Helper method for safe JSON serialization with circular reference handling
|
|
88
|
+
static std::string SafeJsonStringify(Napi::Env env, Napi::Value value);
|
|
89
|
+
static void DestroyAggregateData(sqlite3_context *ctx);
|
|
74
90
|
|
|
75
91
|
AggregateData *GetAggregate(sqlite3_context *ctx);
|
|
76
92
|
Napi::Value SqliteValueToJS(sqlite3_value *value);
|
|
77
93
|
void JSValueToSqliteResult(sqlite3_context *ctx, Napi::Value value);
|
|
78
94
|
Napi::Value GetStartValue();
|
|
79
95
|
|
|
80
|
-
// New methods for raw C++ value handling
|
|
81
|
-
void StoreJSValueAsRaw(AggregateData *agg, Napi::Value value);
|
|
82
|
-
Napi::Value RawValueToJS(AggregateData *agg);
|
|
83
|
-
|
|
84
96
|
Napi::Env env_;
|
|
85
|
-
DatabaseSync *db_;
|
|
86
97
|
bool use_bigint_args_;
|
|
87
98
|
|
|
88
|
-
// Storage for start value - handle primitives
|
|
99
|
+
// Storage for start value - handle primitives and objects
|
|
89
100
|
enum StartValueType {
|
|
90
101
|
PRIMITIVE_NULL,
|
|
91
102
|
PRIMITIVE_UNDEFINED,
|
|
@@ -93,7 +104,8 @@ private:
|
|
|
93
104
|
PRIMITIVE_STRING,
|
|
94
105
|
PRIMITIVE_BOOLEAN,
|
|
95
106
|
PRIMITIVE_BIGINT,
|
|
96
|
-
OBJECT
|
|
107
|
+
OBJECT,
|
|
108
|
+
FUNCTION
|
|
97
109
|
};
|
|
98
110
|
StartValueType start_type_;
|
|
99
111
|
double number_value_;
|
|
@@ -101,13 +113,16 @@ private:
|
|
|
101
113
|
bool boolean_value_;
|
|
102
114
|
int64_t bigint_value_;
|
|
103
115
|
Napi::Reference<Napi::Value> object_ref_;
|
|
116
|
+
Napi::Reference<Napi::Function> start_fn_;
|
|
104
117
|
|
|
118
|
+
// Function references
|
|
105
119
|
Napi::Reference<Napi::Function> step_fn_;
|
|
106
120
|
Napi::Reference<Napi::Function> inverse_fn_;
|
|
107
121
|
Napi::Reference<Napi::Function> result_fn_;
|
|
108
122
|
|
|
109
|
-
// Async context for callbacks
|
|
123
|
+
// Async context for callbacks (created lazily)
|
|
110
124
|
napi_async_context async_context_;
|
|
125
|
+
napi_async_context GetAsyncContext();
|
|
111
126
|
};
|
|
112
127
|
|
|
113
128
|
} // namespace sqlite
|
package/src/binding.cpp
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
#include <memory>
|
|
2
1
|
#include <mutex>
|
|
3
2
|
#include <napi.h>
|
|
4
3
|
#include <set>
|
|
5
4
|
|
|
6
5
|
#include "sqlite_impl.h"
|
|
7
6
|
|
|
8
|
-
namespace photostructure {
|
|
9
|
-
namespace sqlite {
|
|
7
|
+
namespace photostructure::sqlite {
|
|
10
8
|
|
|
11
9
|
// Cleanup function for worker termination
|
|
12
|
-
void CleanupAddonData(napi_env env, void *finalize_data,
|
|
13
|
-
|
|
10
|
+
void CleanupAddonData([[maybe_unused]] napi_env env, void *finalize_data,
|
|
11
|
+
[[maybe_unused]] void *finalize_hint) {
|
|
12
|
+
auto *addon_data = static_cast<AddonData *>(finalize_data);
|
|
14
13
|
|
|
15
14
|
// Clean up any remaining database connections
|
|
16
15
|
{
|
|
17
|
-
std::lock_guard<std::mutex>
|
|
16
|
+
const std::lock_guard<std::mutex> mutex_lock(addon_data->mutex);
|
|
18
17
|
addon_data->databases.clear();
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -38,7 +37,7 @@ void CleanupAddonData(napi_env env, void *finalize_data, void *finalize_hint) {
|
|
|
38
37
|
// Helper to get addon data for current environment
|
|
39
38
|
AddonData *GetAddonData(napi_env env) {
|
|
40
39
|
void *data = nullptr;
|
|
41
|
-
napi_status status = napi_get_instance_data(env, &data);
|
|
40
|
+
const napi_status status = napi_get_instance_data(env, &data);
|
|
42
41
|
if (status != napi_ok || data == nullptr) {
|
|
43
42
|
return nullptr;
|
|
44
43
|
}
|
|
@@ -48,8 +47,8 @@ AddonData *GetAddonData(napi_env env) {
|
|
|
48
47
|
// Register a database instance for cleanup tracking
|
|
49
48
|
void RegisterDatabaseInstance(Napi::Env env, DatabaseSync *database) {
|
|
50
49
|
AddonData *addon_data = GetAddonData(env);
|
|
51
|
-
if (addon_data) {
|
|
52
|
-
std::lock_guard<std::mutex>
|
|
50
|
+
if (addon_data != nullptr) {
|
|
51
|
+
const std::lock_guard<std::mutex> mutex_lock(addon_data->mutex);
|
|
53
52
|
addon_data->databases.insert(database);
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -57,8 +56,8 @@ void RegisterDatabaseInstance(Napi::Env env, DatabaseSync *database) {
|
|
|
57
56
|
// Unregister a database instance
|
|
58
57
|
void UnregisterDatabaseInstance(Napi::Env env, DatabaseSync *database) {
|
|
59
58
|
AddonData *addon_data = GetAddonData(env);
|
|
60
|
-
if (addon_data) {
|
|
61
|
-
std::lock_guard<std::mutex>
|
|
59
|
+
if (addon_data != nullptr) {
|
|
60
|
+
const std::lock_guard<std::mutex> mutex_lock(addon_data->mutex);
|
|
62
61
|
addon_data->databases.erase(database);
|
|
63
62
|
}
|
|
64
63
|
}
|
|
@@ -141,15 +140,119 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
141
140
|
constants.Set("SQLITE_CHANGESET_FOREIGN_KEY",
|
|
142
141
|
Napi::Number::New(env, SQLITE_CHANGESET_FOREIGN_KEY));
|
|
143
142
|
|
|
143
|
+
// Authorization result codes
|
|
144
|
+
constants.Set("SQLITE_OK", Napi::Number::New(env, SQLITE_OK));
|
|
145
|
+
constants.Set("SQLITE_DENY", Napi::Number::New(env, SQLITE_DENY));
|
|
146
|
+
constants.Set("SQLITE_IGNORE", Napi::Number::New(env, SQLITE_IGNORE));
|
|
147
|
+
|
|
148
|
+
// Authorization action codes
|
|
149
|
+
constants.Set("SQLITE_CREATE_INDEX",
|
|
150
|
+
Napi::Number::New(env, SQLITE_CREATE_INDEX));
|
|
151
|
+
constants.Set("SQLITE_CREATE_TABLE",
|
|
152
|
+
Napi::Number::New(env, SQLITE_CREATE_TABLE));
|
|
153
|
+
constants.Set("SQLITE_CREATE_TEMP_INDEX",
|
|
154
|
+
Napi::Number::New(env, SQLITE_CREATE_TEMP_INDEX));
|
|
155
|
+
constants.Set("SQLITE_CREATE_TEMP_TABLE",
|
|
156
|
+
Napi::Number::New(env, SQLITE_CREATE_TEMP_TABLE));
|
|
157
|
+
constants.Set("SQLITE_CREATE_TEMP_TRIGGER",
|
|
158
|
+
Napi::Number::New(env, SQLITE_CREATE_TEMP_TRIGGER));
|
|
159
|
+
constants.Set("SQLITE_CREATE_TEMP_VIEW",
|
|
160
|
+
Napi::Number::New(env, SQLITE_CREATE_TEMP_VIEW));
|
|
161
|
+
constants.Set("SQLITE_CREATE_TRIGGER",
|
|
162
|
+
Napi::Number::New(env, SQLITE_CREATE_TRIGGER));
|
|
163
|
+
constants.Set("SQLITE_CREATE_VIEW",
|
|
164
|
+
Napi::Number::New(env, SQLITE_CREATE_VIEW));
|
|
165
|
+
constants.Set("SQLITE_DELETE", Napi::Number::New(env, SQLITE_DELETE));
|
|
166
|
+
constants.Set("SQLITE_DROP_INDEX", Napi::Number::New(env, SQLITE_DROP_INDEX));
|
|
167
|
+
constants.Set("SQLITE_DROP_TABLE", Napi::Number::New(env, SQLITE_DROP_TABLE));
|
|
168
|
+
constants.Set("SQLITE_DROP_TEMP_INDEX",
|
|
169
|
+
Napi::Number::New(env, SQLITE_DROP_TEMP_INDEX));
|
|
170
|
+
constants.Set("SQLITE_DROP_TEMP_TABLE",
|
|
171
|
+
Napi::Number::New(env, SQLITE_DROP_TEMP_TABLE));
|
|
172
|
+
constants.Set("SQLITE_DROP_TEMP_TRIGGER",
|
|
173
|
+
Napi::Number::New(env, SQLITE_DROP_TEMP_TRIGGER));
|
|
174
|
+
constants.Set("SQLITE_DROP_TEMP_VIEW",
|
|
175
|
+
Napi::Number::New(env, SQLITE_DROP_TEMP_VIEW));
|
|
176
|
+
constants.Set("SQLITE_DROP_TRIGGER",
|
|
177
|
+
Napi::Number::New(env, SQLITE_DROP_TRIGGER));
|
|
178
|
+
constants.Set("SQLITE_DROP_VIEW", Napi::Number::New(env, SQLITE_DROP_VIEW));
|
|
179
|
+
constants.Set("SQLITE_INSERT", Napi::Number::New(env, SQLITE_INSERT));
|
|
180
|
+
constants.Set("SQLITE_PRAGMA", Napi::Number::New(env, SQLITE_PRAGMA));
|
|
181
|
+
constants.Set("SQLITE_READ", Napi::Number::New(env, SQLITE_READ));
|
|
182
|
+
constants.Set("SQLITE_SELECT", Napi::Number::New(env, SQLITE_SELECT));
|
|
183
|
+
constants.Set("SQLITE_TRANSACTION",
|
|
184
|
+
Napi::Number::New(env, SQLITE_TRANSACTION));
|
|
185
|
+
constants.Set("SQLITE_UPDATE", Napi::Number::New(env, SQLITE_UPDATE));
|
|
186
|
+
constants.Set("SQLITE_ATTACH", Napi::Number::New(env, SQLITE_ATTACH));
|
|
187
|
+
constants.Set("SQLITE_DETACH", Napi::Number::New(env, SQLITE_DETACH));
|
|
188
|
+
constants.Set("SQLITE_ALTER_TABLE",
|
|
189
|
+
Napi::Number::New(env, SQLITE_ALTER_TABLE));
|
|
190
|
+
constants.Set("SQLITE_REINDEX", Napi::Number::New(env, SQLITE_REINDEX));
|
|
191
|
+
constants.Set("SQLITE_ANALYZE", Napi::Number::New(env, SQLITE_ANALYZE));
|
|
192
|
+
constants.Set("SQLITE_CREATE_VTABLE",
|
|
193
|
+
Napi::Number::New(env, SQLITE_CREATE_VTABLE));
|
|
194
|
+
constants.Set("SQLITE_DROP_VTABLE",
|
|
195
|
+
Napi::Number::New(env, SQLITE_DROP_VTABLE));
|
|
196
|
+
constants.Set("SQLITE_FUNCTION", Napi::Number::New(env, SQLITE_FUNCTION));
|
|
197
|
+
constants.Set("SQLITE_SAVEPOINT", Napi::Number::New(env, SQLITE_SAVEPOINT));
|
|
198
|
+
constants.Set("SQLITE_COPY", Napi::Number::New(env, SQLITE_COPY));
|
|
199
|
+
constants.Set("SQLITE_RECURSIVE", Napi::Number::New(env, SQLITE_RECURSIVE));
|
|
200
|
+
|
|
144
201
|
exports.Set("constants", constants);
|
|
145
202
|
|
|
146
|
-
//
|
|
203
|
+
// Add standalone backup() function (Node.js API compatibility)
|
|
204
|
+
// Signature: backup(sourceDb, destination, options?) -> Promise
|
|
205
|
+
Napi::Function backupFunc = Napi::Function::New(
|
|
206
|
+
env,
|
|
207
|
+
[](const Napi::CallbackInfo &info) -> Napi::Value {
|
|
208
|
+
Napi::Env env = info.Env();
|
|
209
|
+
|
|
210
|
+
// Validate and unwrap DatabaseSync instance
|
|
211
|
+
DatabaseSync *db = nullptr;
|
|
212
|
+
if (info.Length() >= 1 && info[0].IsObject()) {
|
|
213
|
+
try {
|
|
214
|
+
db = DatabaseSync::Unwrap(info[0].As<Napi::Object>());
|
|
215
|
+
} catch (...) {
|
|
216
|
+
// Fall through to error below
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (db == nullptr) {
|
|
220
|
+
Napi::TypeError::New(
|
|
221
|
+
env, "The \"sourceDb\" argument must be a DatabaseSync object")
|
|
222
|
+
.ThrowAsJavaScriptException();
|
|
223
|
+
return env.Undefined();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Validate destination path is provided
|
|
227
|
+
if (info.Length() < 2) {
|
|
228
|
+
Napi::TypeError::New(env, "The \"destination\" argument is required")
|
|
229
|
+
.ThrowAsJavaScriptException();
|
|
230
|
+
return env.Undefined();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Delegate to instance method: db.backup(destination, options?)
|
|
234
|
+
std::vector<napi_value> args;
|
|
235
|
+
for (size_t i = 1; i < info.Length(); i++) {
|
|
236
|
+
args.push_back(info[i]);
|
|
237
|
+
}
|
|
238
|
+
Napi::Function backupMethod =
|
|
239
|
+
db->Value().Get("backup").As<Napi::Function>();
|
|
240
|
+
return backupMethod.Call(db->Value(), args);
|
|
241
|
+
},
|
|
242
|
+
"backup");
|
|
243
|
+
|
|
244
|
+
// Set function name and length properties (Node.js compatibility)
|
|
245
|
+
backupFunc.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
246
|
+
"name", Napi::String::New(env, "backup"), napi_enumerable));
|
|
247
|
+
backupFunc.DefineProperty(Napi::PropertyDescriptor::Value(
|
|
248
|
+
"length", Napi::Number::New(env, 2), napi_enumerable));
|
|
249
|
+
|
|
250
|
+
exports.Set("backup", backupFunc);
|
|
147
251
|
|
|
148
252
|
return exports;
|
|
149
253
|
}
|
|
150
254
|
|
|
151
|
-
} // namespace sqlite
|
|
152
|
-
} // namespace photostructure
|
|
255
|
+
} // namespace photostructure::sqlite
|
|
153
256
|
|
|
154
257
|
// Module initialization function
|
|
155
258
|
Napi::Object InitSqlite(Napi::Env env, Napi::Object exports) {
|
package/src/dirname.ts
CHANGED