@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +522 -0
  4. package/SECURITY.md +114 -0
  5. package/binding.gyp +94 -0
  6. package/dist/index.cjs +134 -0
  7. package/dist/index.cjs.map +1 -0
  8. package/dist/index.d.cts +408 -0
  9. package/dist/index.d.mts +408 -0
  10. package/dist/index.d.ts +408 -0
  11. package/dist/index.mjs +103 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +144 -0
  14. package/prebuilds/darwin-arm64/@photostructure+sqlite.glibc.node +0 -0
  15. package/prebuilds/linux-arm64/@photostructure+sqlite.glibc.node +0 -0
  16. package/prebuilds/linux-arm64/@photostructure+sqlite.musl.node +0 -0
  17. package/prebuilds/linux-x64/@photostructure+sqlite.glibc.node +0 -0
  18. package/prebuilds/linux-x64/@photostructure+sqlite.musl.node +0 -0
  19. package/prebuilds/win32-x64/@photostructure+sqlite.glibc.node +0 -0
  20. package/scripts/post-build.mjs +21 -0
  21. package/scripts/prebuild-linux-glibc.sh +108 -0
  22. package/src/aggregate_function.cpp +417 -0
  23. package/src/aggregate_function.h +116 -0
  24. package/src/binding.cpp +160 -0
  25. package/src/dirname.ts +13 -0
  26. package/src/index.ts +465 -0
  27. package/src/shims/base_object-inl.h +8 -0
  28. package/src/shims/base_object.h +50 -0
  29. package/src/shims/debug_utils-inl.h +23 -0
  30. package/src/shims/env-inl.h +19 -0
  31. package/src/shims/memory_tracker-inl.h +17 -0
  32. package/src/shims/napi_extensions.h +73 -0
  33. package/src/shims/node.h +16 -0
  34. package/src/shims/node_errors.h +66 -0
  35. package/src/shims/node_mem-inl.h +8 -0
  36. package/src/shims/node_mem.h +31 -0
  37. package/src/shims/node_url.h +23 -0
  38. package/src/shims/promise_resolver.h +31 -0
  39. package/src/shims/util-inl.h +18 -0
  40. package/src/shims/util.h +101 -0
  41. package/src/sqlite_impl.cpp +2440 -0
  42. package/src/sqlite_impl.h +314 -0
  43. package/src/stack_path.ts +64 -0
  44. package/src/types/node-gyp-build.d.ts +4 -0
  45. package/src/upstream/node_sqlite.cc +2706 -0
  46. package/src/upstream/node_sqlite.h +234 -0
  47. package/src/upstream/sqlite.gyp +38 -0
  48. package/src/upstream/sqlite.js +19 -0
  49. package/src/upstream/sqlite3.c +262809 -0
  50. package/src/upstream/sqlite3.h +13773 -0
  51. package/src/upstream/sqlite3ext.h +723 -0
  52. package/src/user_function.cpp +225 -0
  53. package/src/user_function.h +40 -0
@@ -0,0 +1,417 @@
1
+ #include "aggregate_function.h"
2
+
3
+ #include <cmath>
4
+ #include <cstring>
5
+
6
+ #include "shims/node_errors.h"
7
+ #include "sqlite_impl.h"
8
+
9
+ namespace photostructure {
10
+ namespace sqlite {
11
+
12
+ CustomAggregate::CustomAggregate(Napi::Env env, DatabaseSync *db,
13
+ bool use_bigint_args, Napi::Value start,
14
+ Napi::Function step_fn,
15
+ Napi::Function inverse_fn,
16
+ Napi::Function result_fn)
17
+ : env_(env), db_(db), use_bigint_args_(use_bigint_args),
18
+ async_context_(nullptr) {
19
+ // Handle start value based on type
20
+ if (start.IsNull()) {
21
+ start_type_ = PRIMITIVE_NULL;
22
+ } else if (start.IsUndefined()) {
23
+ start_type_ = PRIMITIVE_UNDEFINED;
24
+ } else if (start.IsNumber()) {
25
+ start_type_ = PRIMITIVE_NUMBER;
26
+ number_value_ = start.As<Napi::Number>().DoubleValue();
27
+ } else if (start.IsString()) {
28
+ start_type_ = PRIMITIVE_STRING;
29
+ string_value_ = start.As<Napi::String>().Utf8Value();
30
+ } else if (start.IsBoolean()) {
31
+ start_type_ = PRIMITIVE_BOOLEAN;
32
+ boolean_value_ = start.As<Napi::Boolean>().Value();
33
+ } else if (start.IsBigInt()) {
34
+ start_type_ = PRIMITIVE_BIGINT;
35
+ bool lossless;
36
+ bigint_value_ = start.As<Napi::BigInt>().Int64Value(&lossless);
37
+ } else {
38
+ // Object, Array, or other complex type
39
+ start_type_ = OBJECT;
40
+ object_ref_ = Napi::Reference<Napi::Value>::New(start, 1);
41
+ }
42
+
43
+ step_fn_ = Napi::Reference<Napi::Function>::New(step_fn, 1);
44
+
45
+ if (!inverse_fn.IsEmpty()) {
46
+ inverse_fn_ = Napi::Reference<Napi::Function>::New(inverse_fn, 1);
47
+ }
48
+ if (!result_fn.IsEmpty()) {
49
+ result_fn_ = Napi::Reference<Napi::Function>::New(result_fn, 1);
50
+ }
51
+
52
+ // Create async context for callbacks
53
+ napi_status status = napi_async_init(
54
+ env, nullptr, Napi::String::New(env, "SQLiteAggregate"), &async_context_);
55
+ if (status != napi_ok) {
56
+ Napi::Error::New(env, "Failed to create async context")
57
+ .ThrowAsJavaScriptException();
58
+ }
59
+ }
60
+
61
+ CustomAggregate::~CustomAggregate() {
62
+ if (start_type_ == OBJECT && !object_ref_.IsEmpty()) {
63
+ object_ref_.Reset();
64
+ }
65
+ if (!step_fn_.IsEmpty())
66
+ step_fn_.Reset();
67
+ if (!inverse_fn_.IsEmpty())
68
+ inverse_fn_.Reset();
69
+ if (!result_fn_.IsEmpty())
70
+ result_fn_.Reset();
71
+
72
+ // Cleanup async context
73
+ if (async_context_ != nullptr) {
74
+ napi_async_destroy(env_, async_context_);
75
+ }
76
+ }
77
+
78
+ void CustomAggregate::xStep(sqlite3_context *ctx, int argc,
79
+ sqlite3_value **argv) {
80
+ xStepBase(ctx, argc, argv, false);
81
+ }
82
+
83
+ void CustomAggregate::xInverse(sqlite3_context *ctx, int argc,
84
+ sqlite3_value **argv) {
85
+ xStepBase(ctx, argc, argv, true);
86
+ }
87
+
88
+ void CustomAggregate::xFinal(sqlite3_context *ctx) { xValueBase(ctx, true); }
89
+
90
+ void CustomAggregate::xValue(sqlite3_context *ctx) { xValueBase(ctx, false); }
91
+
92
+ void CustomAggregate::xDestroy(void *self) {
93
+ if (self) {
94
+ delete static_cast<CustomAggregate *>(self);
95
+ }
96
+ }
97
+
98
+ void CustomAggregate::xStepBase(sqlite3_context *ctx, int argc,
99
+ sqlite3_value **argv, bool use_inverse) {
100
+ void *user_data = sqlite3_user_data(ctx);
101
+ if (!user_data) {
102
+ sqlite3_result_error(ctx, "Invalid user data in aggregate function", -1);
103
+ return;
104
+ }
105
+
106
+ CustomAggregate *self = static_cast<CustomAggregate *>(user_data);
107
+ if (!self) {
108
+ sqlite3_result_error(ctx, "No user data", -1);
109
+ return;
110
+ }
111
+
112
+ // Create HandleScope and CallbackScope for this operation
113
+ Napi::HandleScope scope(self->env_);
114
+ Napi::CallbackScope callback_scope(self->env_, self->async_context_);
115
+
116
+ try {
117
+ auto agg = self->GetAggregate(ctx);
118
+ if (!agg) {
119
+ sqlite3_result_error(ctx, "Failed to get aggregate context", -1);
120
+ return;
121
+ }
122
+
123
+ // Choose the right function
124
+ Napi::Function func;
125
+ if (use_inverse) {
126
+ if (self->inverse_fn_.IsEmpty()) {
127
+ sqlite3_result_error(ctx, "Inverse function not provided", -1);
128
+ return;
129
+ }
130
+ func = self->inverse_fn_.Value();
131
+ } else {
132
+ if (self->step_fn_.IsEmpty()) {
133
+ sqlite3_result_error(ctx, "Step function is empty", -1);
134
+ return;
135
+ }
136
+ func = self->step_fn_.Value();
137
+ }
138
+
139
+ // Prepare arguments for JavaScript function call
140
+ std::vector<Napi::Value> js_argv;
141
+
142
+ // First argument is the current aggregate value
143
+ Napi::Value agg_val;
144
+ if (agg->first_call) {
145
+ agg_val = self->GetStartValue();
146
+ // Store the start value as raw data for future reference
147
+ self->StoreJSValueAsRaw(agg, agg_val);
148
+ agg->first_call = false;
149
+ } else {
150
+ agg_val = self->RawValueToJS(agg);
151
+ }
152
+ js_argv.push_back(agg_val);
153
+
154
+ // Add the SQLite arguments
155
+ for (int i = 0; i < argc; ++i) {
156
+ Napi::Value js_val = self->SqliteValueToJS(argv[i]);
157
+ js_argv.push_back(js_val);
158
+ }
159
+
160
+ // Call the JavaScript function
161
+ Napi::Value result;
162
+ try {
163
+ // Debug: Log the call
164
+ result = func.Call(js_argv);
165
+ if (result.IsEmpty() || result.IsUndefined()) {
166
+ sqlite3_result_error(ctx, "Step function returned empty/undefined", -1);
167
+ return;
168
+ }
169
+ } catch (const std::exception &e) {
170
+ throw;
171
+ }
172
+
173
+ // Update the aggregate value
174
+ self->StoreJSValueAsRaw(agg, result);
175
+
176
+ } catch (const Napi::Error &e) {
177
+ // More detailed error message
178
+ std::string error_msg = "Aggregate step error: ";
179
+ error_msg += e.what();
180
+ sqlite3_result_error(ctx, error_msg.c_str(), -1);
181
+ } catch (const std::exception &e) {
182
+ // Catch any other exceptions
183
+ std::string error_msg = "Aggregate step exception: ";
184
+ error_msg += e.what();
185
+ sqlite3_result_error(ctx, error_msg.c_str(), -1);
186
+ }
187
+ }
188
+
189
+ void CustomAggregate::xValueBase(sqlite3_context *ctx, bool finalize) {
190
+ void *user_data = sqlite3_user_data(ctx);
191
+ if (!user_data) {
192
+ sqlite3_result_error(ctx, "Invalid user data in aggregate value function",
193
+ -1);
194
+ return;
195
+ }
196
+
197
+ CustomAggregate *self = static_cast<CustomAggregate *>(user_data);
198
+
199
+ // Create HandleScope and CallbackScope for this operation
200
+ Napi::HandleScope scope(self->env_);
201
+ Napi::CallbackScope callback_scope(self->env_, self->async_context_);
202
+
203
+ try {
204
+ auto agg = self->GetAggregate(ctx);
205
+ if (!agg) {
206
+ sqlite3_result_null(ctx);
207
+ return;
208
+ }
209
+
210
+ Napi::Value final_value = self->RawValueToJS(agg);
211
+
212
+ // If we have a result function, call it
213
+ if (!self->result_fn_.IsEmpty()) {
214
+ Napi::Function result_func = self->result_fn_.Value();
215
+ final_value = result_func.Call({final_value});
216
+ }
217
+
218
+ // Convert to SQLite result
219
+ self->JSValueToSqliteResult(ctx, final_value);
220
+
221
+ // Clean up if this is finalization
222
+ if (finalize) {
223
+ // Properly destroy the C++ object constructed with placement new
224
+ // This will call the destructor for Napi::Reference members
225
+ agg->~AggregateData();
226
+ }
227
+
228
+ } catch (const Napi::Error &e) {
229
+ sqlite3_result_error(ctx, e.what(), -1);
230
+ }
231
+ }
232
+
233
+ CustomAggregate::AggregateData *
234
+ CustomAggregate::GetAggregate(sqlite3_context *ctx) {
235
+ AggregateData *agg = static_cast<AggregateData *>(
236
+ sqlite3_aggregate_context(ctx, sizeof(AggregateData)));
237
+
238
+ // sqlite3_aggregate_context only returns NULL if size is 0 or memory
239
+ // allocation fails
240
+ if (!agg) {
241
+ return nullptr;
242
+ }
243
+
244
+ // Check if this is uninitialized memory by testing if initialized flag is
245
+ // false or garbage We need to be careful because the memory might contain
246
+ // random values Check if this memory has been initialized as a C++ object
247
+ if (agg->initialized != true) {
248
+ // First call - use placement new to properly construct the C++ object
249
+ new (agg) AggregateData();
250
+ agg->value_type = AggregateData::TYPE_NULL;
251
+ agg->number_val = 0.0;
252
+ agg->boolean_val = false;
253
+ agg->bigint_val = 0;
254
+ agg->initialized = true;
255
+ agg->is_window = false;
256
+ agg->first_call = true; // Mark that we need to initialize with start value
257
+ }
258
+
259
+ return agg;
260
+ }
261
+
262
+ Napi::Value CustomAggregate::SqliteValueToJS(sqlite3_value *value) {
263
+ // Don't create HandleScope here - let the caller manage it
264
+
265
+ int type = sqlite3_value_type(value);
266
+
267
+ switch (type) {
268
+ case SQLITE_INTEGER: {
269
+ sqlite3_int64 int_val = sqlite3_value_int64(value);
270
+ if (use_bigint_args_) {
271
+ return Napi::BigInt::New(env_, static_cast<int64_t>(int_val));
272
+ } else {
273
+ return Napi::Number::New(env_, static_cast<double>(int_val));
274
+ }
275
+ break;
276
+ }
277
+ case SQLITE_FLOAT: {
278
+ double float_val = sqlite3_value_double(value);
279
+ return Napi::Number::New(env_, float_val);
280
+ }
281
+ case SQLITE_TEXT: {
282
+ const char *text_val =
283
+ reinterpret_cast<const char *>(sqlite3_value_text(value));
284
+ return Napi::String::New(env_, text_val);
285
+ }
286
+ case SQLITE_BLOB: {
287
+ const void *blob_data = sqlite3_value_blob(value);
288
+ int blob_size = sqlite3_value_bytes(value);
289
+ return Napi::Buffer<uint8_t>::Copy(
290
+ env_, static_cast<const uint8_t *>(blob_data), blob_size);
291
+ }
292
+ case SQLITE_NULL:
293
+ default:
294
+ return env_.Null();
295
+ }
296
+ }
297
+
298
+ void CustomAggregate::JSValueToSqliteResult(sqlite3_context *ctx,
299
+ Napi::Value value) {
300
+ if (value.IsNull() || value.IsUndefined()) {
301
+ sqlite3_result_null(ctx);
302
+ } else if (value.IsBoolean()) {
303
+ bool bool_val = value.As<Napi::Boolean>().Value();
304
+ sqlite3_result_int(ctx, bool_val ? 1 : 0);
305
+ } else if (value.IsBigInt()) {
306
+ bool lossless;
307
+ int64_t bigint_val = value.As<Napi::BigInt>().Int64Value(&lossless);
308
+ if (lossless) {
309
+ sqlite3_result_int64(ctx, static_cast<sqlite3_int64>(bigint_val));
310
+ } else {
311
+ sqlite3_result_error(ctx, "BigInt value too large for SQLite", -1);
312
+ }
313
+ } else if (value.IsNumber()) {
314
+ double num_val = value.As<Napi::Number>().DoubleValue();
315
+ // Note: We cast INT64_MIN/MAX to double to avoid implicit conversion
316
+ // warnings
317
+ if (floor(num_val) == num_val &&
318
+ num_val >= static_cast<double>(INT64_MIN) &&
319
+ num_val <= static_cast<double>(INT64_MAX)) {
320
+ sqlite3_result_int64(ctx, static_cast<sqlite3_int64>(num_val));
321
+ } else {
322
+ sqlite3_result_double(ctx, num_val);
323
+ }
324
+ } else if (value.IsString()) {
325
+ std::string str_val = value.As<Napi::String>().Utf8Value();
326
+ sqlite3_result_text(ctx, str_val.c_str(), str_val.length(),
327
+ SQLITE_TRANSIENT);
328
+ } else if (value.IsBuffer()) {
329
+ Napi::Buffer<uint8_t> buffer = value.As<Napi::Buffer<uint8_t>>();
330
+ sqlite3_result_blob(ctx, buffer.Data(), buffer.Length(), SQLITE_TRANSIENT);
331
+ } else {
332
+ // Convert to string as fallback
333
+ std::string str_val = value.ToString().Utf8Value();
334
+ sqlite3_result_text(ctx, str_val.c_str(), str_val.length(),
335
+ SQLITE_TRANSIENT);
336
+ }
337
+ }
338
+
339
+ Napi::Value CustomAggregate::GetStartValue() {
340
+ // Don't create HandleScope here - let the caller manage it
341
+
342
+ switch (start_type_) {
343
+ case PRIMITIVE_NULL:
344
+ return env_.Null();
345
+ case PRIMITIVE_UNDEFINED:
346
+ return env_.Undefined();
347
+ case PRIMITIVE_NUMBER:
348
+ return Napi::Number::New(env_, number_value_);
349
+ case PRIMITIVE_STRING:
350
+ return Napi::String::New(env_, string_value_);
351
+ case PRIMITIVE_BOOLEAN:
352
+ return Napi::Boolean::New(env_, boolean_value_);
353
+ case PRIMITIVE_BIGINT:
354
+ return Napi::BigInt::New(env_, bigint_value_);
355
+ case OBJECT:
356
+ return object_ref_.Value();
357
+ default:
358
+ return env_.Null();
359
+ }
360
+ }
361
+
362
+ void CustomAggregate::StoreJSValueAsRaw(AggregateData *agg, Napi::Value value) {
363
+ // Always clean up previous object reference if it exists
364
+ if (agg->value_type == AggregateData::TYPE_OBJECT &&
365
+ !agg->object_ref.IsEmpty()) {
366
+ agg->object_ref.Reset();
367
+ }
368
+
369
+ if (value.IsNull()) {
370
+ agg->value_type = AggregateData::TYPE_NULL;
371
+ } else if (value.IsUndefined()) {
372
+ agg->value_type = AggregateData::TYPE_UNDEFINED;
373
+ } else if (value.IsNumber()) {
374
+ agg->value_type = AggregateData::TYPE_NUMBER;
375
+ agg->number_val = value.As<Napi::Number>().DoubleValue();
376
+ } else if (value.IsString()) {
377
+ agg->value_type = AggregateData::TYPE_STRING;
378
+ agg->string_val = value.As<Napi::String>().Utf8Value();
379
+ } else if (value.IsBoolean()) {
380
+ agg->value_type = AggregateData::TYPE_BOOLEAN;
381
+ agg->boolean_val = value.As<Napi::Boolean>().Value();
382
+ } else if (value.IsBigInt()) {
383
+ agg->value_type = AggregateData::TYPE_BIGINT;
384
+ bool lossless;
385
+ agg->bigint_val = value.As<Napi::BigInt>().Int64Value(&lossless);
386
+ } else {
387
+ // Complex object - this still requires Persistent reference
388
+ agg->value_type = AggregateData::TYPE_OBJECT;
389
+ agg->object_ref = Napi::Reference<Napi::Value>::New(value, 1);
390
+ }
391
+ }
392
+
393
+ Napi::Value CustomAggregate::RawValueToJS(AggregateData *agg) {
394
+ // Don't create HandleScope here - it should be managed by the caller
395
+
396
+ switch (agg->value_type) {
397
+ case AggregateData::TYPE_NULL:
398
+ return env_.Null();
399
+ case AggregateData::TYPE_UNDEFINED:
400
+ return env_.Undefined();
401
+ case AggregateData::TYPE_NUMBER:
402
+ return Napi::Number::New(env_, agg->number_val);
403
+ case AggregateData::TYPE_STRING:
404
+ return Napi::String::New(env_, agg->string_val);
405
+ case AggregateData::TYPE_BOOLEAN:
406
+ return Napi::Boolean::New(env_, agg->boolean_val);
407
+ case AggregateData::TYPE_BIGINT:
408
+ return Napi::BigInt::New(env_, agg->bigint_val);
409
+ case AggregateData::TYPE_OBJECT:
410
+ return agg->object_ref.Value();
411
+ default:
412
+ return env_.Null();
413
+ }
414
+ }
415
+
416
+ } // namespace sqlite
417
+ } // namespace photostructure
@@ -0,0 +1,116 @@
1
+ #ifndef SRC_AGGREGATE_FUNCTION_H_
2
+ #define SRC_AGGREGATE_FUNCTION_H_
3
+
4
+ #include <napi.h>
5
+ #include <sqlite3.h>
6
+
7
+ #include <memory>
8
+ #include <string>
9
+
10
+ namespace photostructure {
11
+ namespace sqlite {
12
+
13
+ // Forward declarations
14
+ class DatabaseSync;
15
+
16
+ class CustomAggregate {
17
+ public:
18
+ explicit CustomAggregate(Napi::Env env, DatabaseSync *db,
19
+ bool use_bigint_args, Napi::Value start,
20
+ Napi::Function step_fn, Napi::Function inverse_fn,
21
+ Napi::Function result_fn);
22
+ ~CustomAggregate();
23
+
24
+ // SQLite aggregate function callbacks
25
+ static void xStep(sqlite3_context *ctx, int argc, sqlite3_value **argv);
26
+ static void xInverse(sqlite3_context *ctx, int argc, sqlite3_value **argv);
27
+ static void xFinal(sqlite3_context *ctx);
28
+ static void xValue(sqlite3_context *ctx);
29
+ static void xDestroy(void *self);
30
+
31
+ private:
32
+ struct AggregateData {
33
+ // Store value as raw C++ data instead of JavaScript objects
34
+ enum ValueType {
35
+ TYPE_NULL,
36
+ TYPE_UNDEFINED,
37
+ TYPE_NUMBER,
38
+ TYPE_STRING,
39
+ TYPE_BOOLEAN,
40
+ TYPE_BIGINT,
41
+ TYPE_OBJECT // For complex objects, we'll need special handling
42
+ } value_type;
43
+
44
+ union {
45
+ double number_val;
46
+ bool boolean_val;
47
+ int64_t bigint_val;
48
+ };
49
+ std::string string_val; // For strings
50
+ Napi::Reference<Napi::Value> object_ref; // For complex objects (fallback)
51
+
52
+ bool initialized;
53
+ 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
+ };
69
+
70
+ // Helper methods
71
+ static void xStepBase(sqlite3_context *ctx, int argc, sqlite3_value **argv,
72
+ bool use_inverse);
73
+ static void xValueBase(sqlite3_context *ctx, bool finalize);
74
+
75
+ AggregateData *GetAggregate(sqlite3_context *ctx);
76
+ Napi::Value SqliteValueToJS(sqlite3_value *value);
77
+ void JSValueToSqliteResult(sqlite3_context *ctx, Napi::Value value);
78
+ Napi::Value GetStartValue();
79
+
80
+ // New methods for raw C++ value handling
81
+ void StoreJSValueAsRaw(AggregateData *agg, Napi::Value value);
82
+ Napi::Value RawValueToJS(AggregateData *agg);
83
+
84
+ Napi::Env env_;
85
+ DatabaseSync *db_;
86
+ bool use_bigint_args_;
87
+
88
+ // Storage for start value - handle primitives differently
89
+ enum StartValueType {
90
+ PRIMITIVE_NULL,
91
+ PRIMITIVE_UNDEFINED,
92
+ PRIMITIVE_NUMBER,
93
+ PRIMITIVE_STRING,
94
+ PRIMITIVE_BOOLEAN,
95
+ PRIMITIVE_BIGINT,
96
+ OBJECT
97
+ };
98
+ StartValueType start_type_;
99
+ double number_value_;
100
+ std::string string_value_;
101
+ bool boolean_value_;
102
+ int64_t bigint_value_;
103
+ Napi::Reference<Napi::Value> object_ref_;
104
+
105
+ Napi::Reference<Napi::Function> step_fn_;
106
+ Napi::Reference<Napi::Function> inverse_fn_;
107
+ Napi::Reference<Napi::Function> result_fn_;
108
+
109
+ // Async context for callbacks
110
+ napi_async_context async_context_;
111
+ };
112
+
113
+ } // namespace sqlite
114
+ } // namespace photostructure
115
+
116
+ #endif // SRC_AGGREGATE_FUNCTION_H_
@@ -0,0 +1,160 @@
1
+ #include <memory>
2
+ #include <mutex>
3
+ #include <napi.h>
4
+ #include <set>
5
+
6
+ #include "sqlite_impl.h"
7
+
8
+ namespace photostructure {
9
+ namespace sqlite {
10
+
11
+ // Cleanup function for worker termination
12
+ void CleanupAddonData(napi_env env, void *finalize_data, void *finalize_hint) {
13
+ AddonData *addon_data = static_cast<AddonData *>(finalize_data);
14
+
15
+ // Clean up any remaining database connections
16
+ {
17
+ std::lock_guard<std::mutex> lock(addon_data->mutex);
18
+ addon_data->databases.clear();
19
+ }
20
+
21
+ // Clean up persistent references to prevent memory leaks
22
+ if (!addon_data->databaseSyncConstructor.IsEmpty()) {
23
+ addon_data->databaseSyncConstructor.Reset();
24
+ }
25
+ if (!addon_data->statementSyncConstructor.IsEmpty()) {
26
+ addon_data->statementSyncConstructor.Reset();
27
+ }
28
+ if (!addon_data->statementSyncIteratorConstructor.IsEmpty()) {
29
+ addon_data->statementSyncIteratorConstructor.Reset();
30
+ }
31
+ if (!addon_data->sessionConstructor.IsEmpty()) {
32
+ addon_data->sessionConstructor.Reset();
33
+ }
34
+
35
+ delete addon_data;
36
+ }
37
+
38
+ // Helper to get addon data for current environment
39
+ AddonData *GetAddonData(napi_env env) {
40
+ void *data = nullptr;
41
+ napi_status status = napi_get_instance_data(env, &data);
42
+ if (status != napi_ok || data == nullptr) {
43
+ return nullptr;
44
+ }
45
+ return static_cast<AddonData *>(data);
46
+ }
47
+
48
+ // Register a database instance for cleanup tracking
49
+ void RegisterDatabaseInstance(Napi::Env env, DatabaseSync *database) {
50
+ AddonData *addon_data = GetAddonData(env);
51
+ if (addon_data) {
52
+ std::lock_guard<std::mutex> lock(addon_data->mutex);
53
+ addon_data->databases.insert(database);
54
+ }
55
+ }
56
+
57
+ // Unregister a database instance
58
+ void UnregisterDatabaseInstance(Napi::Env env, DatabaseSync *database) {
59
+ AddonData *addon_data = GetAddonData(env);
60
+ if (addon_data) {
61
+ std::lock_guard<std::mutex> lock(addon_data->mutex);
62
+ addon_data->databases.erase(database);
63
+ }
64
+ }
65
+
66
+ // Initialize the SQLite module
67
+ Napi::Object Init(Napi::Env env, Napi::Object exports) {
68
+ // Set up per-worker instance data
69
+ AddonData *addon_data = new AddonData();
70
+ napi_status status =
71
+ napi_set_instance_data(env, addon_data, CleanupAddonData, nullptr);
72
+ if (status != napi_ok) {
73
+ delete addon_data;
74
+ Napi::Error::New(env, "Failed to set instance data")
75
+ .ThrowAsJavaScriptException();
76
+ return exports;
77
+ }
78
+
79
+ DatabaseSync::Init(env, exports);
80
+ StatementSync::Init(env, exports);
81
+ StatementSyncIterator::Init(env, exports);
82
+ Session::Init(env, exports);
83
+
84
+ // Add SQLite constants
85
+ Napi::Object constants = Napi::Object::New(env);
86
+ constants.Set("SQLITE_OPEN_READONLY",
87
+ Napi::Number::New(env, SQLITE_OPEN_READONLY));
88
+ constants.Set("SQLITE_OPEN_READWRITE",
89
+ Napi::Number::New(env, SQLITE_OPEN_READWRITE));
90
+ constants.Set("SQLITE_OPEN_CREATE",
91
+ Napi::Number::New(env, SQLITE_OPEN_CREATE));
92
+ constants.Set("SQLITE_OPEN_DELETEONCLOSE",
93
+ Napi::Number::New(env, SQLITE_OPEN_DELETEONCLOSE));
94
+ constants.Set("SQLITE_OPEN_EXCLUSIVE",
95
+ Napi::Number::New(env, SQLITE_OPEN_EXCLUSIVE));
96
+ constants.Set("SQLITE_OPEN_AUTOPROXY",
97
+ Napi::Number::New(env, SQLITE_OPEN_AUTOPROXY));
98
+ constants.Set("SQLITE_OPEN_URI", Napi::Number::New(env, SQLITE_OPEN_URI));
99
+ constants.Set("SQLITE_OPEN_MEMORY",
100
+ Napi::Number::New(env, SQLITE_OPEN_MEMORY));
101
+ constants.Set("SQLITE_OPEN_MAIN_DB",
102
+ Napi::Number::New(env, SQLITE_OPEN_MAIN_DB));
103
+ constants.Set("SQLITE_OPEN_TEMP_DB",
104
+ Napi::Number::New(env, SQLITE_OPEN_TEMP_DB));
105
+ constants.Set("SQLITE_OPEN_TRANSIENT_DB",
106
+ Napi::Number::New(env, SQLITE_OPEN_TRANSIENT_DB));
107
+ constants.Set("SQLITE_OPEN_MAIN_JOURNAL",
108
+ Napi::Number::New(env, SQLITE_OPEN_MAIN_JOURNAL));
109
+ constants.Set("SQLITE_OPEN_TEMP_JOURNAL",
110
+ Napi::Number::New(env, SQLITE_OPEN_TEMP_JOURNAL));
111
+ constants.Set("SQLITE_OPEN_SUBJOURNAL",
112
+ Napi::Number::New(env, SQLITE_OPEN_SUBJOURNAL));
113
+ constants.Set("SQLITE_OPEN_SUPER_JOURNAL",
114
+ Napi::Number::New(env, SQLITE_OPEN_SUPER_JOURNAL));
115
+ constants.Set("SQLITE_OPEN_NOMUTEX",
116
+ Napi::Number::New(env, SQLITE_OPEN_NOMUTEX));
117
+ constants.Set("SQLITE_OPEN_FULLMUTEX",
118
+ Napi::Number::New(env, SQLITE_OPEN_FULLMUTEX));
119
+ constants.Set("SQLITE_OPEN_SHAREDCACHE",
120
+ Napi::Number::New(env, SQLITE_OPEN_SHAREDCACHE));
121
+ constants.Set("SQLITE_OPEN_PRIVATECACHE",
122
+ Napi::Number::New(env, SQLITE_OPEN_PRIVATECACHE));
123
+ constants.Set("SQLITE_OPEN_WAL", Napi::Number::New(env, SQLITE_OPEN_WAL));
124
+
125
+ // Changeset/session constants
126
+ constants.Set("SQLITE_CHANGESET_OMIT",
127
+ Napi::Number::New(env, SQLITE_CHANGESET_OMIT));
128
+ constants.Set("SQLITE_CHANGESET_REPLACE",
129
+ Napi::Number::New(env, SQLITE_CHANGESET_REPLACE));
130
+ constants.Set("SQLITE_CHANGESET_ABORT",
131
+ Napi::Number::New(env, SQLITE_CHANGESET_ABORT));
132
+
133
+ constants.Set("SQLITE_CHANGESET_DATA",
134
+ Napi::Number::New(env, SQLITE_CHANGESET_DATA));
135
+ constants.Set("SQLITE_CHANGESET_NOTFOUND",
136
+ Napi::Number::New(env, SQLITE_CHANGESET_NOTFOUND));
137
+ constants.Set("SQLITE_CHANGESET_CONFLICT",
138
+ Napi::Number::New(env, SQLITE_CHANGESET_CONFLICT));
139
+ constants.Set("SQLITE_CHANGESET_CONSTRAINT",
140
+ Napi::Number::New(env, SQLITE_CHANGESET_CONSTRAINT));
141
+ constants.Set("SQLITE_CHANGESET_FOREIGN_KEY",
142
+ Napi::Number::New(env, SQLITE_CHANGESET_FOREIGN_KEY));
143
+
144
+ exports.Set("constants", constants);
145
+
146
+ // TODO: Add backup function
147
+
148
+ return exports;
149
+ }
150
+
151
+ } // namespace sqlite
152
+ } // namespace photostructure
153
+
154
+ // Module initialization function
155
+ Napi::Object InitSqlite(Napi::Env env, Napi::Object exports) {
156
+ return photostructure::sqlite::Init(env, exports);
157
+ }
158
+
159
+ // Register the module
160
+ NODE_API_MODULE(phstr_sqlite, InitSqlite)
package/src/dirname.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { getCallerDirname } from "./stack_path";
2
+
3
+ // Thanks to tsup shims, __dirname should always be defined except when run by
4
+ // jest (which will use the stack_path shim)
5
+ export function _dirname() {
6
+ try {
7
+ if (typeof __dirname !== "undefined") return __dirname;
8
+ } catch {
9
+ // ignore
10
+ }
11
+ // we must be in jest. Use the stack_path ~~hack~~ shim:
12
+ return getCallerDirname();
13
+ }