@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,225 @@
|
|
|
1
|
+
#include "user_function.h"
|
|
2
|
+
|
|
3
|
+
#include <climits>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
#include <stdexcept>
|
|
6
|
+
|
|
7
|
+
#include "sqlite_impl.h"
|
|
8
|
+
|
|
9
|
+
namespace photostructure {
|
|
10
|
+
namespace sqlite {
|
|
11
|
+
|
|
12
|
+
UserDefinedFunction::UserDefinedFunction(Napi::Env env, Napi::Function fn,
|
|
13
|
+
DatabaseSync *db, bool use_bigint_args)
|
|
14
|
+
: env_(env), fn_(Napi::Reference<Napi::Function>::New(fn, 1)), db_(db),
|
|
15
|
+
use_bigint_args_(use_bigint_args) {
|
|
16
|
+
// No need for SuppressDestruct when using reference count
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
UserDefinedFunction::~UserDefinedFunction() {
|
|
20
|
+
// Clean up the persistent function reference
|
|
21
|
+
if (!fn_.IsEmpty()) {
|
|
22
|
+
fn_.Reset();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
void UserDefinedFunction::xFunc(sqlite3_context *ctx, int argc,
|
|
27
|
+
sqlite3_value **argv) {
|
|
28
|
+
void *user_data = sqlite3_user_data(ctx);
|
|
29
|
+
if (!user_data) {
|
|
30
|
+
sqlite3_result_error(ctx, "Invalid user data in function callback", -1);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
UserDefinedFunction *self = static_cast<UserDefinedFunction *>(user_data);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
Napi::HandleScope scope(self->env_);
|
|
38
|
+
|
|
39
|
+
// Check if function reference is still valid
|
|
40
|
+
if (self->fn_.IsEmpty()) {
|
|
41
|
+
sqlite3_result_error(ctx, "Function reference is no longer valid", -1);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Napi::Value fn_value;
|
|
46
|
+
try {
|
|
47
|
+
fn_value = self->fn_.Value();
|
|
48
|
+
} catch (const Napi::Error &e) {
|
|
49
|
+
sqlite3_result_error(ctx, "Failed to retrieve function reference", -1);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Additional check for function validity
|
|
54
|
+
if (!fn_value.IsFunction()) {
|
|
55
|
+
sqlite3_result_error(ctx, "Invalid function reference - not a function",
|
|
56
|
+
-1);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
Napi::Function fn = fn_value.As<Napi::Function>();
|
|
61
|
+
|
|
62
|
+
// Convert SQLite arguments to JavaScript values
|
|
63
|
+
std::vector<napi_value> js_args;
|
|
64
|
+
js_args.reserve(argc);
|
|
65
|
+
|
|
66
|
+
for (int i = 0; i < argc; i++) {
|
|
67
|
+
Napi::Value js_val = self->SqliteValueToJS(argv[i]);
|
|
68
|
+
js_args.push_back(js_val);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Call the JavaScript function
|
|
72
|
+
Napi::Value result = fn.Call(js_args);
|
|
73
|
+
|
|
74
|
+
// Check if there's a pending exception after the call
|
|
75
|
+
if (self->env_.IsExceptionPending()) {
|
|
76
|
+
Napi::Error error = self->env_.GetAndClearPendingException();
|
|
77
|
+
std::string error_msg = error.Message();
|
|
78
|
+
try {
|
|
79
|
+
sqlite3_result_error(ctx, error_msg.c_str(),
|
|
80
|
+
SafeCastToInt(error_msg.length()));
|
|
81
|
+
} catch (const std::overflow_error &) {
|
|
82
|
+
sqlite3_result_error(ctx, "Error message too long", -1);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Convert result back to SQLite
|
|
88
|
+
self->JSValueToSqliteResult(ctx, result);
|
|
89
|
+
|
|
90
|
+
} catch (const Napi::Error &e) {
|
|
91
|
+
// Handle JavaScript exceptions
|
|
92
|
+
std::string error_msg = e.Message();
|
|
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
|
+
}
|
|
99
|
+
} catch (const std::exception &e) {
|
|
100
|
+
sqlite3_result_error(ctx, e.what(), -1);
|
|
101
|
+
} catch (...) {
|
|
102
|
+
sqlite3_result_error(ctx, "Unknown error in user-defined function", -1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
void UserDefinedFunction::xDestroy(void *self) {
|
|
107
|
+
if (self) {
|
|
108
|
+
delete static_cast<UserDefinedFunction *>(self);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Napi::Value UserDefinedFunction::SqliteValueToJS(sqlite3_value *value) {
|
|
113
|
+
switch (sqlite3_value_type(value)) {
|
|
114
|
+
case SQLITE_INTEGER: {
|
|
115
|
+
sqlite3_int64 int_val = sqlite3_value_int64(value);
|
|
116
|
+
|
|
117
|
+
if (use_bigint_args_) {
|
|
118
|
+
return Napi::BigInt::New(env_, static_cast<int64_t>(int_val));
|
|
119
|
+
} else if (int_val >= INT32_MIN && int_val <= INT32_MAX) {
|
|
120
|
+
return Napi::Number::New(env_, static_cast<int32_t>(int_val));
|
|
121
|
+
} else {
|
|
122
|
+
// Large integers that don't fit in int32 but aren't using BigInt
|
|
123
|
+
// Check if the value can be safely represented as a JavaScript number
|
|
124
|
+
if (int_val < -0x1FFFFFFFFFFFFF || int_val > 0x1FFFFFFFFFFFFF) {
|
|
125
|
+
// Value is outside safe integer range for JavaScript numbers
|
|
126
|
+
std::string error_msg =
|
|
127
|
+
"Value is too large to be represented as a JavaScript number: " +
|
|
128
|
+
std::to_string(int_val);
|
|
129
|
+
throw std::runtime_error(error_msg);
|
|
130
|
+
}
|
|
131
|
+
return Napi::Number::New(env_, static_cast<double>(int_val));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case SQLITE_FLOAT: {
|
|
136
|
+
double double_val = sqlite3_value_double(value);
|
|
137
|
+
return Napi::Number::New(env_, double_val);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
case SQLITE_TEXT: {
|
|
141
|
+
const unsigned char *text = sqlite3_value_text(value);
|
|
142
|
+
return Napi::String::New(env_, reinterpret_cast<const char *>(text));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case SQLITE_BLOB: {
|
|
146
|
+
const void *blob_data = sqlite3_value_blob(value);
|
|
147
|
+
int blob_size = sqlite3_value_bytes(value);
|
|
148
|
+
return Napi::Buffer<uint8_t>::Copy(
|
|
149
|
+
env_, static_cast<const uint8_t *>(blob_data), blob_size);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case SQLITE_NULL:
|
|
153
|
+
default:
|
|
154
|
+
return env_.Null();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
void UserDefinedFunction::JSValueToSqliteResult(sqlite3_context *ctx,
|
|
159
|
+
Napi::Value value) {
|
|
160
|
+
if (value.IsNull() || value.IsUndefined()) {
|
|
161
|
+
sqlite3_result_null(ctx);
|
|
162
|
+
} else if (value.IsBoolean()) {
|
|
163
|
+
// Check boolean BEFORE number, since boolean values can also be numbers
|
|
164
|
+
bool bool_val = value.As<Napi::Boolean>().Value();
|
|
165
|
+
sqlite3_result_int(ctx, bool_val ? 1 : 0);
|
|
166
|
+
} else if (value.IsBigInt()) {
|
|
167
|
+
// Check BigInt BEFORE number to handle large integers properly
|
|
168
|
+
bool lossless;
|
|
169
|
+
int64_t bigint_val = value.As<Napi::BigInt>().Int64Value(&lossless);
|
|
170
|
+
if (lossless) {
|
|
171
|
+
sqlite3_result_int64(ctx, static_cast<sqlite3_int64>(bigint_val));
|
|
172
|
+
} else {
|
|
173
|
+
// BigInt too large, convert to text representation
|
|
174
|
+
std::string bigint_str = value.As<Napi::BigInt>().ToString().Utf8Value();
|
|
175
|
+
try {
|
|
176
|
+
sqlite3_result_text(ctx, bigint_str.c_str(),
|
|
177
|
+
SafeCastToInt(bigint_str.length()),
|
|
178
|
+
SQLITE_TRANSIENT);
|
|
179
|
+
} catch (const std::overflow_error &) {
|
|
180
|
+
sqlite3_result_error(ctx, "BigInt string representation too long", -1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} else if (value.IsNumber()) {
|
|
184
|
+
double num_val = value.As<Napi::Number>().DoubleValue();
|
|
185
|
+
|
|
186
|
+
// Check if it's an integer value
|
|
187
|
+
// Note: We cast INT64_MIN/MAX to double to avoid implicit conversion
|
|
188
|
+
// warnings
|
|
189
|
+
if (std::floor(num_val) == num_val &&
|
|
190
|
+
num_val >= static_cast<double>(INT64_MIN) &&
|
|
191
|
+
num_val <= static_cast<double>(INT64_MAX)) {
|
|
192
|
+
sqlite3_result_int64(ctx, static_cast<sqlite3_int64>(num_val));
|
|
193
|
+
} else {
|
|
194
|
+
sqlite3_result_double(ctx, num_val);
|
|
195
|
+
}
|
|
196
|
+
} else if (value.IsString()) {
|
|
197
|
+
std::string str_val = value.As<Napi::String>().Utf8Value();
|
|
198
|
+
try {
|
|
199
|
+
sqlite3_result_text(ctx, str_val.c_str(), SafeCastToInt(str_val.length()),
|
|
200
|
+
SQLITE_TRANSIENT);
|
|
201
|
+
} catch (const std::overflow_error &) {
|
|
202
|
+
sqlite3_result_error(ctx, "String value too long", -1);
|
|
203
|
+
}
|
|
204
|
+
} else if (value.IsBuffer()) {
|
|
205
|
+
Napi::Buffer<uint8_t> buffer = value.As<Napi::Buffer<uint8_t>>();
|
|
206
|
+
try {
|
|
207
|
+
sqlite3_result_blob(ctx, buffer.Data(), SafeCastToInt(buffer.Length()),
|
|
208
|
+
SQLITE_TRANSIENT);
|
|
209
|
+
} catch (const std::overflow_error &) {
|
|
210
|
+
sqlite3_result_error(ctx, "Buffer too large", -1);
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
// For any other type, convert to string
|
|
214
|
+
std::string str_val = value.ToString().Utf8Value();
|
|
215
|
+
try {
|
|
216
|
+
sqlite3_result_text(ctx, str_val.c_str(), SafeCastToInt(str_val.length()),
|
|
217
|
+
SQLITE_TRANSIENT);
|
|
218
|
+
} catch (const std::overflow_error &) {
|
|
219
|
+
sqlite3_result_error(ctx, "Converted string value too long", -1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
} // namespace sqlite
|
|
225
|
+
} // namespace photostructure
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#ifndef SRC_USER_FUNCTION_H_
|
|
2
|
+
#define SRC_USER_FUNCTION_H_
|
|
3
|
+
|
|
4
|
+
#include <napi.h>
|
|
5
|
+
#include <sqlite3.h>
|
|
6
|
+
|
|
7
|
+
#include <string>
|
|
8
|
+
|
|
9
|
+
namespace photostructure {
|
|
10
|
+
namespace sqlite {
|
|
11
|
+
|
|
12
|
+
// Forward declaration
|
|
13
|
+
class DatabaseSync;
|
|
14
|
+
|
|
15
|
+
// User-defined function wrapper for SQLite callbacks
|
|
16
|
+
class UserDefinedFunction {
|
|
17
|
+
public:
|
|
18
|
+
UserDefinedFunction(Napi::Env env, Napi::Function fn, DatabaseSync *db,
|
|
19
|
+
bool use_bigint_args);
|
|
20
|
+
~UserDefinedFunction();
|
|
21
|
+
|
|
22
|
+
// SQLite callback functions
|
|
23
|
+
static void xFunc(sqlite3_context *ctx, int argc, sqlite3_value **argv);
|
|
24
|
+
static void xDestroy(void *self);
|
|
25
|
+
|
|
26
|
+
private:
|
|
27
|
+
Napi::Env env_;
|
|
28
|
+
Napi::FunctionReference fn_;
|
|
29
|
+
DatabaseSync *db_;
|
|
30
|
+
bool use_bigint_args_;
|
|
31
|
+
|
|
32
|
+
// Helper methods
|
|
33
|
+
Napi::Value SqliteValueToJS(sqlite3_value *value);
|
|
34
|
+
void JSValueToSqliteResult(sqlite3_context *ctx, Napi::Value value);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
} // namespace sqlite
|
|
38
|
+
} // namespace photostructure
|
|
39
|
+
|
|
40
|
+
#endif // SRC_USER_FUNCTION_H_
|