@homeofthings/sqlite3 0.0.1-alpha0 → 6.1.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/LICENSE +25 -0
- package/README.md +209 -14
- package/binding.gyp +58 -0
- package/deps/common-sqlite.gypi +60 -0
- package/deps/extract.js +19 -0
- package/deps/sqlite-autoconf-3510300.tar.gz +0 -0
- package/deps/sqlite3.gyp +121 -0
- package/lib/sqlite3-binding.js +1 -0
- package/lib/sqlite3.d.ts +205 -0
- package/lib/sqlite3.js +207 -0
- package/lib/trace.js +38 -0
- package/package.json +79 -3
- package/src/async.h +76 -0
- package/src/backup.cc +418 -0
- package/src/backup.h +209 -0
- package/src/database.cc +751 -0
- package/src/database.h +188 -0
- package/src/gcc-preinclude.h +31 -0
- package/src/macros.h +207 -0
- package/src/node_sqlite3.cc +128 -0
- package/src/statement.cc +939 -0
- package/src/statement.h +244 -0
- package/src/threading.h +10 -0
- package/index.js +0 -1
package/src/database.cc
ADDED
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
#include <cstring>
|
|
2
|
+
#include <napi.h>
|
|
3
|
+
|
|
4
|
+
#include "macros.h"
|
|
5
|
+
#include "database.h"
|
|
6
|
+
#include "statement.h"
|
|
7
|
+
|
|
8
|
+
using namespace node_sqlite3;
|
|
9
|
+
|
|
10
|
+
#if NAPI_VERSION < 6
|
|
11
|
+
Napi::FunctionReference Database::constructor;
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
Napi::Object Database::Init(Napi::Env env, Napi::Object exports) {
|
|
15
|
+
Napi::HandleScope scope(env);
|
|
16
|
+
// declare napi_default_method here as it is only available in Node v14.12.0+
|
|
17
|
+
auto napi_default_method = static_cast<napi_property_attributes>(napi_writable | napi_configurable);
|
|
18
|
+
|
|
19
|
+
auto t = DefineClass(env, "Database", {
|
|
20
|
+
InstanceMethod("close", &Database::Close, napi_default_method),
|
|
21
|
+
InstanceMethod("exec", &Database::Exec, napi_default_method),
|
|
22
|
+
InstanceMethod("wait", &Database::Wait, napi_default_method),
|
|
23
|
+
InstanceMethod("loadExtension", &Database::LoadExtension, napi_default_method),
|
|
24
|
+
InstanceMethod("serialize", &Database::Serialize, napi_default_method),
|
|
25
|
+
InstanceMethod("parallelize", &Database::Parallelize, napi_default_method),
|
|
26
|
+
InstanceMethod("configure", &Database::Configure, napi_default_method),
|
|
27
|
+
InstanceMethod("interrupt", &Database::Interrupt, napi_default_method),
|
|
28
|
+
InstanceAccessor("open", &Database::Open, nullptr)
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
#if NAPI_VERSION < 6
|
|
32
|
+
constructor = Napi::Persistent(t);
|
|
33
|
+
constructor.SuppressDestruct();
|
|
34
|
+
#else
|
|
35
|
+
Napi::FunctionReference* constructor = new Napi::FunctionReference();
|
|
36
|
+
*constructor = Napi::Persistent(t);
|
|
37
|
+
env.SetInstanceData<Napi::FunctionReference>(constructor);
|
|
38
|
+
#endif
|
|
39
|
+
|
|
40
|
+
exports.Set("Database", t);
|
|
41
|
+
return exports;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void Database::Process() {
|
|
45
|
+
auto env = this->Env();
|
|
46
|
+
Napi::HandleScope scope(env);
|
|
47
|
+
|
|
48
|
+
if (!open && locked && !queue.empty()) {
|
|
49
|
+
EXCEPTION(Napi::String::New(env, "Database handle is closed"), SQLITE_MISUSE, exception);
|
|
50
|
+
Napi::Value argv[] = { exception };
|
|
51
|
+
bool called = false;
|
|
52
|
+
|
|
53
|
+
// Call all callbacks with the error object.
|
|
54
|
+
while (!queue.empty()) {
|
|
55
|
+
auto call = std::unique_ptr<Call>(queue.front());
|
|
56
|
+
queue.pop();
|
|
57
|
+
auto baton = std::unique_ptr<Baton>(call->baton);
|
|
58
|
+
Napi::Function cb = baton->callback.Value();
|
|
59
|
+
if (IS_FUNCTION(cb)) {
|
|
60
|
+
TRY_CATCH_CALL(this->Value(), cb, 1, argv);
|
|
61
|
+
called = true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// When we couldn't call a callback function, emit an error on the
|
|
66
|
+
// Database object.
|
|
67
|
+
if (!called) {
|
|
68
|
+
Napi::Value info[] = { Napi::String::New(env, "error"), exception };
|
|
69
|
+
EMIT_EVENT(Value(), 2, info);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
while (open && (!locked || pending == 0) && !queue.empty()) {
|
|
75
|
+
Call *c = queue.front();
|
|
76
|
+
|
|
77
|
+
if (c->exclusive && pending > 0) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
queue.pop();
|
|
82
|
+
std::unique_ptr<Call> call(c);
|
|
83
|
+
locked = call->exclusive;
|
|
84
|
+
call->callback(call->baton);
|
|
85
|
+
|
|
86
|
+
if (locked) break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
void Database::Schedule(Work_Callback callback, Baton* baton, bool exclusive) {
|
|
91
|
+
auto env = this->Env();
|
|
92
|
+
Napi::HandleScope scope(env);
|
|
93
|
+
|
|
94
|
+
if (!open && locked) {
|
|
95
|
+
EXCEPTION(Napi::String::New(env, "Database is closed"), SQLITE_MISUSE, exception);
|
|
96
|
+
Napi::Function cb = baton->callback.Value();
|
|
97
|
+
// We don't call the actual callback, so we have to make sure that
|
|
98
|
+
// the baton gets destroyed.
|
|
99
|
+
delete baton;
|
|
100
|
+
if (IS_FUNCTION(cb)) {
|
|
101
|
+
Napi::Value argv[] = { exception };
|
|
102
|
+
TRY_CATCH_CALL(Value(), cb, 1, argv);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
Napi::Value argv[] = { Napi::String::New(env, "error"), exception };
|
|
106
|
+
EMIT_EVENT(Value(), 2, argv);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!open || ((locked || exclusive || serialize) && pending > 0)) {
|
|
112
|
+
queue.emplace(new Call(callback, baton, exclusive || serialize));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
locked = exclusive;
|
|
116
|
+
callback(baton);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Database::Database(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Database>(info) {
|
|
121
|
+
auto env = info.Env();
|
|
122
|
+
|
|
123
|
+
if (info.Length() <= 0 || !info[0].IsString()) {
|
|
124
|
+
Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
auto filename = info[0].As<Napi::String>().Utf8Value();
|
|
128
|
+
|
|
129
|
+
unsigned int pos = 1;
|
|
130
|
+
|
|
131
|
+
int mode;
|
|
132
|
+
if (info.Length() >= pos && info[pos].IsNumber() && OtherIsInt(info[pos].As<Napi::Number>())) {
|
|
133
|
+
mode = info[pos++].As<Napi::Number>().Int32Value();
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
mode = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Napi::Function callback;
|
|
140
|
+
if (info.Length() >= pos && info[pos].IsFunction()) {
|
|
141
|
+
callback = info[pos++].As<Napi::Function>();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
info.This().As<Napi::Object>().DefineProperty(Napi::PropertyDescriptor::Value("filename", info[0].As<Napi::String>(), napi_default));
|
|
145
|
+
info.This().As<Napi::Object>().DefineProperty(Napi::PropertyDescriptor::Value("mode", Napi::Number::New(env, mode), napi_default));
|
|
146
|
+
|
|
147
|
+
// Start opening the database.
|
|
148
|
+
auto* baton = new OpenBaton(this, callback, filename.c_str(), mode);
|
|
149
|
+
Work_BeginOpen(baton);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
void Database::Work_BeginOpen(Baton* baton) {
|
|
153
|
+
auto env = baton->db->Env();
|
|
154
|
+
CREATE_WORK("sqlite3.Database.Open", Work_Open, Work_AfterOpen);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
void Database::Work_Open(napi_env e, void* data) {
|
|
158
|
+
auto* baton = static_cast<OpenBaton*>(data);
|
|
159
|
+
auto* db = baton->db;
|
|
160
|
+
|
|
161
|
+
baton->status = sqlite3_open_v2(
|
|
162
|
+
baton->filename.c_str(),
|
|
163
|
+
&db->_handle,
|
|
164
|
+
baton->mode,
|
|
165
|
+
NULL
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (baton->status != SQLITE_OK) {
|
|
169
|
+
baton->message = std::string(sqlite3_errmsg(db->_handle));
|
|
170
|
+
sqlite3_close(db->_handle);
|
|
171
|
+
db->_handle = NULL;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Set default database handle values.
|
|
175
|
+
sqlite3_busy_timeout(db->_handle, 1000);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
void Database::Work_AfterOpen(napi_env e, napi_status status, void* data) {
|
|
180
|
+
std::unique_ptr<OpenBaton> baton(static_cast<OpenBaton*>(data));
|
|
181
|
+
|
|
182
|
+
auto* db = baton->db;
|
|
183
|
+
|
|
184
|
+
auto env = db->Env();
|
|
185
|
+
Napi::HandleScope scope(env);
|
|
186
|
+
|
|
187
|
+
Napi::Value argv[1];
|
|
188
|
+
if (baton->status != SQLITE_OK) {
|
|
189
|
+
EXCEPTION(Napi::String::New(env, baton->message.c_str()), baton->status, exception);
|
|
190
|
+
argv[0] = exception;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
db->open = true;
|
|
194
|
+
argv[0] = env.Null();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
Napi::Function cb = baton->callback.Value();
|
|
198
|
+
|
|
199
|
+
if (IS_FUNCTION(cb)) {
|
|
200
|
+
TRY_CATCH_CALL(db->Value(), cb, 1, argv);
|
|
201
|
+
}
|
|
202
|
+
else if (!db->open) {
|
|
203
|
+
Napi::Value info[] = { Napi::String::New(env, "error"), argv[0] };
|
|
204
|
+
EMIT_EVENT(db->Value(), 2, info);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (db->open) {
|
|
208
|
+
Napi::Value info[] = { Napi::String::New(env, "open") };
|
|
209
|
+
EMIT_EVENT(db->Value(), 1, info);
|
|
210
|
+
db->Process();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
Napi::Value Database::Open(const Napi::CallbackInfo& info) {
|
|
215
|
+
auto env = this->Env();
|
|
216
|
+
auto* db = this;
|
|
217
|
+
return Napi::Boolean::New(env, db->open);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
Napi::Value Database::Close(const Napi::CallbackInfo& info) {
|
|
221
|
+
auto env = info.Env();
|
|
222
|
+
auto* db = this;
|
|
223
|
+
OPTIONAL_ARGUMENT_FUNCTION(0, callback);
|
|
224
|
+
|
|
225
|
+
auto* baton = new Baton(db, callback);
|
|
226
|
+
db->Schedule(Work_BeginClose, baton, true);
|
|
227
|
+
|
|
228
|
+
return info.This();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
void Database::Work_BeginClose(Baton* baton) {
|
|
232
|
+
assert(baton->db->locked);
|
|
233
|
+
assert(baton->db->open);
|
|
234
|
+
assert(baton->db->_handle);
|
|
235
|
+
assert(baton->db->pending == 0);
|
|
236
|
+
|
|
237
|
+
baton->db->pending++;
|
|
238
|
+
baton->db->RemoveCallbacks();
|
|
239
|
+
baton->db->closing = true;
|
|
240
|
+
|
|
241
|
+
auto env = baton->db->Env();
|
|
242
|
+
CREATE_WORK("sqlite3.Database.Close", Work_Close, Work_AfterClose);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
void Database::Work_Close(napi_env e, void* data) {
|
|
246
|
+
auto* baton = static_cast<Baton*>(data);
|
|
247
|
+
auto* db = baton->db;
|
|
248
|
+
|
|
249
|
+
baton->status = sqlite3_close(db->_handle);
|
|
250
|
+
|
|
251
|
+
if (baton->status != SQLITE_OK) {
|
|
252
|
+
baton->message = std::string(sqlite3_errmsg(db->_handle));
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
db->_handle = NULL;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
void Database::Work_AfterClose(napi_env e, napi_status status, void* data) {
|
|
260
|
+
std::unique_ptr<Baton> baton(static_cast<Baton*>(data));
|
|
261
|
+
|
|
262
|
+
auto* db = baton->db;
|
|
263
|
+
|
|
264
|
+
auto env = db->Env();
|
|
265
|
+
Napi::HandleScope scope(env);
|
|
266
|
+
|
|
267
|
+
db->pending--;
|
|
268
|
+
db->closing = false;
|
|
269
|
+
|
|
270
|
+
Napi::Value argv[1];
|
|
271
|
+
if (baton->status != SQLITE_OK) {
|
|
272
|
+
EXCEPTION(Napi::String::New(env, baton->message.c_str()), baton->status, exception);
|
|
273
|
+
argv[0] = exception;
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
db->open = false;
|
|
277
|
+
// Leave db->locked to indicate that this db object has reached
|
|
278
|
+
// the end of its life.
|
|
279
|
+
argv[0] = env.Null();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
Napi::Function cb = baton->callback.Value();
|
|
283
|
+
|
|
284
|
+
// Fire callbacks.
|
|
285
|
+
if (IS_FUNCTION(cb)) {
|
|
286
|
+
TRY_CATCH_CALL(db->Value(), cb, 1, argv);
|
|
287
|
+
}
|
|
288
|
+
else if (db->open) {
|
|
289
|
+
Napi::Value info[] = { Napi::String::New(env, "error"), argv[0] };
|
|
290
|
+
EMIT_EVENT(db->Value(), 2, info);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!db->open) {
|
|
294
|
+
Napi::Value info[] = { Napi::String::New(env, "close") };
|
|
295
|
+
EMIT_EVENT(db->Value(), 1, info);
|
|
296
|
+
db->Process();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
Napi::Value Database::Serialize(const Napi::CallbackInfo& info) {
|
|
301
|
+
auto env = this->Env();
|
|
302
|
+
auto* db = this;
|
|
303
|
+
OPTIONAL_ARGUMENT_FUNCTION(0, callback);
|
|
304
|
+
|
|
305
|
+
bool before = db->serialize;
|
|
306
|
+
db->serialize = true;
|
|
307
|
+
|
|
308
|
+
if (!callback.IsEmpty() && callback.IsFunction()) {
|
|
309
|
+
TRY_CATCH_CALL(info.This(), callback, 0, NULL, info.This());
|
|
310
|
+
db->serialize = before;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
db->Process();
|
|
314
|
+
|
|
315
|
+
return info.This();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
Napi::Value Database::Parallelize(const Napi::CallbackInfo& info) {
|
|
319
|
+
auto env = this->Env();
|
|
320
|
+
auto* db = this;
|
|
321
|
+
OPTIONAL_ARGUMENT_FUNCTION(0, callback);
|
|
322
|
+
|
|
323
|
+
auto before = db->serialize;
|
|
324
|
+
db->serialize = false;
|
|
325
|
+
|
|
326
|
+
if (!callback.IsEmpty() && callback.IsFunction()) {
|
|
327
|
+
TRY_CATCH_CALL(info.This(), callback, 0, NULL, info.This());
|
|
328
|
+
db->serialize = before;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
db->Process();
|
|
332
|
+
|
|
333
|
+
return info.This();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
Napi::Value Database::Configure(const Napi::CallbackInfo& info) {
|
|
337
|
+
auto env = this->Env();
|
|
338
|
+
auto* db = this;
|
|
339
|
+
|
|
340
|
+
REQUIRE_ARGUMENTS(2);
|
|
341
|
+
|
|
342
|
+
Napi::Function handle;
|
|
343
|
+
if (info[0].StrictEquals( Napi::String::New(env, "trace"))) {
|
|
344
|
+
auto* baton = new Baton(db, handle);
|
|
345
|
+
db->Schedule(RegisterTraceCallback, baton);
|
|
346
|
+
}
|
|
347
|
+
else if (info[0].StrictEquals( Napi::String::New(env, "profile"))) {
|
|
348
|
+
auto* baton = new Baton(db, handle);
|
|
349
|
+
db->Schedule(RegisterProfileCallback, baton);
|
|
350
|
+
}
|
|
351
|
+
else if (info[0].StrictEquals( Napi::String::New(env, "busyTimeout"))) {
|
|
352
|
+
if (!info[1].IsNumber()) {
|
|
353
|
+
Napi::TypeError::New(env, "Value must be an integer").ThrowAsJavaScriptException();
|
|
354
|
+
return env.Null();
|
|
355
|
+
}
|
|
356
|
+
auto* baton = new Baton(db, handle);
|
|
357
|
+
baton->status = info[1].As<Napi::Number>().Int32Value();
|
|
358
|
+
db->Schedule(SetBusyTimeout, baton);
|
|
359
|
+
}
|
|
360
|
+
else if (info[0].StrictEquals( Napi::String::New(env, "limit"))) {
|
|
361
|
+
REQUIRE_ARGUMENTS(3);
|
|
362
|
+
if (!info[1].IsNumber()) {
|
|
363
|
+
Napi::TypeError::New(env, "limit id must be an integer").ThrowAsJavaScriptException();
|
|
364
|
+
return env.Null();
|
|
365
|
+
}
|
|
366
|
+
if (!info[2].IsNumber()) {
|
|
367
|
+
Napi::TypeError::New(env, "limit value must be an integer").ThrowAsJavaScriptException();
|
|
368
|
+
return env.Null();
|
|
369
|
+
}
|
|
370
|
+
int id = info[1].As<Napi::Number>().Int32Value();
|
|
371
|
+
int value = info[2].As<Napi::Number>().Int32Value();
|
|
372
|
+
Baton* baton = new LimitBaton(db, handle, id, value);
|
|
373
|
+
db->Schedule(SetLimit, baton);
|
|
374
|
+
}
|
|
375
|
+
else if (info[0].StrictEquals(Napi::String::New(env, "change"))) {
|
|
376
|
+
auto* baton = new Baton(db, handle);
|
|
377
|
+
db->Schedule(RegisterUpdateCallback, baton);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
Napi::TypeError::New(env, (StringConcat(
|
|
381
|
+
#if V8_MAJOR_VERSION > 6
|
|
382
|
+
info.GetIsolate(),
|
|
383
|
+
#endif
|
|
384
|
+
info[0].As<Napi::String>(),
|
|
385
|
+
Napi::String::New(env, " is not a valid configuration option")
|
|
386
|
+
)).Utf8Value().c_str()).ThrowAsJavaScriptException();
|
|
387
|
+
return env.Null();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
db->Process();
|
|
391
|
+
|
|
392
|
+
return info.This();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
Napi::Value Database::Interrupt(const Napi::CallbackInfo& info) {
|
|
396
|
+
auto env = this->Env();
|
|
397
|
+
auto* db = this;
|
|
398
|
+
|
|
399
|
+
if (!db->open) {
|
|
400
|
+
Napi::Error::New(env, "Database is not open").ThrowAsJavaScriptException();
|
|
401
|
+
return env.Null();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (db->closing) {
|
|
405
|
+
Napi::Error::New(env, "Database is closing").ThrowAsJavaScriptException();
|
|
406
|
+
return env.Null();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
sqlite3_interrupt(db->_handle);
|
|
410
|
+
return info.This();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
void Database::SetBusyTimeout(Baton* b) {
|
|
414
|
+
auto baton = std::unique_ptr<Baton>(b);
|
|
415
|
+
|
|
416
|
+
assert(baton->db->open);
|
|
417
|
+
assert(baton->db->_handle);
|
|
418
|
+
|
|
419
|
+
// Abuse the status field for passing the timeout.
|
|
420
|
+
sqlite3_busy_timeout(baton->db->_handle, baton->status);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
void Database::SetLimit(Baton* b) {
|
|
424
|
+
std::unique_ptr<LimitBaton> baton(static_cast<LimitBaton*>(b));
|
|
425
|
+
|
|
426
|
+
assert(baton->db->open);
|
|
427
|
+
assert(baton->db->_handle);
|
|
428
|
+
|
|
429
|
+
sqlite3_limit(baton->db->_handle, baton->id, baton->value);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
void Database::RegisterTraceCallback(Baton* b) {
|
|
433
|
+
auto baton = std::unique_ptr<Baton>(b);
|
|
434
|
+
assert(baton->db->open);
|
|
435
|
+
assert(baton->db->_handle);
|
|
436
|
+
auto* db = baton->db;
|
|
437
|
+
|
|
438
|
+
if (db->debug_trace == NULL) {
|
|
439
|
+
// Add it.
|
|
440
|
+
db->debug_trace = new AsyncTrace(db, TraceCallback);
|
|
441
|
+
sqlite3_trace(db->_handle, TraceCallback, db);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
// Remove it.
|
|
445
|
+
sqlite3_trace(db->_handle, NULL, NULL);
|
|
446
|
+
db->debug_trace->finish();
|
|
447
|
+
db->debug_trace = NULL;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
void Database::TraceCallback(void* db, const char* sql) {
|
|
452
|
+
// Note: This function is called in the thread pool.
|
|
453
|
+
// Note: Some queries, such as "EXPLAIN" queries, are not sent through this.
|
|
454
|
+
static_cast<Database*>(db)->debug_trace->send(new std::string(sql));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
void Database::TraceCallback(Database* db, std::string* s) {
|
|
458
|
+
std::unique_ptr<std::string> sql(s);
|
|
459
|
+
// Note: This function is called in the main V8 thread.
|
|
460
|
+
auto env = db->Env();
|
|
461
|
+
Napi::HandleScope scope(env);
|
|
462
|
+
|
|
463
|
+
Napi::Value argv[] = {
|
|
464
|
+
Napi::String::New(env, "trace"),
|
|
465
|
+
Napi::String::New(env, sql->c_str())
|
|
466
|
+
};
|
|
467
|
+
EMIT_EVENT(db->Value(), 2, argv);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
void Database::RegisterProfileCallback(Baton* b) {
|
|
471
|
+
auto baton = std::unique_ptr<Baton>(b);
|
|
472
|
+
assert(baton->db->open);
|
|
473
|
+
assert(baton->db->_handle);
|
|
474
|
+
auto* db = baton->db;
|
|
475
|
+
|
|
476
|
+
if (db->debug_profile == NULL) {
|
|
477
|
+
// Add it.
|
|
478
|
+
db->debug_profile = new AsyncProfile(db, ProfileCallback);
|
|
479
|
+
sqlite3_profile(db->_handle, ProfileCallback, db);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
// Remove it.
|
|
483
|
+
sqlite3_profile(db->_handle, NULL, NULL);
|
|
484
|
+
db->debug_profile->finish();
|
|
485
|
+
db->debug_profile = NULL;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
void Database::ProfileCallback(void* db, const char* sql, sqlite3_uint64 nsecs) {
|
|
490
|
+
// Note: This function is called in the thread pool.
|
|
491
|
+
// Note: Some queries, such as "EXPLAIN" queries, are not sent through this.
|
|
492
|
+
auto* info = new ProfileInfo();
|
|
493
|
+
info->sql = std::string(sql);
|
|
494
|
+
info->nsecs = nsecs;
|
|
495
|
+
static_cast<Database*>(db)->debug_profile->send(info);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
void Database::ProfileCallback(Database *db, ProfileInfo* i) {
|
|
499
|
+
auto info = std::unique_ptr<ProfileInfo>(i);
|
|
500
|
+
auto env = db->Env();
|
|
501
|
+
Napi::HandleScope scope(env);
|
|
502
|
+
|
|
503
|
+
Napi::Value argv[] = {
|
|
504
|
+
Napi::String::New(env, "profile"),
|
|
505
|
+
Napi::String::New(env, info->sql.c_str()),
|
|
506
|
+
Napi::Number::New(env, (double)info->nsecs / 1000000.0)
|
|
507
|
+
};
|
|
508
|
+
EMIT_EVENT(db->Value(), 3, argv);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
void Database::RegisterUpdateCallback(Baton* b) {
|
|
512
|
+
auto baton = std::unique_ptr<Baton>(b);
|
|
513
|
+
assert(baton->db->open);
|
|
514
|
+
assert(baton->db->_handle);
|
|
515
|
+
auto* db = baton->db;
|
|
516
|
+
|
|
517
|
+
if (db->update_event == NULL) {
|
|
518
|
+
// Add it.
|
|
519
|
+
db->update_event = new AsyncUpdate(db, UpdateCallback);
|
|
520
|
+
sqlite3_update_hook(db->_handle, UpdateCallback, db);
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
// Remove it.
|
|
524
|
+
sqlite3_update_hook(db->_handle, NULL, NULL);
|
|
525
|
+
db->update_event->finish();
|
|
526
|
+
db->update_event = NULL;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
void Database::UpdateCallback(void* db, int type, const char* database,
|
|
531
|
+
const char* table, sqlite3_int64 rowid) {
|
|
532
|
+
// Note: This function is called in the thread pool.
|
|
533
|
+
// Note: Some queries, such as "EXPLAIN" queries, are not sent through this.
|
|
534
|
+
auto* info = new UpdateInfo();
|
|
535
|
+
info->type = type;
|
|
536
|
+
info->database = std::string(database);
|
|
537
|
+
info->table = std::string(table);
|
|
538
|
+
info->rowid = rowid;
|
|
539
|
+
static_cast<Database*>(db)->update_event->send(info);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
void Database::UpdateCallback(Database *db, UpdateInfo* i) {
|
|
543
|
+
auto info = std::unique_ptr<UpdateInfo>(i);
|
|
544
|
+
auto env = db->Env();
|
|
545
|
+
Napi::HandleScope scope(env);
|
|
546
|
+
|
|
547
|
+
Napi::Value argv[] = {
|
|
548
|
+
Napi::String::New(env, "change"),
|
|
549
|
+
Napi::String::New(env, sqlite_authorizer_string(info->type)),
|
|
550
|
+
Napi::String::New(env, info->database.c_str()),
|
|
551
|
+
Napi::String::New(env, info->table.c_str()),
|
|
552
|
+
Napi::Number::New(env, info->rowid),
|
|
553
|
+
};
|
|
554
|
+
EMIT_EVENT(db->Value(), 5, argv);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
Napi::Value Database::Exec(const Napi::CallbackInfo& info) {
|
|
558
|
+
auto env = this->Env();
|
|
559
|
+
auto* db = this;
|
|
560
|
+
|
|
561
|
+
REQUIRE_ARGUMENT_STRING(0, sql);
|
|
562
|
+
OPTIONAL_ARGUMENT_FUNCTION(1, callback);
|
|
563
|
+
|
|
564
|
+
Baton* baton = new ExecBaton(db, callback, sql.c_str());
|
|
565
|
+
db->Schedule(Work_BeginExec, baton, true);
|
|
566
|
+
|
|
567
|
+
return info.This();
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
void Database::Work_BeginExec(Baton* baton) {
|
|
571
|
+
assert(baton->db->locked);
|
|
572
|
+
assert(baton->db->open);
|
|
573
|
+
assert(baton->db->_handle);
|
|
574
|
+
assert(baton->db->pending == 0);
|
|
575
|
+
baton->db->pending++;
|
|
576
|
+
|
|
577
|
+
auto env = baton->db->Env();
|
|
578
|
+
CREATE_WORK("sqlite3.Database.Exec", Work_Exec, Work_AfterExec);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
void Database::Work_Exec(napi_env e, void* data) {
|
|
582
|
+
auto* baton = static_cast<ExecBaton*>(data);
|
|
583
|
+
|
|
584
|
+
char* message = NULL;
|
|
585
|
+
baton->status = sqlite3_exec(
|
|
586
|
+
baton->db->_handle,
|
|
587
|
+
baton->sql.c_str(),
|
|
588
|
+
NULL,
|
|
589
|
+
NULL,
|
|
590
|
+
&message
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
if (baton->status != SQLITE_OK && message != NULL) {
|
|
594
|
+
baton->message = std::string(message);
|
|
595
|
+
sqlite3_free(message);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
void Database::Work_AfterExec(napi_env e, napi_status status, void* data) {
|
|
600
|
+
std::unique_ptr<ExecBaton> baton(static_cast<ExecBaton*>(data));
|
|
601
|
+
|
|
602
|
+
auto* db = baton->db;
|
|
603
|
+
db->pending--;
|
|
604
|
+
|
|
605
|
+
auto env = db->Env();
|
|
606
|
+
Napi::HandleScope scope(env);
|
|
607
|
+
|
|
608
|
+
Napi::Function cb = baton->callback.Value();
|
|
609
|
+
|
|
610
|
+
if (baton->status != SQLITE_OK) {
|
|
611
|
+
EXCEPTION(Napi::String::New(env, baton->message.c_str()), baton->status, exception);
|
|
612
|
+
|
|
613
|
+
if (IS_FUNCTION(cb)) {
|
|
614
|
+
Napi::Value argv[] = { exception };
|
|
615
|
+
TRY_CATCH_CALL(db->Value(), cb, 1, argv);
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
Napi::Value info[] = { Napi::String::New(env, "error"), exception };
|
|
619
|
+
EMIT_EVENT(db->Value(), 2, info);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
else if (IS_FUNCTION(cb)) {
|
|
623
|
+
Napi::Value argv[] = { env.Null() };
|
|
624
|
+
TRY_CATCH_CALL(db->Value(), cb, 1, argv);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
db->Process();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
Napi::Value Database::Wait(const Napi::CallbackInfo& info) {
|
|
631
|
+
auto env = info.Env();
|
|
632
|
+
auto* db = this;
|
|
633
|
+
|
|
634
|
+
OPTIONAL_ARGUMENT_FUNCTION(0, callback);
|
|
635
|
+
|
|
636
|
+
auto* baton = new Baton(db, callback);
|
|
637
|
+
db->Schedule(Work_Wait, baton, true);
|
|
638
|
+
|
|
639
|
+
return info.This();
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
void Database::Work_Wait(Baton* b) {
|
|
643
|
+
auto baton = std::unique_ptr<Baton>(b);
|
|
644
|
+
|
|
645
|
+
auto env = baton->db->Env();
|
|
646
|
+
Napi::HandleScope scope(env);
|
|
647
|
+
|
|
648
|
+
assert(baton->db->locked);
|
|
649
|
+
assert(baton->db->open);
|
|
650
|
+
assert(baton->db->_handle);
|
|
651
|
+
assert(baton->db->pending == 0);
|
|
652
|
+
|
|
653
|
+
Napi::Function cb = baton->callback.Value();
|
|
654
|
+
if (IS_FUNCTION(cb)) {
|
|
655
|
+
Napi::Value argv[] = { env.Null() };
|
|
656
|
+
TRY_CATCH_CALL(baton->db->Value(), cb, 1, argv);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
baton->db->Process();
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
Napi::Value Database::LoadExtension(const Napi::CallbackInfo& info) {
|
|
663
|
+
auto env = this->Env();
|
|
664
|
+
auto* db = this;
|
|
665
|
+
|
|
666
|
+
REQUIRE_ARGUMENT_STRING(0, filename);
|
|
667
|
+
OPTIONAL_ARGUMENT_FUNCTION(1, callback);
|
|
668
|
+
|
|
669
|
+
Baton* baton = new LoadExtensionBaton(db, callback, filename.c_str());
|
|
670
|
+
db->Schedule(Work_BeginLoadExtension, baton, true);
|
|
671
|
+
|
|
672
|
+
return info.This();
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
void Database::Work_BeginLoadExtension(Baton* baton) {
|
|
676
|
+
assert(baton->db->locked);
|
|
677
|
+
assert(baton->db->open);
|
|
678
|
+
assert(baton->db->_handle);
|
|
679
|
+
assert(baton->db->pending == 0);
|
|
680
|
+
baton->db->pending++;
|
|
681
|
+
|
|
682
|
+
auto env = baton->db->Env();
|
|
683
|
+
CREATE_WORK("sqlite3.Database.LoadExtension", Work_LoadExtension, Work_AfterLoadExtension);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
void Database::Work_LoadExtension(napi_env e, void* data) {
|
|
687
|
+
auto* baton = static_cast<LoadExtensionBaton*>(data);
|
|
688
|
+
|
|
689
|
+
sqlite3_enable_load_extension(baton->db->_handle, 1);
|
|
690
|
+
|
|
691
|
+
char* message = NULL;
|
|
692
|
+
baton->status = sqlite3_load_extension(
|
|
693
|
+
baton->db->_handle,
|
|
694
|
+
baton->filename.c_str(),
|
|
695
|
+
0,
|
|
696
|
+
&message
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
sqlite3_enable_load_extension(baton->db->_handle, 0);
|
|
700
|
+
|
|
701
|
+
if (baton->status != SQLITE_OK && message != NULL) {
|
|
702
|
+
baton->message = std::string(message);
|
|
703
|
+
sqlite3_free(message);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
void Database::Work_AfterLoadExtension(napi_env e, napi_status status, void* data) {
|
|
708
|
+
std::unique_ptr<LoadExtensionBaton> baton(static_cast<LoadExtensionBaton*>(data));
|
|
709
|
+
|
|
710
|
+
auto* db = baton->db;
|
|
711
|
+
db->pending--;
|
|
712
|
+
|
|
713
|
+
auto env = db->Env();
|
|
714
|
+
Napi::HandleScope scope(env);
|
|
715
|
+
|
|
716
|
+
Napi::Function cb = baton->callback.Value();
|
|
717
|
+
|
|
718
|
+
if (baton->status != SQLITE_OK) {
|
|
719
|
+
EXCEPTION(Napi::String::New(env, baton->message.c_str()), baton->status, exception);
|
|
720
|
+
|
|
721
|
+
if (IS_FUNCTION(cb)) {
|
|
722
|
+
Napi::Value argv[] = { exception };
|
|
723
|
+
TRY_CATCH_CALL(db->Value(), cb, 1, argv);
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
Napi::Value info[] = { Napi::String::New(env, "error"), exception };
|
|
727
|
+
EMIT_EVENT(db->Value(), 2, info);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
else if (IS_FUNCTION(cb)) {
|
|
731
|
+
Napi::Value argv[] = { env.Null() };
|
|
732
|
+
TRY_CATCH_CALL(db->Value(), cb, 1, argv);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
db->Process();
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
void Database::RemoveCallbacks() {
|
|
739
|
+
if (debug_trace) {
|
|
740
|
+
debug_trace->finish();
|
|
741
|
+
debug_trace = NULL;
|
|
742
|
+
}
|
|
743
|
+
if (debug_profile) {
|
|
744
|
+
debug_profile->finish();
|
|
745
|
+
debug_profile = NULL;
|
|
746
|
+
}
|
|
747
|
+
if (update_event) {
|
|
748
|
+
update_event->finish();
|
|
749
|
+
update_event = NULL;
|
|
750
|
+
}
|
|
751
|
+
}
|