duckdb 0.4.1-dev155.0 → 0.4.1-dev1559.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 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
- Connection.prototype.run = function(sql) {
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
- Connection.prototype.all = function(sql) {
17
- var statement = new Statement(this,sql);
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
- Connection.prototype.each = function(sql) {
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
- // this follows the wasm udfs somewhat but is simpler because we can pass data much more cleanly
27
- Connection.prototype.register = function(name, return_type, fun) {
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 : msg};
257
+ throw { name: 'DuckDB-UDF-Exception', message: msg };
136
258
  }
137
259
  })
138
260
  }
139
261
 
140
- default_connection = function(o) {
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(o.default_connection);
303
+ return o.default_connection;
145
304
  }
146
305
 
147
- Database.prototype.prepare = function() {
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
- Database.prototype.run = function() {
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
- Database.prototype.each = function() {
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
- Database.prototype.all = function() {
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
- Database.prototype.exec = function() {
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
- Database.prototype.register = function() {
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
- Database.prototype.unregister = function() {
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
- Database.prototype.get = function() {
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
- Statement.prototype.get = function() {
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-dev155.0",
4
+ "version": "0.4.1-dev1559.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": {
@@ -207,7 +207,7 @@ struct RegisterTask : public Task {
207
207
  // here we can do only DuckDB stuff because we do not have a functioning env
208
208
 
209
209
  // Flatten all args to simplify udfs
210
- args.Normalify();
210
+ args.Flatten();
211
211
 
212
212
  JSArgs jsargs;
213
213
  jsargs.rows = args.size();
@@ -321,19 +321,25 @@ struct ExecTask : public Task {
321
321
  auto &connection = Get<Connection>();
322
322
 
323
323
  success = true;
324
- auto statements = connection.connection->ExtractStatements(sql);
325
- if (statements.empty()) {
326
- return;
327
- }
324
+ try {
325
+ auto statements = connection.connection->ExtractStatements(sql);
326
+ if (statements.empty()) {
327
+ return;
328
+ }
328
329
 
329
- // thanks Mark
330
- for (duckdb::idx_t i = 0; i < statements.size(); i++) {
331
- auto res = connection.connection->Query(move(statements[i]));
332
- if (!res->success) {
333
- success = false;
334
- error = res->error;
335
- break;
330
+ // thanks Mark
331
+ for (duckdb::idx_t i = 0; i < statements.size(); i++) {
332
+ auto res = connection.connection->Query(move(statements[i]));
333
+ if (!res->success) {
334
+ success = false;
335
+ error = res->error;
336
+ break;
337
+ }
336
338
  }
339
+ } catch (duckdb::ParserException &e) {
340
+ success = false;
341
+ error = e.what();
342
+ return;
337
343
  }
338
344
  }
339
345
 
@@ -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.Normalify(chunk.size());
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->Normalify(chunk.size());
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++) {
@@ -182,4 +182,4 @@ Napi::Array EncodeDataChunk(Napi::Env env, duckdb::DataChunk &chunk, bool with_t
182
182
  return col_descs;
183
183
  }
184
184
 
185
- } // namespace node_duckdb
185
+ } // namespace node_duckdb
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,17 +23,39 @@ 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_, bool read_only_, Napi::Function callback_)
26
- : Task(database_, callback_), filename(filename_), read_only(read_only_) {
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::DBConfig config;
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;
@@ -59,22 +82,35 @@ struct OpenTask : public Task {
59
82
  }
60
83
 
61
84
  std::string filename;
62
- bool read_only = false;
85
+ duckdb::DBConfig duckdb_config;
63
86
  std::string error = "";
64
87
  bool success = false;
65
88
  };
66
89
 
67
- Database::Database(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Database>(info), task_inflight(false) {
90
+ Database::Database(const Napi::CallbackInfo &info)
91
+ : Napi::ObjectWrap<Database>(info), task_inflight(false), env(info.Env()) {
68
92
  auto env = info.Env();
93
+
69
94
  if (info.Length() < 1 || !info[0].IsString()) {
70
95
  Napi::TypeError::New(env, "Database location expected").ThrowAsJavaScriptException();
71
96
  return;
72
97
  }
73
98
  std::string filename = info[0].As<Napi::String>();
74
99
  unsigned int pos = 1;
100
+
101
+ duckdb::AccessMode access_mode = duckdb::AccessMode::AUTOMATIC;
102
+
75
103
  int mode = 0;
76
104
  if (info.Length() >= pos && info[pos].IsNumber() && Utils::OtherIsInt(info[pos].As<Napi::Number>())) {
77
105
  mode = info[pos++].As<Napi::Number>().Int32Value();
106
+ if (mode == DUCKDB_NODEJS_READONLY) {
107
+ access_mode = duckdb::AccessMode::READ_ONLY;
108
+ }
109
+ }
110
+
111
+ Napi::Object config;
112
+ if (info.Length() >= pos && info[pos].IsObject() && !info[pos].IsFunction()) {
113
+ config = info[pos++].As<Napi::Object>();
78
114
  }
79
115
 
80
116
  Napi::Function callback;
@@ -82,7 +118,11 @@ Database::Database(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Database>(
82
118
  callback = info[pos++].As<Napi::Function>();
83
119
  }
84
120
 
85
- Schedule(env, duckdb::make_unique<OpenTask>(*this, filename, mode == DUCKDB_NODEJS_READONLY, callback));
121
+ Schedule(env, duckdb::make_unique<OpenTask>(*this, filename, access_mode, config, callback));
122
+ }
123
+
124
+ Database::~Database() {
125
+ Napi::MemoryManagement::AdjustExternalMemory(env, -bytes_allocated);
86
126
  }
87
127
 
88
128
  void Database::Schedule(Napi::Env env, std::unique_ptr<Task> task) {
@@ -101,9 +141,7 @@ static void TaskExecuteCallback(napi_env e, void *data) {
101
141
  static void TaskCompleteCallback(napi_env e, napi_status status, void *data) {
102
142
  std::unique_ptr<TaskHolder> holder((TaskHolder *)data);
103
143
  holder->db->TaskComplete(e);
104
- if (holder->task->callback.Value().IsFunction()) {
105
- holder->task->Callback();
106
- }
144
+ holder->task->DoCallback();
107
145
  }
108
146
 
109
147
  void Database::TaskComplete(Napi::Env env) {
@@ -112,6 +150,16 @@ void Database::TaskComplete(Napi::Env env) {
112
150
  task_inflight = false;
113
151
  }
114
152
  Process(env);
153
+
154
+ if (database) {
155
+ // Bookkeeping: tell node (and the node GC in particular) how much
156
+ // memory we're using, such that it can make better decisions on when to
157
+ // trigger collections.
158
+ auto &buffer_manager = duckdb::BufferManager::GetBufferManager(*database->instance);
159
+ auto current_bytes = buffer_manager.GetUsedMemory();
160
+ Napi::MemoryManagement::AdjustExternalMemory(env, current_bytes - bytes_allocated);
161
+ bytes_allocated = current_bytes;
162
+ }
115
163
  }
116
164
 
117
165
  void Database::Process(Napi::Env env) {