duckdb 0.4.1-dev211.0 → 0.4.1-dev2145.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/lib/duckdb.js +316 -20
- package/package.json +3 -1
- package/src/connection.cpp +25 -18
- package/src/data_chunk.cpp +2 -2
- package/src/database.cpp +66 -16
- package/src/duckdb.cpp +81222 -40109
- package/src/duckdb.hpp +5550 -2044
- package/src/duckdb_node.cpp +1 -0
- package/src/duckdb_node.hpp +37 -1
- package/src/parquet-amalgamation.cpp +31799 -31154
- package/src/parquet-amalgamation.hpp +112 -114
- package/src/statement.cpp +128 -20
- package/test/data_type_support.test.js +83 -13
- package/test/extension.test.js +1 -1
- package/test/jsdoc.test.js +60 -0
- package/test/query_result.test.js +23 -0
- package/test/syntax_error.test.js +16 -0
package/lib/duckdb.js
CHANGED
|
@@ -1,32 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module duckdb
|
|
3
|
+
* @summary these jsdoc annotations are still a work in progress - feedback and suggestions are welcome!
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
var duckdb = require('./duckdb-binding.js');
|
|
2
7
|
module.exports = exports = duckdb;
|
|
3
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Check that errno attribute equals this to check for a duckdb error
|
|
11
|
+
* @constant {number}
|
|
12
|
+
*/
|
|
13
|
+
var ERROR = duckdb.ERROR;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Open database in readonly mode
|
|
17
|
+
* @constant {number}
|
|
18
|
+
*/
|
|
19
|
+
var OPEN_READONLY = duckdb.OPEN_READONLY;
|
|
20
|
+
/**
|
|
21
|
+
* Currently ignored
|
|
22
|
+
* @constant {number}
|
|
23
|
+
*/
|
|
24
|
+
var OPEN_READWRITE = duckdb.OPEN_READWRITE;
|
|
25
|
+
/**
|
|
26
|
+
* Currently ignored
|
|
27
|
+
* @constant {number}
|
|
28
|
+
*/
|
|
29
|
+
var OPEN_CREATE = duckdb.OPEN_CREATE;
|
|
30
|
+
/**
|
|
31
|
+
* Currently ignored
|
|
32
|
+
* @constant {number}
|
|
33
|
+
*/
|
|
34
|
+
var OPEN_FULLMUTEX = duckdb.OPEN_FULLMUTEX;
|
|
35
|
+
/**
|
|
36
|
+
* Currently ignored
|
|
37
|
+
* @constant {number}
|
|
38
|
+
*/
|
|
39
|
+
var OPEN_SHAREDCACHE = duckdb.OPEN_SHAREDCACHE;
|
|
40
|
+
/**
|
|
41
|
+
* Currently ignored
|
|
42
|
+
* @constant {number}
|
|
43
|
+
*/
|
|
44
|
+
var OPEN_PRIVATECACHE = duckdb.OPEN_PRIVATECACHE;
|
|
4
45
|
|
|
5
46
|
// some wrappers for compatibilities sake
|
|
47
|
+
/**
|
|
48
|
+
* Main database interface
|
|
49
|
+
*/
|
|
6
50
|
var Database = duckdb.Database;
|
|
51
|
+
/**
|
|
52
|
+
* @class
|
|
53
|
+
*/
|
|
7
54
|
var Connection = duckdb.Connection;
|
|
55
|
+
/**
|
|
56
|
+
* @class
|
|
57
|
+
*/
|
|
8
58
|
var Statement = duckdb.Statement;
|
|
59
|
+
/**
|
|
60
|
+
* @class
|
|
61
|
+
*/
|
|
62
|
+
var QueryResult = duckdb.QueryResult;
|
|
9
63
|
|
|
64
|
+
/**
|
|
65
|
+
* @method
|
|
66
|
+
* @return data chunk
|
|
67
|
+
*/
|
|
68
|
+
QueryResult.prototype.nextChunk;
|
|
10
69
|
|
|
11
|
-
|
|
70
|
+
/**
|
|
71
|
+
* @name asyncIterator
|
|
72
|
+
* @memberof module:duckdb~QueryResult
|
|
73
|
+
* @method
|
|
74
|
+
* @instance
|
|
75
|
+
* @yields data chunks
|
|
76
|
+
*/
|
|
77
|
+
QueryResult.prototype[Symbol.asyncIterator] = async function*() {
|
|
78
|
+
let prefetch = this.nextChunk();
|
|
79
|
+
while (true) {
|
|
80
|
+
const chunk = await prefetch;
|
|
81
|
+
// Null chunk indicates end of stream
|
|
82
|
+
if (!chunk) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Prefetch the next chunk while we're iterating
|
|
86
|
+
prefetch = this.nextChunk();
|
|
87
|
+
for (const row of chunk) {
|
|
88
|
+
yield row;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @arg sql
|
|
96
|
+
* @param {...*} params
|
|
97
|
+
* @param callback
|
|
98
|
+
* @return {void}
|
|
99
|
+
*/
|
|
100
|
+
Connection.prototype.run = function (sql) {
|
|
12
101
|
var statement = new Statement(this, sql);
|
|
13
102
|
return statement.run.apply(statement, arguments);
|
|
14
103
|
}
|
|
15
104
|
|
|
16
|
-
|
|
17
|
-
|
|
105
|
+
/**
|
|
106
|
+
* @arg sql
|
|
107
|
+
* @param {...*} params
|
|
108
|
+
* @param callback
|
|
109
|
+
* @return {void}
|
|
110
|
+
*/
|
|
111
|
+
Connection.prototype.all = function (sql) {
|
|
112
|
+
var statement = new Statement(this, sql);
|
|
18
113
|
return statement.all.apply(statement, arguments);
|
|
19
114
|
}
|
|
20
115
|
|
|
21
|
-
|
|
116
|
+
/**
|
|
117
|
+
* @arg sql
|
|
118
|
+
* @param {...*} params
|
|
119
|
+
* @param callback
|
|
120
|
+
* @return {void}
|
|
121
|
+
*/
|
|
122
|
+
Connection.prototype.each = function (sql) {
|
|
22
123
|
var statement = new Statement(this, sql);
|
|
23
124
|
return statement.each.apply(statement, arguments);
|
|
24
125
|
}
|
|
25
126
|
|
|
26
|
-
|
|
27
|
-
|
|
127
|
+
/**
|
|
128
|
+
* @arg sql
|
|
129
|
+
* @param {...*} params
|
|
130
|
+
* @yields row chunks
|
|
131
|
+
*/
|
|
132
|
+
Connection.prototype.stream = async function* (sql) {
|
|
133
|
+
const statement = new Statement(this, sql);
|
|
134
|
+
const queryResult = await statement.stream.apply(statement, arguments);
|
|
135
|
+
for await (const result of queryResult) {
|
|
136
|
+
yield result;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Register a User Defined Function
|
|
142
|
+
*
|
|
143
|
+
* @arg name
|
|
144
|
+
* @arg return_type
|
|
145
|
+
* @arg fun
|
|
146
|
+
* @return {void}
|
|
147
|
+
* @note this follows the wasm udfs somewhat but is simpler because we can pass data much more cleanly
|
|
148
|
+
*/
|
|
149
|
+
Connection.prototype.register = function (name, return_type, fun) {
|
|
28
150
|
// TODO what if this throws an error somewhere? do we need a try/catch?
|
|
29
|
-
return this.register_bulk(name, return_type, function(desc) {
|
|
151
|
+
return this.register_bulk(name, return_type, function (desc) {
|
|
30
152
|
try {
|
|
31
153
|
// Build an argument resolver
|
|
32
154
|
const buildResolver = (arg) => {
|
|
@@ -126,62 +248,236 @@ Connection.prototype.register = function(name, return_type, fun) {
|
|
|
126
248
|
desc.ret.data[i] = res;
|
|
127
249
|
desc.ret.validity[i] = res === undefined || res === null ? 0 : 1;
|
|
128
250
|
}
|
|
129
|
-
} catch(error) { // work around recently fixed napi bug https://github.com/nodejs/node-addon-api/issues/912
|
|
251
|
+
} catch (error) { // work around recently fixed napi bug https://github.com/nodejs/node-addon-api/issues/912
|
|
130
252
|
console.log(desc.ret);
|
|
131
253
|
msg = error;
|
|
132
254
|
if (typeof error == 'object' && 'message' in error) {
|
|
133
255
|
msg = error.message
|
|
134
256
|
}
|
|
135
|
-
throw {name: 'DuckDB-UDF-Exception', message
|
|
257
|
+
throw { name: 'DuckDB-UDF-Exception', message: msg };
|
|
136
258
|
}
|
|
137
259
|
})
|
|
138
260
|
}
|
|
139
261
|
|
|
140
|
-
|
|
262
|
+
/**
|
|
263
|
+
* @method
|
|
264
|
+
* @arg sql
|
|
265
|
+
* @param {...*} params
|
|
266
|
+
* @param callback
|
|
267
|
+
* @return {Statement}
|
|
268
|
+
*/
|
|
269
|
+
Connection.prototype.prepare;
|
|
270
|
+
/**
|
|
271
|
+
* @method
|
|
272
|
+
* @arg sql
|
|
273
|
+
* @param {...*} params
|
|
274
|
+
* @param callback
|
|
275
|
+
* @return {void}
|
|
276
|
+
*/
|
|
277
|
+
Connection.prototype.exec;
|
|
278
|
+
/**
|
|
279
|
+
* Register a User Defined Function
|
|
280
|
+
*
|
|
281
|
+
* @method
|
|
282
|
+
* @arg name
|
|
283
|
+
* @arg return_type
|
|
284
|
+
* @param callback
|
|
285
|
+
* @return {void}
|
|
286
|
+
*/
|
|
287
|
+
Connection.prototype.register_bulk;
|
|
288
|
+
/**
|
|
289
|
+
* Unregister a User Defined Function
|
|
290
|
+
*
|
|
291
|
+
* @method
|
|
292
|
+
* @arg name
|
|
293
|
+
* @arg return_type
|
|
294
|
+
* @param callback
|
|
295
|
+
* @return {void}
|
|
296
|
+
*/
|
|
297
|
+
Connection.prototype.unregister;
|
|
298
|
+
|
|
299
|
+
default_connection = function (o) {
|
|
141
300
|
if (o.default_connection == undefined) {
|
|
142
301
|
o.default_connection = new duckdb.Connection(o);
|
|
143
302
|
}
|
|
144
|
-
return
|
|
303
|
+
return o.default_connection;
|
|
145
304
|
}
|
|
146
305
|
|
|
147
|
-
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* @method
|
|
309
|
+
* @param callback
|
|
310
|
+
* @return {void}
|
|
311
|
+
*/
|
|
312
|
+
Database.prototype.close;
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* @method
|
|
316
|
+
* @param callback
|
|
317
|
+
* TODO: what does this do?
|
|
318
|
+
* @return {void}
|
|
319
|
+
*/
|
|
320
|
+
Database.prototype.wait;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* TODO: what does this do?
|
|
324
|
+
* @method
|
|
325
|
+
* @param callback
|
|
326
|
+
* @return {void}
|
|
327
|
+
*/
|
|
328
|
+
Database.prototype.serialize;
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* TODO: what does this do?
|
|
332
|
+
* @method
|
|
333
|
+
* @param callback
|
|
334
|
+
* @return {void}
|
|
335
|
+
*/
|
|
336
|
+
Database.prototype.parallelize;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* @method
|
|
340
|
+
* @arg path the database to connect to, either a file path, or `:memory:`
|
|
341
|
+
* @return {Connection}
|
|
342
|
+
*/
|
|
343
|
+
Database.prototype.connect;
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* TODO: what does this do?
|
|
347
|
+
* @method
|
|
348
|
+
* @param callback
|
|
349
|
+
* @return {void}
|
|
350
|
+
*/
|
|
351
|
+
Database.prototype.interrupt;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* @arg sql
|
|
355
|
+
* @return {Statement}
|
|
356
|
+
*/
|
|
357
|
+
Database.prototype.prepare = function () {
|
|
148
358
|
return default_connection(this).prepare.apply(this.default_connection, arguments);
|
|
149
359
|
}
|
|
150
360
|
|
|
151
|
-
|
|
361
|
+
/**
|
|
362
|
+
* @arg sql
|
|
363
|
+
* @param {...*} params
|
|
364
|
+
* @param callback
|
|
365
|
+
* @return {void}
|
|
366
|
+
*/
|
|
367
|
+
Database.prototype.run = function () {
|
|
152
368
|
default_connection(this).run.apply(this.default_connection, arguments);
|
|
153
369
|
return this;
|
|
154
370
|
}
|
|
155
371
|
|
|
156
|
-
|
|
372
|
+
/**
|
|
373
|
+
* @arg sql
|
|
374
|
+
* @param {...*} params
|
|
375
|
+
* @param callback
|
|
376
|
+
* @return {void}
|
|
377
|
+
*/
|
|
378
|
+
Database.prototype.each = function () {
|
|
157
379
|
default_connection(this).each.apply(this.default_connection, arguments);
|
|
158
380
|
return this;
|
|
159
381
|
}
|
|
160
382
|
|
|
161
|
-
|
|
383
|
+
/**
|
|
384
|
+
* @arg sql
|
|
385
|
+
* @param {...*} params
|
|
386
|
+
* @param callback
|
|
387
|
+
* @return {void}
|
|
388
|
+
*/
|
|
389
|
+
Database.prototype.all = function () {
|
|
162
390
|
default_connection(this).all.apply(this.default_connection, arguments);
|
|
163
391
|
return this;
|
|
164
392
|
}
|
|
165
393
|
|
|
166
|
-
|
|
394
|
+
/**
|
|
395
|
+
* @arg sql
|
|
396
|
+
* @param {...*} params
|
|
397
|
+
* @param callback
|
|
398
|
+
* @return {void}
|
|
399
|
+
*/
|
|
400
|
+
Database.prototype.exec = function () {
|
|
167
401
|
default_connection(this).exec.apply(this.default_connection, arguments);
|
|
168
402
|
return this;
|
|
169
403
|
}
|
|
170
404
|
|
|
171
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Register a User Defined Function
|
|
407
|
+
*
|
|
408
|
+
* Convenience method for Connection#register
|
|
409
|
+
* @arg name
|
|
410
|
+
* @arg return_type
|
|
411
|
+
* @arg fun
|
|
412
|
+
* @return {this}
|
|
413
|
+
*/
|
|
414
|
+
Database.prototype.register = function () {
|
|
172
415
|
default_connection(this).register.apply(this.default_connection, arguments);
|
|
173
416
|
return this;
|
|
174
417
|
}
|
|
175
418
|
|
|
176
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Unregister a User Defined Function
|
|
421
|
+
*
|
|
422
|
+
* Convenience method for Connection#unregister
|
|
423
|
+
* @arg name
|
|
424
|
+
* @return {this}
|
|
425
|
+
*/
|
|
426
|
+
Database.prototype.unregister = function () {
|
|
177
427
|
default_connection(this).unregister.apply(this.default_connection, arguments);
|
|
178
428
|
return this;
|
|
179
429
|
}
|
|
180
430
|
|
|
181
|
-
|
|
431
|
+
/**
|
|
432
|
+
* Not implemented
|
|
433
|
+
*/
|
|
434
|
+
Database.prototype.get = function () {
|
|
182
435
|
throw "get() is not implemented because it's evil";
|
|
183
436
|
}
|
|
184
437
|
|
|
185
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Not implemented
|
|
440
|
+
*/
|
|
441
|
+
Statement.prototype.get = function () {
|
|
186
442
|
throw "get() is not implemented because it's evil";
|
|
187
443
|
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* @method
|
|
447
|
+
* @arg sql
|
|
448
|
+
* @param {...*} params
|
|
449
|
+
* @param callback
|
|
450
|
+
* @return {void}
|
|
451
|
+
*/
|
|
452
|
+
Statement.prototype.run;
|
|
453
|
+
/**
|
|
454
|
+
* @method
|
|
455
|
+
* @arg sql
|
|
456
|
+
* @param {...*} params
|
|
457
|
+
* @param callback
|
|
458
|
+
* @return {void}
|
|
459
|
+
*/
|
|
460
|
+
Statement.prototype.all;
|
|
461
|
+
/**
|
|
462
|
+
* @method
|
|
463
|
+
* @arg sql
|
|
464
|
+
* @param {...*} params
|
|
465
|
+
* @param callback
|
|
466
|
+
* @return {void}
|
|
467
|
+
*/
|
|
468
|
+
Statement.prototype.each;
|
|
469
|
+
/**
|
|
470
|
+
* @method
|
|
471
|
+
* @arg sql
|
|
472
|
+
* @param {...*} params
|
|
473
|
+
* @param callback
|
|
474
|
+
* @return {void}
|
|
475
|
+
*/
|
|
476
|
+
Statement.prototype.finalize
|
|
477
|
+
/**
|
|
478
|
+
* @method
|
|
479
|
+
* @arg sql
|
|
480
|
+
* @param {...*} params
|
|
481
|
+
* @yield callback
|
|
482
|
+
*/
|
|
483
|
+
Statement.prototype.stream;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "duckdb",
|
|
3
3
|
"main": "./lib/duckdb.js",
|
|
4
|
-
"version": "0.4.1-
|
|
4
|
+
"version": "0.4.1-dev2145.0",
|
|
5
5
|
"description": "DuckDB node.js API",
|
|
6
6
|
"gypfile": true,
|
|
7
7
|
"dependencies": {
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"aws-sdk": "^2.790.0",
|
|
29
|
+
"chai": "^4.3.6",
|
|
30
|
+
"jsdoc3-parser": "^2.0.0",
|
|
29
31
|
"mocha": "^8.3.0"
|
|
30
32
|
},
|
|
31
33
|
"repository": {
|
package/src/connection.cpp
CHANGED
|
@@ -78,7 +78,7 @@ struct JSArgs {
|
|
|
78
78
|
duckdb::DataChunk *args;
|
|
79
79
|
duckdb::Vector *result;
|
|
80
80
|
bool done;
|
|
81
|
-
|
|
81
|
+
duckdb::PreservedError error;
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
void DuckDBNodeUDFLauncher(Napi::Env env, Napi::Function jsudf, std::nullptr_t *, JSArgs *jsargs) {
|
|
@@ -188,8 +188,10 @@ void DuckDBNodeUDFLauncher(Napi::Env env, Napi::Function jsudf, std::nullptr_t *
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
|
+
} catch (const duckdb::Exception &e) {
|
|
192
|
+
jsargs->error = duckdb::PreservedError(e);
|
|
191
193
|
} catch (const std::exception &e) {
|
|
192
|
-
jsargs->error = e
|
|
194
|
+
jsargs->error = duckdb::PreservedError(e);
|
|
193
195
|
}
|
|
194
196
|
jsargs->done = true;
|
|
195
197
|
}
|
|
@@ -207,7 +209,7 @@ struct RegisterTask : public Task {
|
|
|
207
209
|
// here we can do only DuckDB stuff because we do not have a functioning env
|
|
208
210
|
|
|
209
211
|
// Flatten all args to simplify udfs
|
|
210
|
-
args.
|
|
212
|
+
args.Flatten();
|
|
211
213
|
|
|
212
214
|
JSArgs jsargs;
|
|
213
215
|
jsargs.rows = args.size();
|
|
@@ -219,8 +221,8 @@ struct RegisterTask : public Task {
|
|
|
219
221
|
while (!jsargs.done) {
|
|
220
222
|
std::this_thread::yield();
|
|
221
223
|
}
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
+
if (jsargs.error) {
|
|
225
|
+
jsargs.error.Throw();
|
|
224
226
|
}
|
|
225
227
|
};
|
|
226
228
|
|
|
@@ -321,31 +323,36 @@ struct ExecTask : public Task {
|
|
|
321
323
|
auto &connection = Get<Connection>();
|
|
322
324
|
|
|
323
325
|
success = true;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
try {
|
|
327
|
+
auto statements = connection.connection->ExtractStatements(sql);
|
|
328
|
+
if (statements.empty()) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
328
331
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
332
|
+
for (duckdb::idx_t i = 0; i < statements.size(); i++) {
|
|
333
|
+
auto res = connection.connection->Query(move(statements[i]));
|
|
334
|
+
if (res->HasError()) {
|
|
335
|
+
success = false;
|
|
336
|
+
error = res->GetErrorObject();
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
336
339
|
}
|
|
340
|
+
} catch (duckdb::ParserException &e) {
|
|
341
|
+
success = false;
|
|
342
|
+
error = duckdb::PreservedError(e);
|
|
343
|
+
return;
|
|
337
344
|
}
|
|
338
345
|
}
|
|
339
346
|
|
|
340
347
|
void Callback() override {
|
|
341
348
|
auto env = object.Env();
|
|
342
349
|
Napi::HandleScope scope(env);
|
|
343
|
-
callback.Value().MakeCallback(object.Value(), {success ? env.Null() : Napi::String::New(env, error)});
|
|
350
|
+
callback.Value().MakeCallback(object.Value(), {success ? env.Null() : Napi::String::New(env, error.Message())});
|
|
344
351
|
};
|
|
345
352
|
|
|
346
353
|
std::string sql;
|
|
347
354
|
bool success;
|
|
348
|
-
|
|
355
|
+
duckdb::PreservedError error;
|
|
349
356
|
};
|
|
350
357
|
|
|
351
358
|
Napi::Value Connection::Exec(const Napi::CallbackInfo &info) {
|
package/src/data_chunk.cpp
CHANGED
|
@@ -14,7 +14,7 @@ Napi::Array EncodeDataChunk(Napi::Env env, duckdb::DataChunk &chunk, bool with_t
|
|
|
14
14
|
// Make sure we only have flat vectors hereafter (for now)
|
|
15
15
|
auto &chunk_vec = chunk.data[col_idx];
|
|
16
16
|
if (with_data) {
|
|
17
|
-
chunk_vec.
|
|
17
|
+
chunk_vec.Flatten(chunk.size());
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// Do a post-order DFS traversal
|
|
@@ -52,7 +52,7 @@ Napi::Array EncodeDataChunk(Napi::Env env, duckdb::DataChunk &chunk, bool with_t
|
|
|
52
52
|
|
|
53
53
|
// Create validity vector
|
|
54
54
|
if (with_data) {
|
|
55
|
-
vec->
|
|
55
|
+
vec->Flatten(chunk.size());
|
|
56
56
|
auto &validity = duckdb::FlatVector::Validity(*vec);
|
|
57
57
|
auto validity_buffer = Napi::Uint8Array::New(env, chunk.size());
|
|
58
58
|
for (idx_t row_idx = 0; row_idx < chunk.size(); row_idx++) {
|
package/src/database.cpp
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include "duckdb_node.hpp"
|
|
2
|
+
#include "napi.h"
|
|
2
3
|
#include "parquet-amalgamation.hpp"
|
|
3
4
|
|
|
4
5
|
namespace node_duckdb {
|
|
@@ -22,23 +23,47 @@ Napi::Object Database::Init(Napi::Env env, Napi::Object exports) {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
struct OpenTask : public Task {
|
|
25
|
-
OpenTask(Database &database_, std::string filename_,
|
|
26
|
-
|
|
26
|
+
OpenTask(Database &database_, std::string filename_, duckdb::AccessMode access_mode_, Napi::Object config_,
|
|
27
|
+
Napi::Function callback_)
|
|
28
|
+
: Task(database_, callback_), filename(filename_) {
|
|
29
|
+
|
|
30
|
+
duckdb_config.options.access_mode = access_mode_;
|
|
31
|
+
Napi::Env env = database_.Env();
|
|
32
|
+
Napi::HandleScope scope(env);
|
|
33
|
+
|
|
34
|
+
if (!config_.IsUndefined()) {
|
|
35
|
+
const Napi::Array config_names = config_.GetPropertyNames();
|
|
36
|
+
|
|
37
|
+
for (duckdb::idx_t config_idx = 0; config_idx < config_names.Length(); config_idx++) {
|
|
38
|
+
std::string key = config_names.Get(config_idx).As<Napi::String>();
|
|
39
|
+
std::string val = config_.Get(key).As<Napi::String>();
|
|
40
|
+
auto config_property = duckdb::DBConfig::GetOptionByName(key);
|
|
41
|
+
if (!config_property) {
|
|
42
|
+
Napi::TypeError::New(env, "Unrecognized configuration property" + key).ThrowAsJavaScriptException();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
duckdb_config.SetOption(*config_property, duckdb::Value(val));
|
|
47
|
+
} catch (std::exception &e) {
|
|
48
|
+
Napi::TypeError::New(env, "Failed to set configuration option " + key + ": " + e.what())
|
|
49
|
+
.ThrowAsJavaScriptException();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
27
54
|
}
|
|
28
55
|
|
|
29
56
|
void DoWork() override {
|
|
30
57
|
try {
|
|
31
|
-
duckdb::
|
|
32
|
-
if (read_only) {
|
|
33
|
-
config.access_mode = duckdb::AccessMode::READ_ONLY;
|
|
34
|
-
}
|
|
35
|
-
Get<Database>().database = duckdb::make_unique<duckdb::DuckDB>(filename, &config);
|
|
58
|
+
Get<Database>().database = duckdb::make_unique<duckdb::DuckDB>(filename, &duckdb_config);
|
|
36
59
|
duckdb::ParquetExtension extension;
|
|
37
60
|
extension.Load(*Get<Database>().database);
|
|
38
61
|
success = true;
|
|
39
62
|
|
|
63
|
+
} catch (const duckdb::Exception &ex) {
|
|
64
|
+
error = duckdb::PreservedError(ex);
|
|
40
65
|
} catch (std::exception &ex) {
|
|
41
|
-
error = ex
|
|
66
|
+
error = duckdb::PreservedError(ex);
|
|
42
67
|
}
|
|
43
68
|
}
|
|
44
69
|
|
|
@@ -48,7 +73,7 @@ struct OpenTask : public Task {
|
|
|
48
73
|
|
|
49
74
|
std::vector<napi_value> args;
|
|
50
75
|
if (!success) {
|
|
51
|
-
args.push_back(Utils::CreateError(env, error));
|
|
76
|
+
args.push_back(Utils::CreateError(env, error.Message()));
|
|
52
77
|
} else {
|
|
53
78
|
args.push_back(env.Null());
|
|
54
79
|
}
|
|
@@ -59,22 +84,35 @@ struct OpenTask : public Task {
|
|
|
59
84
|
}
|
|
60
85
|
|
|
61
86
|
std::string filename;
|
|
62
|
-
|
|
63
|
-
|
|
87
|
+
duckdb::DBConfig duckdb_config;
|
|
88
|
+
duckdb::PreservedError error;
|
|
64
89
|
bool success = false;
|
|
65
90
|
};
|
|
66
91
|
|
|
67
|
-
Database::Database(const Napi::CallbackInfo &info)
|
|
92
|
+
Database::Database(const Napi::CallbackInfo &info)
|
|
93
|
+
: Napi::ObjectWrap<Database>(info), task_inflight(false), env(info.Env()) {
|
|
68
94
|
auto env = info.Env();
|
|
95
|
+
|
|
69
96
|
if (info.Length() < 1 || !info[0].IsString()) {
|
|
70
97
|
Napi::TypeError::New(env, "Database location expected").ThrowAsJavaScriptException();
|
|
71
98
|
return;
|
|
72
99
|
}
|
|
73
100
|
std::string filename = info[0].As<Napi::String>();
|
|
74
101
|
unsigned int pos = 1;
|
|
102
|
+
|
|
103
|
+
duckdb::AccessMode access_mode = duckdb::AccessMode::AUTOMATIC;
|
|
104
|
+
|
|
75
105
|
int mode = 0;
|
|
76
106
|
if (info.Length() >= pos && info[pos].IsNumber() && Utils::OtherIsInt(info[pos].As<Napi::Number>())) {
|
|
77
107
|
mode = info[pos++].As<Napi::Number>().Int32Value();
|
|
108
|
+
if (mode == DUCKDB_NODEJS_READONLY) {
|
|
109
|
+
access_mode = duckdb::AccessMode::READ_ONLY;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
Napi::Object config;
|
|
114
|
+
if (info.Length() >= pos && info[pos].IsObject() && !info[pos].IsFunction()) {
|
|
115
|
+
config = info[pos++].As<Napi::Object>();
|
|
78
116
|
}
|
|
79
117
|
|
|
80
118
|
Napi::Function callback;
|
|
@@ -82,7 +120,11 @@ Database::Database(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Database>(
|
|
|
82
120
|
callback = info[pos++].As<Napi::Function>();
|
|
83
121
|
}
|
|
84
122
|
|
|
85
|
-
Schedule(env, duckdb::make_unique<OpenTask>(*this, filename,
|
|
123
|
+
Schedule(env, duckdb::make_unique<OpenTask>(*this, filename, access_mode, config, callback));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Database::~Database() {
|
|
127
|
+
Napi::MemoryManagement::AdjustExternalMemory(env, -bytes_allocated);
|
|
86
128
|
}
|
|
87
129
|
|
|
88
130
|
void Database::Schedule(Napi::Env env, std::unique_ptr<Task> task) {
|
|
@@ -101,9 +143,7 @@ static void TaskExecuteCallback(napi_env e, void *data) {
|
|
|
101
143
|
static void TaskCompleteCallback(napi_env e, napi_status status, void *data) {
|
|
102
144
|
std::unique_ptr<TaskHolder> holder((TaskHolder *)data);
|
|
103
145
|
holder->db->TaskComplete(e);
|
|
104
|
-
|
|
105
|
-
holder->task->Callback();
|
|
106
|
-
}
|
|
146
|
+
holder->task->DoCallback();
|
|
107
147
|
}
|
|
108
148
|
|
|
109
149
|
void Database::TaskComplete(Napi::Env env) {
|
|
@@ -112,6 +152,16 @@ void Database::TaskComplete(Napi::Env env) {
|
|
|
112
152
|
task_inflight = false;
|
|
113
153
|
}
|
|
114
154
|
Process(env);
|
|
155
|
+
|
|
156
|
+
if (database) {
|
|
157
|
+
// Bookkeeping: tell node (and the node GC in particular) how much
|
|
158
|
+
// memory we're using, such that it can make better decisions on when to
|
|
159
|
+
// trigger collections.
|
|
160
|
+
auto &buffer_manager = duckdb::BufferManager::GetBufferManager(*database->instance);
|
|
161
|
+
auto current_bytes = buffer_manager.GetUsedMemory();
|
|
162
|
+
Napi::MemoryManagement::AdjustExternalMemory(env, current_bytes - bytes_allocated);
|
|
163
|
+
bytes_allocated = current_bytes;
|
|
164
|
+
}
|
|
115
165
|
}
|
|
116
166
|
|
|
117
167
|
void Database::Process(Napi::Env env) {
|