@photostructure/sqlite 0.0.1 → 0.2.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 +38 -2
- package/README.md +47 -483
- package/SECURITY.md +27 -83
- 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-arm64/@photostructure+sqlite.glibc.node +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 +7 -1
- package/src/shims/sqlite_errors.h +168 -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 +736 -129
- 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
|
@@ -368,6 +368,10 @@ struct sqlite3_api_routines {
|
|
|
368
368
|
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
|
|
369
369
|
/* Version 3.50.0 and later */
|
|
370
370
|
int (*setlk_timeout)(sqlite3*,int,int);
|
|
371
|
+
/* Version 3.51.0 and later */
|
|
372
|
+
int (*set_errmsg)(sqlite3*,int,const char*);
|
|
373
|
+
int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);
|
|
374
|
+
|
|
371
375
|
};
|
|
372
376
|
|
|
373
377
|
/*
|
|
@@ -703,6 +707,9 @@ typedef int (*sqlite3_loadext_entry)(
|
|
|
703
707
|
#define sqlite3_set_clientdata sqlite3_api->set_clientdata
|
|
704
708
|
/* Version 3.50.0 and later */
|
|
705
709
|
#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
|
|
710
|
+
/* Version 3.51.0 and later */
|
|
711
|
+
#define sqlite3_set_errmsg sqlite3_api->set_errmsg
|
|
712
|
+
#define sqlite3_db_status64 sqlite3_api->db_status64
|
|
706
713
|
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
|
707
714
|
|
|
708
715
|
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
package/src/user_function.cpp
CHANGED
|
@@ -2,25 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
#include <climits>
|
|
4
4
|
#include <cmath>
|
|
5
|
+
#include <limits>
|
|
5
6
|
#include <stdexcept>
|
|
6
7
|
|
|
7
8
|
#include "sqlite_impl.h"
|
|
8
9
|
|
|
9
|
-
namespace photostructure {
|
|
10
|
-
namespace sqlite {
|
|
10
|
+
namespace photostructure::sqlite {
|
|
11
11
|
|
|
12
12
|
UserDefinedFunction::UserDefinedFunction(Napi::Env env, Napi::Function fn,
|
|
13
13
|
DatabaseSync *db, bool use_bigint_args)
|
|
14
14
|
: env_(env), fn_(Napi::Reference<Napi::Function>::New(fn, 1)), db_(db),
|
|
15
|
-
use_bigint_args_(use_bigint_args) {
|
|
16
|
-
//
|
|
15
|
+
use_bigint_args_(use_bigint_args), async_context_(nullptr) {
|
|
16
|
+
// Create async context for callbacks
|
|
17
|
+
const napi_status status = napi_async_init(
|
|
18
|
+
env, nullptr, Napi::String::New(env, "SQLiteUserFunction"),
|
|
19
|
+
&async_context_);
|
|
20
|
+
if (status != napi_ok) {
|
|
21
|
+
Napi::Error::New(env, "Failed to create async context")
|
|
22
|
+
.ThrowAsJavaScriptException();
|
|
23
|
+
}
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
UserDefinedFunction::~UserDefinedFunction() {
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
UserDefinedFunction::~UserDefinedFunction() noexcept {
|
|
27
|
+
// Check if environment is still valid before N-API operations.
|
|
28
|
+
// During shutdown, env_ may be torn down and N-API operations would crash.
|
|
29
|
+
// Try to create a handle scope - if this fails, env is invalid.
|
|
30
|
+
napi_handle_scope scope;
|
|
31
|
+
napi_status status = napi_open_handle_scope(env_, &scope);
|
|
32
|
+
|
|
33
|
+
if (status == napi_ok) {
|
|
34
|
+
// Safe to do N-API operations
|
|
35
|
+
if (!fn_.IsEmpty()) {
|
|
36
|
+
fn_.Reset();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (async_context_ != nullptr) {
|
|
40
|
+
napi_async_destroy(env_, async_context_);
|
|
41
|
+
async_context_ = nullptr;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
napi_close_handle_scope(env_, scope);
|
|
23
45
|
}
|
|
46
|
+
// If status != napi_ok, env is invalid - skip cleanup.
|
|
47
|
+
// References will be leaked, but that's better than crashing.
|
|
24
48
|
}
|
|
25
49
|
|
|
26
50
|
void UserDefinedFunction::xFunc(sqlite3_context *ctx, int argc,
|
|
@@ -35,6 +59,7 @@ void UserDefinedFunction::xFunc(sqlite3_context *ctx, int argc,
|
|
|
35
59
|
|
|
36
60
|
try {
|
|
37
61
|
Napi::HandleScope scope(self->env_);
|
|
62
|
+
Napi::CallbackScope callback_scope(self->env_, self->async_context_);
|
|
38
63
|
|
|
39
64
|
// Check if function reference is still valid
|
|
40
65
|
if (self->fn_.IsEmpty()) {
|
|
@@ -68,34 +93,33 @@ void UserDefinedFunction::xFunc(sqlite3_context *ctx, int argc,
|
|
|
68
93
|
js_args.push_back(js_val);
|
|
69
94
|
}
|
|
70
95
|
|
|
71
|
-
// Call the JavaScript function
|
|
72
|
-
|
|
96
|
+
// Call the JavaScript function with safer exception handling
|
|
97
|
+
napi_value js_result;
|
|
98
|
+
napi_value js_func = fn;
|
|
99
|
+
napi_value this_arg = self->env_.Undefined();
|
|
73
100
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
napi_status status =
|
|
102
|
+
napi_call_function(self->env_, this_arg, js_func, js_args.size(),
|
|
103
|
+
js_args.data(), &js_result);
|
|
104
|
+
|
|
105
|
+
if (status != napi_ok || self->env_.IsExceptionPending()) {
|
|
106
|
+
// Handle JavaScript exception by setting a generic SQLite error
|
|
107
|
+
if (self->env_.IsExceptionPending()) {
|
|
108
|
+
sqlite3_result_error(ctx, "JavaScript exception in user function", -1);
|
|
109
|
+
} else {
|
|
110
|
+
sqlite3_result_error(ctx, "Failed to call user function", -1);
|
|
83
111
|
}
|
|
84
112
|
return;
|
|
85
113
|
}
|
|
86
114
|
|
|
115
|
+
Napi::Value result(self->env_, js_result);
|
|
116
|
+
|
|
87
117
|
// Convert result back to SQLite
|
|
88
118
|
self->JSValueToSqliteResult(ctx, result);
|
|
89
119
|
|
|
90
120
|
} catch (const Napi::Error &e) {
|
|
91
|
-
// Handle JavaScript
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
sqlite3_result_error(ctx, error_msg.c_str(),
|
|
95
|
-
SafeCastToInt(error_msg.length()));
|
|
96
|
-
} catch (const std::overflow_error &) {
|
|
97
|
-
sqlite3_result_error(ctx, "Error message too long", -1);
|
|
98
|
-
}
|
|
121
|
+
// Handle JavaScript errors by setting a generic SQLite error
|
|
122
|
+
sqlite3_result_error(ctx, "JavaScript exception in user function", -1);
|
|
99
123
|
} catch (const std::exception &e) {
|
|
100
124
|
sqlite3_result_error(ctx, e.what(), -1);
|
|
101
125
|
} catch (...) {
|
|
@@ -138,15 +162,20 @@ Napi::Value UserDefinedFunction::SqliteValueToJS(sqlite3_value *value) {
|
|
|
138
162
|
}
|
|
139
163
|
|
|
140
164
|
case SQLITE_TEXT: {
|
|
141
|
-
const
|
|
142
|
-
|
|
165
|
+
const char *text =
|
|
166
|
+
reinterpret_cast<const char *>(sqlite3_value_text(value));
|
|
167
|
+
return Napi::String::New(env_, text ? text : "");
|
|
143
168
|
}
|
|
144
169
|
|
|
145
170
|
case SQLITE_BLOB: {
|
|
146
|
-
const void *
|
|
147
|
-
int
|
|
148
|
-
|
|
149
|
-
|
|
171
|
+
const void *blob = sqlite3_value_blob(value);
|
|
172
|
+
int bytes = sqlite3_value_bytes(value);
|
|
173
|
+
if (blob && bytes > 0) {
|
|
174
|
+
return Napi::Buffer<uint8_t>::Copy(
|
|
175
|
+
env_, static_cast<const uint8_t *>(blob), static_cast<size_t>(bytes));
|
|
176
|
+
} else {
|
|
177
|
+
return Napi::Buffer<uint8_t>::New(env_, 0);
|
|
178
|
+
}
|
|
150
179
|
}
|
|
151
180
|
|
|
152
181
|
case SQLITE_NULL:
|
|
@@ -186,7 +215,8 @@ void UserDefinedFunction::JSValueToSqliteResult(sqlite3_context *ctx,
|
|
|
186
215
|
// Check if it's an integer value
|
|
187
216
|
// Note: We cast INT64_MIN/MAX to double to avoid implicit conversion
|
|
188
217
|
// warnings
|
|
189
|
-
if (std::
|
|
218
|
+
if (std::abs(num_val - std::floor(num_val)) <
|
|
219
|
+
std::numeric_limits<double>::epsilon() &&
|
|
190
220
|
num_val >= static_cast<double>(INT64_MIN) &&
|
|
191
221
|
num_val <= static_cast<double>(INT64_MAX)) {
|
|
192
222
|
sqlite3_result_int64(ctx, static_cast<sqlite3_int64>(num_val));
|
|
@@ -201,7 +231,30 @@ void UserDefinedFunction::JSValueToSqliteResult(sqlite3_context *ctx,
|
|
|
201
231
|
} catch (const std::overflow_error &) {
|
|
202
232
|
sqlite3_result_error(ctx, "String value too long", -1);
|
|
203
233
|
}
|
|
234
|
+
} else if (value.IsDataView()) {
|
|
235
|
+
// IMPORTANT: Check DataView BEFORE IsBuffer() because N-API's IsBuffer()
|
|
236
|
+
// returns true for ALL ArrayBufferViews (including DataView), but
|
|
237
|
+
// Buffer::As() doesn't work correctly for DataView (returns length=0).
|
|
238
|
+
// See: https://github.com/nodejs/node/pull/56227
|
|
239
|
+
Napi::DataView dataView = value.As<Napi::DataView>();
|
|
240
|
+
Napi::ArrayBuffer arrayBuffer = dataView.ArrayBuffer();
|
|
241
|
+
size_t byteOffset = dataView.ByteOffset();
|
|
242
|
+
size_t byteLength = dataView.ByteLength();
|
|
243
|
+
|
|
244
|
+
if (arrayBuffer.Data() != nullptr && byteLength > 0) {
|
|
245
|
+
const uint8_t *data =
|
|
246
|
+
static_cast<const uint8_t *>(arrayBuffer.Data()) + byteOffset;
|
|
247
|
+
try {
|
|
248
|
+
sqlite3_result_blob(ctx, data, SafeCastToInt(byteLength),
|
|
249
|
+
SQLITE_TRANSIENT);
|
|
250
|
+
} catch (const std::overflow_error &) {
|
|
251
|
+
sqlite3_result_error(ctx, "DataView too large", -1);
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
sqlite3_result_zeroblob(ctx, 0);
|
|
255
|
+
}
|
|
204
256
|
} else if (value.IsBuffer()) {
|
|
257
|
+
// Handles both Node.js Buffer and TypedArrays (Uint8Array, etc.)
|
|
205
258
|
Napi::Buffer<uint8_t> buffer = value.As<Napi::Buffer<uint8_t>>();
|
|
206
259
|
try {
|
|
207
260
|
sqlite3_result_blob(ctx, buffer.Data(), SafeCastToInt(buffer.Length()),
|
|
@@ -221,5 +274,4 @@ void UserDefinedFunction::JSValueToSqliteResult(sqlite3_context *ctx,
|
|
|
221
274
|
}
|
|
222
275
|
}
|
|
223
276
|
|
|
224
|
-
} // namespace sqlite
|
|
225
|
-
} // namespace photostructure
|
|
277
|
+
} // namespace photostructure::sqlite
|
package/src/user_function.h
CHANGED
|
@@ -17,7 +17,7 @@ class UserDefinedFunction {
|
|
|
17
17
|
public:
|
|
18
18
|
UserDefinedFunction(Napi::Env env, Napi::Function fn, DatabaseSync *db,
|
|
19
19
|
bool use_bigint_args);
|
|
20
|
-
~UserDefinedFunction();
|
|
20
|
+
~UserDefinedFunction() noexcept;
|
|
21
21
|
|
|
22
22
|
// SQLite callback functions
|
|
23
23
|
static void xFunc(sqlite3_context *ctx, int argc, sqlite3_value **argv);
|
|
@@ -28,6 +28,7 @@ private:
|
|
|
28
28
|
Napi::FunctionReference fn_;
|
|
29
29
|
DatabaseSync *db_;
|
|
30
30
|
bool use_bigint_args_;
|
|
31
|
+
napi_async_context async_context_;
|
|
31
32
|
|
|
32
33
|
// Helper methods
|
|
33
34
|
Napi::Value SqliteValueToJS(sqlite3_value *value);
|