@barakxyz/better-sqlite3 12.6.2
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/LICENSE +21 -0
- package/README.md +99 -0
- package/binding.gyp +42 -0
- package/deps/common.gypi +68 -0
- package/deps/copy.js +31 -0
- package/deps/defines.gypi +43 -0
- package/deps/download.sh +122 -0
- package/deps/patches/1208.patch +15 -0
- package/deps/sqlite3/sqlite3.c +265969 -0
- package/deps/sqlite3/sqlite3.h +13968 -0
- package/deps/sqlite3/sqlite3ext.h +730 -0
- package/deps/sqlite3.gyp +80 -0
- package/deps/test_extension.c +21 -0
- package/lib/database.js +93 -0
- package/lib/index.js +3 -0
- package/lib/methods/aggregate.js +43 -0
- package/lib/methods/backup.js +67 -0
- package/lib/methods/function.js +31 -0
- package/lib/methods/inspect.js +7 -0
- package/lib/methods/pragma.js +12 -0
- package/lib/methods/serialize.js +16 -0
- package/lib/methods/table.js +189 -0
- package/lib/methods/transaction.js +78 -0
- package/lib/methods/wrappers.js +73 -0
- package/lib/sqlite-error.js +20 -0
- package/lib/util.js +12 -0
- package/package.json +59 -0
- package/src/addon.cpp +48 -0
- package/src/better_sqlite3.cpp +79 -0
- package/src/objects/backup.cpp +120 -0
- package/src/objects/backup.hpp +36 -0
- package/src/objects/database.cpp +521 -0
- package/src/objects/database.hpp +116 -0
- package/src/objects/session.cpp +148 -0
- package/src/objects/session.hpp +33 -0
- package/src/objects/statement-iterator.cpp +113 -0
- package/src/objects/statement-iterator.hpp +50 -0
- package/src/objects/statement.cpp +383 -0
- package/src/objects/statement.hpp +58 -0
- package/src/util/bind-map.cpp +73 -0
- package/src/util/binder.cpp +193 -0
- package/src/util/constants.cpp +172 -0
- package/src/util/custom-aggregate.cpp +121 -0
- package/src/util/custom-function.cpp +59 -0
- package/src/util/custom-table.cpp +409 -0
- package/src/util/data-converter.cpp +17 -0
- package/src/util/data.cpp +194 -0
- package/src/util/helpers.cpp +109 -0
- package/src/util/macros.cpp +70 -0
- package/src/util/query-macros.cpp +71 -0
- package/src/util/row-builder.cpp +49 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
const int Database::MAX_BUFFER_SIZE = (
|
|
2
|
+
node::Buffer::kMaxLength > INT_MAX
|
|
3
|
+
? INT_MAX
|
|
4
|
+
: static_cast<int>(node::Buffer::kMaxLength)
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
const int Database::MAX_STRING_SIZE = (
|
|
8
|
+
v8::String::kMaxLength > INT_MAX
|
|
9
|
+
? INT_MAX
|
|
10
|
+
: static_cast<int>(v8::String::kMaxLength)
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
Database::Database(
|
|
14
|
+
v8::Isolate* isolate,
|
|
15
|
+
Addon* addon,
|
|
16
|
+
sqlite3* db_handle,
|
|
17
|
+
v8::Local<v8::Value> logger
|
|
18
|
+
) :
|
|
19
|
+
node::ObjectWrap(),
|
|
20
|
+
db_handle(db_handle),
|
|
21
|
+
open(true),
|
|
22
|
+
busy(false),
|
|
23
|
+
safe_ints(false),
|
|
24
|
+
unsafe_mode(false),
|
|
25
|
+
was_js_error(false),
|
|
26
|
+
has_logger(logger->IsFunction()),
|
|
27
|
+
iterators(0),
|
|
28
|
+
addon(addon),
|
|
29
|
+
logger(isolate, logger),
|
|
30
|
+
stmts(),
|
|
31
|
+
backups(),
|
|
32
|
+
sessions() {
|
|
33
|
+
assert(db_handle != NULL);
|
|
34
|
+
addon->dbs.insert(this);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Database::~Database() {
|
|
38
|
+
if (open) addon->dbs.erase(this);
|
|
39
|
+
CloseHandles();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Whenever this is used, addon->dbs.erase() must be invoked beforehand.
|
|
43
|
+
void Database::CloseHandles() {
|
|
44
|
+
if (open) {
|
|
45
|
+
open = false;
|
|
46
|
+
for (Statement* stmt : stmts) stmt->CloseHandles();
|
|
47
|
+
for (Backup* backup : backups) backup->CloseHandles();
|
|
48
|
+
for (Session* session : sessions) session->CloseHandles();
|
|
49
|
+
stmts.clear();
|
|
50
|
+
backups.clear();
|
|
51
|
+
sessions.clear();
|
|
52
|
+
int status = sqlite3_close(db_handle);
|
|
53
|
+
assert(status == SQLITE_OK); ((void)status);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void Database::ThrowDatabaseError() {
|
|
58
|
+
if (was_js_error) was_js_error = false;
|
|
59
|
+
else ThrowSqliteError(addon, db_handle);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
void Database::ThrowSqliteError(Addon* addon, sqlite3* db_handle) {
|
|
63
|
+
assert(db_handle != NULL);
|
|
64
|
+
ThrowSqliteError(addon, sqlite3_errmsg(db_handle), sqlite3_extended_errcode(db_handle));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
void Database::ThrowSqliteError(Addon* addon, const char* message, int code) {
|
|
68
|
+
assert(message != NULL);
|
|
69
|
+
assert((code & 0xff) != SQLITE_OK);
|
|
70
|
+
assert((code & 0xff) != SQLITE_ROW);
|
|
71
|
+
assert((code & 0xff) != SQLITE_DONE);
|
|
72
|
+
EasyIsolate;
|
|
73
|
+
v8::Local<v8::Value> args[2] = {
|
|
74
|
+
StringFromUtf8(isolate, message, -1),
|
|
75
|
+
addon->cs.Code(isolate, code)
|
|
76
|
+
};
|
|
77
|
+
isolate->ThrowException(addon->SqliteError.Get(isolate)
|
|
78
|
+
->NewInstance(OnlyContext, 2, args)
|
|
79
|
+
.ToLocalChecked());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Allows Statements to log their executed SQL.
|
|
83
|
+
bool Database::Log(v8::Isolate* isolate, sqlite3_stmt* handle) {
|
|
84
|
+
assert(was_js_error == false);
|
|
85
|
+
if (!has_logger) return false;
|
|
86
|
+
char* expanded = sqlite3_expanded_sql(handle);
|
|
87
|
+
v8::Local<v8::Value> arg = StringFromUtf8(isolate, expanded ? expanded : sqlite3_sql(handle), -1);
|
|
88
|
+
was_js_error = logger.Get(isolate).As<v8::Function>()
|
|
89
|
+
->Call(OnlyContext, v8::Undefined(isolate), 1, &arg)
|
|
90
|
+
.IsEmpty();
|
|
91
|
+
if (expanded) sqlite3_free(expanded);
|
|
92
|
+
return was_js_error;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
bool Database::Deserialize(
|
|
96
|
+
v8::Local<v8::Object> buffer,
|
|
97
|
+
Addon* addon,
|
|
98
|
+
sqlite3* db_handle,
|
|
99
|
+
bool readonly
|
|
100
|
+
) {
|
|
101
|
+
size_t length = node::Buffer::Length(buffer);
|
|
102
|
+
unsigned char* data = (unsigned char*)sqlite3_malloc64(length);
|
|
103
|
+
unsigned int flags = SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE;
|
|
104
|
+
|
|
105
|
+
if (readonly) {
|
|
106
|
+
flags |= SQLITE_DESERIALIZE_READONLY;
|
|
107
|
+
}
|
|
108
|
+
if (length) {
|
|
109
|
+
if (!data) {
|
|
110
|
+
ThrowError("Out of memory");
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
memcpy(data, node::Buffer::Data(buffer), length);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
int status = sqlite3_deserialize(db_handle, "main", data, length, length, flags);
|
|
117
|
+
if (status != SQLITE_OK) {
|
|
118
|
+
ThrowSqliteError(addon, status == SQLITE_ERROR ? "unable to deserialize database" : sqlite3_errstr(status), status);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
void Database::FreeSerialization(char* data, void* _) {
|
|
126
|
+
sqlite3_free(data);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
INIT(Database::Init) {
|
|
130
|
+
v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "Database");
|
|
131
|
+
SetPrototypeMethod(isolate, data, t, "prepare", JS_prepare);
|
|
132
|
+
SetPrototypeMethod(isolate, data, t, "exec", JS_exec);
|
|
133
|
+
SetPrototypeMethod(isolate, data, t, "backup", JS_backup);
|
|
134
|
+
SetPrototypeMethod(isolate, data, t, "serialize", JS_serialize);
|
|
135
|
+
SetPrototypeMethod(isolate, data, t, "function", JS_function);
|
|
136
|
+
SetPrototypeMethod(isolate, data, t, "aggregate", JS_aggregate);
|
|
137
|
+
SetPrototypeMethod(isolate, data, t, "table", JS_table);
|
|
138
|
+
SetPrototypeMethod(isolate, data, t, "loadExtension", JS_loadExtension);
|
|
139
|
+
SetPrototypeMethod(isolate, data, t, "close", JS_close);
|
|
140
|
+
SetPrototypeMethod(isolate, data, t, "defaultSafeIntegers", JS_defaultSafeIntegers);
|
|
141
|
+
SetPrototypeMethod(isolate, data, t, "unsafeMode", JS_unsafeMode);
|
|
142
|
+
SetPrototypeMethod(isolate, data, t, "createSession", JS_createSession);
|
|
143
|
+
SetPrototypeMethod(isolate, data, t, "applyChangeset", JS_applyChangeset);
|
|
144
|
+
SetPrototypeMethod(isolate, data, t, "invertChangeset", JS_invertChangeset);
|
|
145
|
+
SetPrototypeGetter(isolate, data, t, "open", JS_open);
|
|
146
|
+
SetPrototypeGetter(isolate, data, t, "inTransaction", JS_inTransaction);
|
|
147
|
+
return t->GetFunction(OnlyContext).ToLocalChecked();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
NODE_METHOD(Database::JS_new) {
|
|
151
|
+
assert(info.IsConstructCall());
|
|
152
|
+
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> filename);
|
|
153
|
+
REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> filenameGiven);
|
|
154
|
+
REQUIRE_ARGUMENT_BOOLEAN(third, bool in_memory);
|
|
155
|
+
REQUIRE_ARGUMENT_BOOLEAN(fourth, bool readonly);
|
|
156
|
+
REQUIRE_ARGUMENT_BOOLEAN(fifth, bool must_exist);
|
|
157
|
+
REQUIRE_ARGUMENT_INT32(sixth, int timeout);
|
|
158
|
+
REQUIRE_ARGUMENT_ANY(seventh, v8::Local<v8::Value> logger);
|
|
159
|
+
REQUIRE_ARGUMENT_ANY(eighth, v8::Local<v8::Value> buffer);
|
|
160
|
+
|
|
161
|
+
UseAddon;
|
|
162
|
+
UseIsolate;
|
|
163
|
+
sqlite3* db_handle;
|
|
164
|
+
v8::String::Utf8Value utf8(isolate, filename);
|
|
165
|
+
int mask = readonly ? SQLITE_OPEN_READONLY
|
|
166
|
+
: must_exist ? SQLITE_OPEN_READWRITE
|
|
167
|
+
: (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
|
|
168
|
+
|
|
169
|
+
if (sqlite3_open_v2(*utf8, &db_handle, mask, NULL) != SQLITE_OK) {
|
|
170
|
+
ThrowSqliteError(addon, db_handle);
|
|
171
|
+
int status = sqlite3_close(db_handle);
|
|
172
|
+
assert(status == SQLITE_OK); ((void)status);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
assert(sqlite3_db_mutex(db_handle) == NULL);
|
|
177
|
+
sqlite3_extended_result_codes(db_handle, 1);
|
|
178
|
+
sqlite3_busy_timeout(db_handle, timeout);
|
|
179
|
+
sqlite3_limit(db_handle, SQLITE_LIMIT_LENGTH, MAX_BUFFER_SIZE < MAX_STRING_SIZE ? MAX_BUFFER_SIZE : MAX_STRING_SIZE);
|
|
180
|
+
sqlite3_limit(db_handle, SQLITE_LIMIT_SQL_LENGTH, MAX_STRING_SIZE);
|
|
181
|
+
int status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL);
|
|
182
|
+
assert(status == SQLITE_OK); ((void)status);
|
|
183
|
+
status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL);
|
|
184
|
+
assert(status == SQLITE_OK); ((void)status);
|
|
185
|
+
|
|
186
|
+
if (node::Buffer::HasInstance(buffer) && !Deserialize(buffer.As<v8::Object>(), addon, db_handle, readonly)) {
|
|
187
|
+
int status = sqlite3_close(db_handle);
|
|
188
|
+
assert(status == SQLITE_OK); ((void)status);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
UseContext;
|
|
193
|
+
Database* db = new Database(isolate, addon, db_handle, logger);
|
|
194
|
+
db->Wrap(info.This());
|
|
195
|
+
SetFrozen(isolate, ctx, info.This(), addon->cs.memory, v8::Boolean::New(isolate, in_memory));
|
|
196
|
+
SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, v8::Boolean::New(isolate, readonly));
|
|
197
|
+
SetFrozen(isolate, ctx, info.This(), addon->cs.name, filenameGiven);
|
|
198
|
+
|
|
199
|
+
info.GetReturnValue().Set(info.This());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
NODE_METHOD(Database::JS_prepare) {
|
|
203
|
+
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> source);
|
|
204
|
+
REQUIRE_ARGUMENT_OBJECT(second, v8::Local<v8::Object> database);
|
|
205
|
+
REQUIRE_ARGUMENT_BOOLEAN(third, bool pragmaMode);
|
|
206
|
+
(void)source;
|
|
207
|
+
(void)database;
|
|
208
|
+
(void)pragmaMode;
|
|
209
|
+
UseAddon;
|
|
210
|
+
UseIsolate;
|
|
211
|
+
v8::Local<v8::Function> c = addon->Statement.Get(isolate);
|
|
212
|
+
addon->privileged_info = &info;
|
|
213
|
+
v8::MaybeLocal<v8::Object> maybeStatement = c->NewInstance(OnlyContext, 0, NULL);
|
|
214
|
+
addon->privileged_info = NULL;
|
|
215
|
+
if (!maybeStatement.IsEmpty()) info.GetReturnValue().Set(maybeStatement.ToLocalChecked());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
NODE_METHOD(Database::JS_exec) {
|
|
219
|
+
Database* db = Unwrap<Database>(info.This());
|
|
220
|
+
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> source);
|
|
221
|
+
REQUIRE_DATABASE_OPEN(db);
|
|
222
|
+
REQUIRE_DATABASE_NOT_BUSY(db);
|
|
223
|
+
REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db);
|
|
224
|
+
db->busy = true;
|
|
225
|
+
|
|
226
|
+
UseIsolate;
|
|
227
|
+
v8::String::Utf8Value utf8(isolate, source);
|
|
228
|
+
const char* sql = *utf8;
|
|
229
|
+
const char* tail;
|
|
230
|
+
|
|
231
|
+
int status;
|
|
232
|
+
const bool has_logger = db->has_logger;
|
|
233
|
+
sqlite3* const db_handle = db->db_handle;
|
|
234
|
+
sqlite3_stmt* handle;
|
|
235
|
+
|
|
236
|
+
for (;;) {
|
|
237
|
+
while (IS_SKIPPED(*sql)) ++sql;
|
|
238
|
+
status = sqlite3_prepare_v2(db_handle, sql, -1, &handle, &tail);
|
|
239
|
+
sql = tail;
|
|
240
|
+
if (!handle) break;
|
|
241
|
+
if (has_logger && db->Log(isolate, handle)) {
|
|
242
|
+
sqlite3_finalize(handle);
|
|
243
|
+
status = -1;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
do status = sqlite3_step(handle);
|
|
247
|
+
while (status == SQLITE_ROW);
|
|
248
|
+
status = sqlite3_finalize(handle);
|
|
249
|
+
if (status != SQLITE_OK) break;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
db->busy = false;
|
|
253
|
+
if (status != SQLITE_OK) {
|
|
254
|
+
db->ThrowDatabaseError();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
NODE_METHOD(Database::JS_backup) {
|
|
259
|
+
REQUIRE_ARGUMENT_OBJECT(first, v8::Local<v8::Object> database);
|
|
260
|
+
REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> attachedName);
|
|
261
|
+
REQUIRE_ARGUMENT_STRING(third, v8::Local<v8::String> destFile);
|
|
262
|
+
REQUIRE_ARGUMENT_BOOLEAN(fourth, bool unlink);
|
|
263
|
+
(void)database;
|
|
264
|
+
(void)attachedName;
|
|
265
|
+
(void)destFile;
|
|
266
|
+
(void)unlink;
|
|
267
|
+
UseAddon;
|
|
268
|
+
UseIsolate;
|
|
269
|
+
v8::Local<v8::Function> c = addon->Backup.Get(isolate);
|
|
270
|
+
addon->privileged_info = &info;
|
|
271
|
+
v8::MaybeLocal<v8::Object> maybeBackup = c->NewInstance(OnlyContext, 0, NULL);
|
|
272
|
+
addon->privileged_info = NULL;
|
|
273
|
+
if (!maybeBackup.IsEmpty()) info.GetReturnValue().Set(maybeBackup.ToLocalChecked());
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
NODE_METHOD(Database::JS_serialize) {
|
|
277
|
+
Database* db = Unwrap<Database>(info.This());
|
|
278
|
+
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> attachedName);
|
|
279
|
+
REQUIRE_DATABASE_OPEN(db);
|
|
280
|
+
REQUIRE_DATABASE_NOT_BUSY(db);
|
|
281
|
+
REQUIRE_DATABASE_NO_ITERATORS(db);
|
|
282
|
+
|
|
283
|
+
UseIsolate;
|
|
284
|
+
v8::String::Utf8Value attached_name(isolate, attachedName);
|
|
285
|
+
sqlite3_int64 length = -1;
|
|
286
|
+
unsigned char* data = sqlite3_serialize(db->db_handle, *attached_name, &length, 0);
|
|
287
|
+
|
|
288
|
+
if (!data && length) {
|
|
289
|
+
ThrowError("Out of memory");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
info.GetReturnValue().Set(
|
|
294
|
+
SAFE_NEW_BUFFER(isolate, reinterpret_cast<char*>(data), length, FreeSerialization, NULL).ToLocalChecked()
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
NODE_METHOD(Database::JS_function) {
|
|
299
|
+
Database* db = Unwrap<Database>(info.This());
|
|
300
|
+
REQUIRE_ARGUMENT_FUNCTION(first, v8::Local<v8::Function> fn);
|
|
301
|
+
REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> nameString);
|
|
302
|
+
REQUIRE_ARGUMENT_INT32(third, int argc);
|
|
303
|
+
REQUIRE_ARGUMENT_INT32(fourth, int safe_ints);
|
|
304
|
+
REQUIRE_ARGUMENT_BOOLEAN(fifth, bool deterministic);
|
|
305
|
+
REQUIRE_ARGUMENT_BOOLEAN(sixth, bool direct_only);
|
|
306
|
+
REQUIRE_DATABASE_OPEN(db);
|
|
307
|
+
REQUIRE_DATABASE_NOT_BUSY(db);
|
|
308
|
+
REQUIRE_DATABASE_NO_ITERATORS(db);
|
|
309
|
+
|
|
310
|
+
UseIsolate;
|
|
311
|
+
v8::String::Utf8Value name(isolate, nameString);
|
|
312
|
+
int mask = SQLITE_UTF8;
|
|
313
|
+
if (deterministic) mask |= SQLITE_DETERMINISTIC;
|
|
314
|
+
if (direct_only) mask |= SQLITE_DIRECTONLY;
|
|
315
|
+
safe_ints = safe_ints < 2 ? safe_ints : static_cast<int>(db->safe_ints);
|
|
316
|
+
|
|
317
|
+
if (sqlite3_create_function_v2(db->db_handle, *name, argc, mask, new CustomFunction(isolate, db, *name, fn, safe_ints), CustomFunction::xFunc, NULL, NULL, CustomFunction::xDestroy) != SQLITE_OK) {
|
|
318
|
+
db->ThrowDatabaseError();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
NODE_METHOD(Database::JS_aggregate) {
|
|
323
|
+
Database* db = Unwrap<Database>(info.This());
|
|
324
|
+
REQUIRE_ARGUMENT_ANY(first, v8::Local<v8::Value> start);
|
|
325
|
+
REQUIRE_ARGUMENT_FUNCTION(second, v8::Local<v8::Function> step);
|
|
326
|
+
REQUIRE_ARGUMENT_ANY(third, v8::Local<v8::Value> inverse);
|
|
327
|
+
REQUIRE_ARGUMENT_ANY(fourth, v8::Local<v8::Value> result);
|
|
328
|
+
REQUIRE_ARGUMENT_STRING(fifth, v8::Local<v8::String> nameString);
|
|
329
|
+
REQUIRE_ARGUMENT_INT32(sixth, int argc);
|
|
330
|
+
REQUIRE_ARGUMENT_INT32(seventh, int safe_ints);
|
|
331
|
+
REQUIRE_ARGUMENT_BOOLEAN(eighth, bool deterministic);
|
|
332
|
+
REQUIRE_ARGUMENT_BOOLEAN(ninth, bool direct_only);
|
|
333
|
+
REQUIRE_DATABASE_OPEN(db);
|
|
334
|
+
REQUIRE_DATABASE_NOT_BUSY(db);
|
|
335
|
+
REQUIRE_DATABASE_NO_ITERATORS(db);
|
|
336
|
+
|
|
337
|
+
UseIsolate;
|
|
338
|
+
v8::String::Utf8Value name(isolate, nameString);
|
|
339
|
+
auto xInverse = inverse->IsFunction() ? CustomAggregate::xInverse : NULL;
|
|
340
|
+
auto xValue = xInverse ? CustomAggregate::xValue : NULL;
|
|
341
|
+
int mask = SQLITE_UTF8;
|
|
342
|
+
if (deterministic) mask |= SQLITE_DETERMINISTIC;
|
|
343
|
+
if (direct_only) mask |= SQLITE_DIRECTONLY;
|
|
344
|
+
safe_ints = safe_ints < 2 ? safe_ints : static_cast<int>(db->safe_ints);
|
|
345
|
+
|
|
346
|
+
if (sqlite3_create_window_function(db->db_handle, *name, argc, mask, new CustomAggregate(isolate, db, *name, start, step, inverse, result, safe_ints), CustomAggregate::xStep, CustomAggregate::xFinal, xValue, xInverse, CustomAggregate::xDestroy) != SQLITE_OK) {
|
|
347
|
+
db->ThrowDatabaseError();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
NODE_METHOD(Database::JS_table) {
|
|
352
|
+
Database* db = Unwrap<Database>(info.This());
|
|
353
|
+
REQUIRE_ARGUMENT_FUNCTION(first, v8::Local<v8::Function> factory);
|
|
354
|
+
REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> nameString);
|
|
355
|
+
REQUIRE_ARGUMENT_BOOLEAN(third, bool eponymous);
|
|
356
|
+
REQUIRE_DATABASE_OPEN(db);
|
|
357
|
+
REQUIRE_DATABASE_NOT_BUSY(db);
|
|
358
|
+
REQUIRE_DATABASE_NO_ITERATORS(db);
|
|
359
|
+
|
|
360
|
+
UseIsolate;
|
|
361
|
+
v8::String::Utf8Value name(isolate, nameString);
|
|
362
|
+
sqlite3_module* module = eponymous ? &CustomTable::EPONYMOUS_MODULE : &CustomTable::MODULE;
|
|
363
|
+
|
|
364
|
+
db->busy = true;
|
|
365
|
+
if (sqlite3_create_module_v2(db->db_handle, *name, module, new CustomTable(isolate, db, *name, factory), CustomTable::Destructor) != SQLITE_OK) {
|
|
366
|
+
db->ThrowDatabaseError();
|
|
367
|
+
}
|
|
368
|
+
db->busy = false;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
NODE_METHOD(Database::JS_loadExtension) {
|
|
372
|
+
Database* db = Unwrap<Database>(info.This());
|
|
373
|
+
v8::Local<v8::String> entryPoint;
|
|
374
|
+
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> filename);
|
|
375
|
+
if (info.Length() > 1) { REQUIRE_ARGUMENT_STRING(second, entryPoint); }
|
|
376
|
+
REQUIRE_DATABASE_OPEN(db);
|
|
377
|
+
REQUIRE_DATABASE_NOT_BUSY(db);
|
|
378
|
+
REQUIRE_DATABASE_NO_ITERATORS(db);
|
|
379
|
+
UseIsolate;
|
|
380
|
+
char* error;
|
|
381
|
+
int status = sqlite3_load_extension(
|
|
382
|
+
db->db_handle,
|
|
383
|
+
*v8::String::Utf8Value(isolate, filename),
|
|
384
|
+
entryPoint.IsEmpty() ? NULL : *v8::String::Utf8Value(isolate, entryPoint),
|
|
385
|
+
&error
|
|
386
|
+
);
|
|
387
|
+
if (status != SQLITE_OK) {
|
|
388
|
+
ThrowSqliteError(db->addon, error, status);
|
|
389
|
+
}
|
|
390
|
+
sqlite3_free(error);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
NODE_METHOD(Database::JS_close) {
|
|
394
|
+
Database* db = Unwrap<Database>(info.This());
|
|
395
|
+
if (db->open) {
|
|
396
|
+
REQUIRE_DATABASE_NOT_BUSY(db);
|
|
397
|
+
REQUIRE_DATABASE_NO_ITERATORS(db);
|
|
398
|
+
db->addon->dbs.erase(db);
|
|
399
|
+
db->CloseHandles();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
NODE_METHOD(Database::JS_defaultSafeIntegers) {
|
|
404
|
+
Database* db = Unwrap<Database>(info.This());
|
|
405
|
+
if (info.Length() == 0) db->safe_ints = true;
|
|
406
|
+
else { REQUIRE_ARGUMENT_BOOLEAN(first, db->safe_ints); }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
NODE_METHOD(Database::JS_unsafeMode) {
|
|
410
|
+
Database* db = Unwrap<Database>(info.This());
|
|
411
|
+
if (info.Length() == 0) db->unsafe_mode = true;
|
|
412
|
+
else { REQUIRE_ARGUMENT_BOOLEAN(first, db->unsafe_mode); }
|
|
413
|
+
sqlite3_db_config(db->db_handle, SQLITE_DBCONFIG_DEFENSIVE, static_cast<int>(!db->unsafe_mode), NULL);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
NODE_GETTER(Database::JS_open) {
|
|
417
|
+
info.GetReturnValue().Set(Unwrap<Database>(info.This())->open);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
NODE_GETTER(Database::JS_inTransaction) {
|
|
421
|
+
Database* db = Unwrap<Database>(info.This());
|
|
422
|
+
info.GetReturnValue().Set(db->open && !static_cast<bool>(sqlite3_get_autocommit(db->db_handle)));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
NODE_METHOD(Database::JS_createSession) {
|
|
426
|
+
REQUIRE_ARGUMENT_OBJECT(first, v8::Local<v8::Object> database);
|
|
427
|
+
REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> dbName);
|
|
428
|
+
(void)database;
|
|
429
|
+
(void)dbName;
|
|
430
|
+
UseAddon;
|
|
431
|
+
UseIsolate;
|
|
432
|
+
v8::Local<v8::Function> c = addon->Session.Get(isolate);
|
|
433
|
+
addon->privileged_info = &info;
|
|
434
|
+
v8::MaybeLocal<v8::Object> maybeSession = c->NewInstance(OnlyContext, 0, NULL);
|
|
435
|
+
addon->privileged_info = NULL;
|
|
436
|
+
if (!maybeSession.IsEmpty()) info.GetReturnValue().Set(maybeSession.ToLocalChecked());
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Custom destructor for sqlite3_free used by changeset operations
|
|
440
|
+
static void FreeChangesetMemory(char* data, void* hint) {
|
|
441
|
+
sqlite3_free(data);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
NODE_METHOD(Database::JS_applyChangeset) {
|
|
445
|
+
Database* db = Unwrap<Database>(info.This());
|
|
446
|
+
REQUIRE_DATABASE_OPEN(db);
|
|
447
|
+
REQUIRE_DATABASE_NOT_BUSY(db);
|
|
448
|
+
|
|
449
|
+
if (info.Length() == 0 || !node::Buffer::HasInstance(info[0])) {
|
|
450
|
+
return ThrowTypeError("Expected first argument to be a Buffer");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
v8::Local<v8::Object> buffer = info[0].As<v8::Object>();
|
|
454
|
+
int size = static_cast<int>(node::Buffer::Length(buffer));
|
|
455
|
+
void* data = node::Buffer::Data(buffer);
|
|
456
|
+
|
|
457
|
+
// Conflict handler - default to REPLACE (consistent with expo-sqlite)
|
|
458
|
+
auto onConflict = [](void* pCtx, int eConflict, sqlite3_changeset_iter* pIter) -> int {
|
|
459
|
+
return SQLITE_CHANGESET_REPLACE;
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
int status = sqlite3changeset_apply(
|
|
463
|
+
db->db_handle,
|
|
464
|
+
size,
|
|
465
|
+
data,
|
|
466
|
+
NULL, // xFilter
|
|
467
|
+
onConflict, // xConflict
|
|
468
|
+
NULL // pCtx
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
if (status != SQLITE_OK) {
|
|
472
|
+
db->ThrowDatabaseError();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
NODE_METHOD(Database::JS_invertChangeset) {
|
|
477
|
+
Database* db = Unwrap<Database>(info.This());
|
|
478
|
+
(void)db; // db is only used for addon access pattern consistency
|
|
479
|
+
|
|
480
|
+
if (info.Length() == 0 || !node::Buffer::HasInstance(info[0])) {
|
|
481
|
+
return ThrowTypeError("Expected first argument to be a Buffer");
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
v8::Local<v8::Object> buffer = info[0].As<v8::Object>();
|
|
485
|
+
int inSize = static_cast<int>(node::Buffer::Length(buffer));
|
|
486
|
+
void* inData = node::Buffer::Data(buffer);
|
|
487
|
+
|
|
488
|
+
int outSize = 0;
|
|
489
|
+
void* outData = NULL;
|
|
490
|
+
|
|
491
|
+
int status = sqlite3changeset_invert(inSize, inData, &outSize, &outData);
|
|
492
|
+
|
|
493
|
+
if (status != SQLITE_OK) {
|
|
494
|
+
UseAddon;
|
|
495
|
+
ThrowSqliteError(addon, sqlite3_errstr(status), status);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
UseIsolate;
|
|
500
|
+
|
|
501
|
+
if (outData == NULL || outSize == 0) {
|
|
502
|
+
if (outData) sqlite3_free(outData);
|
|
503
|
+
info.GetReturnValue().Set(v8::Undefined(isolate));
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
auto result = SAFE_NEW_BUFFER(
|
|
508
|
+
isolate,
|
|
509
|
+
reinterpret_cast<char*>(outData),
|
|
510
|
+
outSize,
|
|
511
|
+
FreeChangesetMemory,
|
|
512
|
+
NULL
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
if (result.IsEmpty()) {
|
|
516
|
+
sqlite3_free(outData);
|
|
517
|
+
return ThrowError("Failed to create buffer for inverted changeset");
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
info.GetReturnValue().Set(result.ToLocalChecked());
|
|
521
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
class Database : public node::ObjectWrap {
|
|
2
|
+
public:
|
|
3
|
+
|
|
4
|
+
~Database();
|
|
5
|
+
|
|
6
|
+
// Whenever this is used, addon->dbs.erase() must be invoked beforehand.
|
|
7
|
+
void CloseHandles();
|
|
8
|
+
|
|
9
|
+
// Used to support ordered containers.
|
|
10
|
+
class CompareDatabase { public:
|
|
11
|
+
inline bool operator() (Database const * const a, Database const * const b) const {
|
|
12
|
+
return a < b;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
class CompareStatement { public:
|
|
16
|
+
inline bool operator() (Statement const * const a, Statement const * const b) const {
|
|
17
|
+
return Statement::Compare(a, b);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
class CompareBackup { public:
|
|
21
|
+
inline bool operator() (Backup const * const a, Backup const * const b) const {
|
|
22
|
+
return Backup::Compare(a, b);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
class CompareSession { public:
|
|
26
|
+
inline bool operator() (Session const * const a, Session const * const b) const {
|
|
27
|
+
return Session::Compare(a, b);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Proper error handling logic for when an sqlite3 operation fails.
|
|
32
|
+
void ThrowDatabaseError();
|
|
33
|
+
static void ThrowSqliteError(Addon* addon, sqlite3* db_handle);
|
|
34
|
+
static void ThrowSqliteError(Addon* addon, const char* message, int code);
|
|
35
|
+
|
|
36
|
+
// Allows Statements to log their executed SQL.
|
|
37
|
+
bool Log(v8::Isolate* isolate, sqlite3_stmt* handle);
|
|
38
|
+
|
|
39
|
+
// Allow Statements to manage themselves when created and garbage collected.
|
|
40
|
+
inline void AddStatement(Statement* stmt) { stmts.insert(stmts.end(), stmt); }
|
|
41
|
+
inline void RemoveStatement(Statement* stmt) { stmts.erase(stmt); }
|
|
42
|
+
|
|
43
|
+
// Allow Backups to manage themselves when created and garbage collected.
|
|
44
|
+
inline void AddBackup(Backup* backup) { backups.insert(backups.end(), backup); }
|
|
45
|
+
inline void RemoveBackup(Backup* backup) { backups.erase(backup); }
|
|
46
|
+
|
|
47
|
+
// Allow Sessions to manage themselves when created and garbage collected.
|
|
48
|
+
inline void AddSession(Session* session) { sessions.insert(sessions.end(), session); }
|
|
49
|
+
inline void RemoveSession(Session* session) { sessions.erase(session); }
|
|
50
|
+
|
|
51
|
+
// A view for Statements to see and modify Database state.
|
|
52
|
+
// The order of these fields must exactly match their actual order.
|
|
53
|
+
struct State {
|
|
54
|
+
const bool open;
|
|
55
|
+
bool busy;
|
|
56
|
+
const bool safe_ints;
|
|
57
|
+
const bool unsafe_mode;
|
|
58
|
+
bool was_js_error;
|
|
59
|
+
const bool has_logger;
|
|
60
|
+
unsigned short iterators;
|
|
61
|
+
Addon* const addon;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
inline State* GetState() { return reinterpret_cast<State*>(&open); }
|
|
65
|
+
inline sqlite3* GetHandle() { return db_handle; }
|
|
66
|
+
inline Addon* GetAddon() { return addon; }
|
|
67
|
+
|
|
68
|
+
static INIT(Init);
|
|
69
|
+
|
|
70
|
+
private:
|
|
71
|
+
|
|
72
|
+
explicit Database(
|
|
73
|
+
v8::Isolate* isolate,
|
|
74
|
+
Addon* addon,
|
|
75
|
+
sqlite3* db_handle,
|
|
76
|
+
v8::Local<v8::Value> logger
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
static NODE_METHOD(JS_new);
|
|
80
|
+
static NODE_METHOD(JS_prepare);
|
|
81
|
+
static NODE_METHOD(JS_exec);
|
|
82
|
+
static NODE_METHOD(JS_backup);
|
|
83
|
+
static NODE_METHOD(JS_serialize);
|
|
84
|
+
static NODE_METHOD(JS_function);
|
|
85
|
+
static NODE_METHOD(JS_aggregate);
|
|
86
|
+
static NODE_METHOD(JS_table);
|
|
87
|
+
static NODE_METHOD(JS_loadExtension);
|
|
88
|
+
static NODE_METHOD(JS_close);
|
|
89
|
+
static NODE_METHOD(JS_defaultSafeIntegers);
|
|
90
|
+
static NODE_METHOD(JS_unsafeMode);
|
|
91
|
+
static NODE_METHOD(JS_createSession);
|
|
92
|
+
static NODE_METHOD(JS_applyChangeset);
|
|
93
|
+
static NODE_METHOD(JS_invertChangeset);
|
|
94
|
+
static NODE_GETTER(JS_open);
|
|
95
|
+
static NODE_GETTER(JS_inTransaction);
|
|
96
|
+
|
|
97
|
+
static bool Deserialize(v8::Local<v8::Object> buffer, Addon* addon, sqlite3* db_handle, bool readonly);
|
|
98
|
+
static void FreeSerialization(char* data, void* _);
|
|
99
|
+
|
|
100
|
+
static const int MAX_BUFFER_SIZE;
|
|
101
|
+
static const int MAX_STRING_SIZE;
|
|
102
|
+
|
|
103
|
+
sqlite3* const db_handle;
|
|
104
|
+
bool open;
|
|
105
|
+
bool busy;
|
|
106
|
+
bool safe_ints;
|
|
107
|
+
bool unsafe_mode;
|
|
108
|
+
bool was_js_error;
|
|
109
|
+
const bool has_logger;
|
|
110
|
+
unsigned short iterators;
|
|
111
|
+
Addon* const addon;
|
|
112
|
+
const v8::Global<v8::Value> logger;
|
|
113
|
+
std::set<Statement*, CompareStatement> stmts;
|
|
114
|
+
std::set<Backup*, CompareBackup> backups;
|
|
115
|
+
std::set<Session*, CompareSession> sessions;
|
|
116
|
+
};
|