duckdb 0.4.1-dev13.0 → 0.4.1-dev1328.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/src/statement.cpp CHANGED
@@ -1,4 +1,9 @@
1
+ #include "duckdb.hpp"
1
2
  #include "duckdb_node.hpp"
3
+ #include "napi.h"
4
+
5
+ #include <algorithm>
6
+ #include <cassert>
2
7
 
3
8
  namespace node_duckdb {
4
9
 
@@ -10,7 +15,8 @@ Napi::Object Statement::Init(Napi::Env env, Napi::Object exports) {
10
15
  Napi::Function t =
11
16
  DefineClass(env, "Statement",
12
17
  {InstanceMethod("run", &Statement::Run), InstanceMethod("all", &Statement::All),
13
- InstanceMethod("each", &Statement::Each), InstanceMethod("finalize", &Statement::Finalize_)});
18
+ InstanceMethod("each", &Statement::Each), InstanceMethod("finalize", &Statement::Finish),
19
+ InstanceMethod("stream", &Statement::Stream)});
14
20
 
15
21
  constructor = Napi::Persistent(t);
16
22
  constructor.SuppressDestruct();
@@ -20,7 +26,7 @@ Napi::Object Statement::Init(Napi::Env env, Napi::Object exports) {
20
26
  }
21
27
 
22
28
  struct PrepareTask : public Task {
23
- PrepareTask(Statement &statement_, Napi::Function callback_) : Task(statement_, callback_) {
29
+ PrepareTask(Statement &statement, Napi::Function callback) : Task(statement, callback) {
24
30
  }
25
31
 
26
32
  void DoWork() override {
@@ -77,7 +83,7 @@ Statement::~Statement() {
77
83
  }
78
84
 
79
85
  // A Napi InstanceOf for Javascript Objects "Date" and "RegExp"
80
- static bool other_instance_of(Napi::Object source, const char *object_type) {
86
+ static bool OtherInstanceOf(Napi::Object source, const char *object_type) {
81
87
  if (strcmp(object_type, "Date") == 0) {
82
88
  return source.InstanceOf(source.Env().Global().Get(object_type).As<Napi::Function>());
83
89
  } else if (strcmp(object_type, "RegExp") == 0) {
@@ -87,10 +93,10 @@ static bool other_instance_of(Napi::Object source, const char *object_type) {
87
93
  return false;
88
94
  }
89
95
 
90
- static duckdb::Value bind_parameter(const Napi::Value source) {
96
+ static duckdb::Value BindParameter(const Napi::Value source) {
91
97
  if (source.IsString()) {
92
98
  return duckdb::Value(source.As<Napi::String>().Utf8Value());
93
- } else if (other_instance_of(source.As<Napi::Object>(), "RegExp")) {
99
+ } else if (OtherInstanceOf(source.As<Napi::Object>(), "RegExp")) {
94
100
  return duckdb::Value(source.ToString().Utf8Value());
95
101
  } else if (source.IsNumber()) {
96
102
  if (Utils::OtherIsInt(source.As<Napi::Number>())) {
@@ -124,6 +130,10 @@ static duckdb::Value bind_parameter(const Napi::Value source) {
124
130
  static Napi::Value convert_col_val(Napi::Env &env, duckdb::Value dval, duckdb::LogicalTypeId id) {
125
131
  Napi::Value value;
126
132
 
133
+ if (dval.IsNull()) {
134
+ return env.Null();
135
+ }
136
+
127
137
  // TODO templateroo here
128
138
  switch (id) {
129
139
  case duckdb::LogicalTypeId::BOOLEAN: {
@@ -208,6 +218,7 @@ static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names,
208
218
  Napi::EscapableHandleScope scope(env);
209
219
  std::vector<Napi::String> node_names;
210
220
  assert(names.size() == chunk.ColumnCount());
221
+ node_names.reserve(names.size());
211
222
  for (auto &name : names) {
212
223
  node_names.push_back(Napi::String::New(env, name));
213
224
  }
@@ -218,11 +229,6 @@ static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names,
218
229
 
219
230
  for (duckdb::idx_t col_idx = 0; col_idx < chunk.ColumnCount(); col_idx++) {
220
231
  duckdb::Value dval = chunk.GetValue(col_idx, row_idx);
221
- if (dval.IsNull()) {
222
- row_result.Set(node_names[col_idx], env.Null());
223
- continue;
224
- }
225
-
226
232
  row_result.Set(node_names[col_idx], convert_col_val(env, dval, chunk.data[col_idx].GetType().id()));
227
233
  }
228
234
  result.Set(row_idx, row_result);
@@ -240,8 +246,8 @@ struct StatementParam {
240
246
  };
241
247
 
242
248
  struct RunPreparedTask : public Task {
243
- RunPreparedTask(Statement &statement_, duckdb::unique_ptr<StatementParam> params_, RunType run_type_)
244
- : Task(statement_, params_->callback), params(move(params_)), run_type(run_type_) {
249
+ RunPreparedTask(Statement &statement, duckdb::unique_ptr<StatementParam> params, RunType run_type)
250
+ : Task(statement, params->callback), params(move(params)), run_type(run_type) {
245
251
  }
246
252
 
247
253
  void DoWork() override {
@@ -269,10 +275,6 @@ struct RunPreparedTask : public Task {
269
275
  cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error)});
270
276
  return;
271
277
  }
272
- if (!statement.statement->success) {
273
- cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error)});
274
- return;
275
- }
276
278
  if (!result->success) {
277
279
  cb.MakeCallback(statement.Value(), {Utils::CreateError(env, result->error)});
278
280
  return;
@@ -285,6 +287,8 @@ struct RunPreparedTask : public Task {
285
287
  case RunType::EACH: {
286
288
  duckdb::idx_t count = 0;
287
289
  while (true) {
290
+ Napi::HandleScope scope(env);
291
+
288
292
  auto chunk = result->Fetch();
289
293
  if (!chunk || chunk->size() == 0) {
290
294
  break;
@@ -335,6 +339,45 @@ struct RunPreparedTask : public Task {
335
339
  RunType run_type;
336
340
  };
337
341
 
342
+ struct RunQueryTask : public Task {
343
+ RunQueryTask(Statement &statement, duckdb::unique_ptr<StatementParam> params, Napi::Promise::Deferred deferred)
344
+ : Task(statement), deferred(deferred), params(move(params)) {
345
+ }
346
+
347
+ void DoWork() override {
348
+ auto &statement = Get<Statement>();
349
+ if (!statement.statement || !statement.statement->success) {
350
+ return;
351
+ }
352
+
353
+ result = statement.statement->Execute(params->params, true);
354
+ }
355
+
356
+ void DoCallback() override {
357
+ auto &statement = Get<Statement>();
358
+ Napi::Env env = statement.Env();
359
+ Napi::HandleScope scope(env);
360
+
361
+ if (!statement.statement) {
362
+ deferred.Reject(Utils::CreateError(env, "statement was finalized"));
363
+ } else if (!statement.statement->success) {
364
+ deferred.Reject(Utils::CreateError(env, statement.statement->error));
365
+ } else if (!result->success) {
366
+ deferred.Reject(Utils::CreateError(env, result->error));
367
+ } else {
368
+ auto db = statement.connection_ref->database_ref->Value();
369
+ auto query_result = QueryResult::constructor.New({db});
370
+ auto unwrapped = Napi::ObjectWrap<QueryResult>::Unwrap(query_result);
371
+ unwrapped->result = move(result);
372
+ deferred.Resolve(query_result);
373
+ }
374
+ }
375
+
376
+ Napi::Promise::Deferred deferred;
377
+ std::unique_ptr<duckdb::QueryResult> result;
378
+ duckdb::unique_ptr<StatementParam> params;
379
+ };
380
+
338
381
  duckdb::unique_ptr<StatementParam> Statement::HandleArgs(const Napi::CallbackInfo &info) {
339
382
  size_t start_idx = ignore_first_param ? 1 : 0;
340
383
  auto params = duckdb::make_unique<StatementParam>();
@@ -352,7 +395,7 @@ duckdb::unique_ptr<StatementParam> Statement::HandleArgs(const Napi::CallbackInf
352
395
  if (p.IsUndefined()) {
353
396
  continue;
354
397
  }
355
- params->params.push_back(bind_parameter(p));
398
+ params->params.push_back(BindParameter(p));
356
399
  }
357
400
  return params;
358
401
  }
@@ -364,21 +407,26 @@ Napi::Value Statement::All(const Napi::CallbackInfo &info) {
364
407
  }
365
408
 
366
409
  Napi::Value Statement::Run(const Napi::CallbackInfo &info) {
367
- auto params = HandleArgs(info);
368
410
  connection_ref->database_ref->Schedule(info.Env(),
369
411
  duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::RUN));
370
412
  return info.This();
371
413
  }
372
414
 
373
415
  Napi::Value Statement::Each(const Napi::CallbackInfo &info) {
374
- auto params = HandleArgs(info);
375
416
  connection_ref->database_ref->Schedule(
376
417
  info.Env(), duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::EACH));
377
418
  return info.This();
378
419
  }
379
420
 
380
- struct FinalizeTask : public Task {
381
- FinalizeTask(Statement &statement_, Napi::Function callback_) : Task(statement_, callback_) {
421
+ Napi::Value Statement::Stream(const Napi::CallbackInfo &info) {
422
+ auto deferred = Napi::Promise::Deferred::New(info.Env());
423
+ connection_ref->database_ref->Schedule(info.Env(),
424
+ duckdb::make_unique<RunQueryTask>(*this, HandleArgs(info), deferred));
425
+ return deferred.Promise();
426
+ }
427
+
428
+ struct FinishTask : public Task {
429
+ FinishTask(Statement &statement, Napi::Function callback) : Task(statement, callback) {
382
430
  }
383
431
 
384
432
  void DoWork() override {
@@ -387,7 +435,7 @@ struct FinalizeTask : public Task {
387
435
  }
388
436
  };
389
437
 
390
- Napi::Value Statement::Finalize_(const Napi::CallbackInfo &info) {
438
+ Napi::Value Statement::Finish(const Napi::CallbackInfo &info) {
391
439
  Napi::Env env = info.Env();
392
440
  Napi::HandleScope scope(env);
393
441
 
@@ -397,8 +445,71 @@ Napi::Value Statement::Finalize_(const Napi::CallbackInfo &info) {
397
445
  callback = info[0].As<Napi::Function>();
398
446
  }
399
447
 
400
- connection_ref->database_ref->Schedule(env, duckdb::make_unique<FinalizeTask>(*this, callback));
448
+ connection_ref->database_ref->Schedule(env, duckdb::make_unique<FinishTask>(*this, callback));
401
449
  return env.Null();
402
450
  }
403
451
 
452
+ Napi::FunctionReference QueryResult::constructor;
453
+
454
+ Napi::Object QueryResult::Init(Napi::Env env, Napi::Object exports) {
455
+ Napi::HandleScope scope(env);
456
+
457
+ Napi::Function t = DefineClass(env, "QueryResult", {InstanceMethod("nextChunk", &QueryResult::NextChunk)});
458
+
459
+ constructor = Napi::Persistent(t);
460
+ constructor.SuppressDestruct();
461
+
462
+ exports.Set("QueryResult", t);
463
+ return exports;
464
+ }
465
+
466
+ QueryResult::QueryResult(const Napi::CallbackInfo &info) : Napi::ObjectWrap<QueryResult>(info) {
467
+ database_ref = Napi::ObjectWrap<Database>::Unwrap(info[0].As<Napi::Object>());
468
+ database_ref->Ref();
469
+ }
470
+
471
+ QueryResult::~QueryResult() {
472
+ database_ref->Unref();
473
+ database_ref = nullptr;
474
+ }
475
+
476
+ struct GetChunkTask : public Task {
477
+ GetChunkTask(QueryResult &query_result, Napi::Promise::Deferred deferred) : Task(query_result), deferred(deferred) {
478
+ }
479
+
480
+ void DoWork() override {
481
+ auto &query_result = Get<QueryResult>();
482
+ chunk = query_result.result->Fetch();
483
+ }
484
+
485
+ void DoCallback() override {
486
+ auto &query_result = Get<QueryResult>();
487
+ Napi::Env env = query_result.Env();
488
+ Napi::HandleScope scope(env);
489
+
490
+ if (chunk == nullptr || chunk->size() == 0) {
491
+ deferred.Resolve(env.Null());
492
+ return;
493
+ }
494
+
495
+ auto chunk_converted = convert_chunk(env, query_result.result->names, *chunk).ToObject();
496
+ if (!chunk_converted.IsArray()) {
497
+ deferred.Reject(Utils::CreateError(env, "internal error: chunk is not array"));
498
+ } else {
499
+ deferred.Resolve(chunk_converted);
500
+ }
501
+ }
502
+
503
+ Napi::Promise::Deferred deferred;
504
+ std::unique_ptr<duckdb::DataChunk> chunk;
505
+ };
506
+
507
+ Napi::Value QueryResult::NextChunk(const Napi::CallbackInfo &info) {
508
+ auto env = info.Env();
509
+ auto deferred = Napi::Promise::Deferred::New(env);
510
+ database_ref->Schedule(env, duckdb::make_unique<GetChunkTask>(*this, deferred));
511
+
512
+ return deferred.Promise();
513
+ }
514
+
404
515
  } // namespace node_duckdb
@@ -10,7 +10,7 @@ describe("data type support", function () {
10
10
  it("supports BOOLEAN values", function (done) {
11
11
  db.run("CREATE TABLE boolean_table (i BOOLEAN)");
12
12
  const stmt = db.prepare("INSERT INTO boolean_table VALUES (?)");
13
- const values = [true, false];
13
+ const values = [true, false, null];
14
14
  values.forEach((bool) => {
15
15
  stmt.run(bool);
16
16
  });
@@ -21,7 +21,8 @@ describe("data type support", function () {
21
21
  });
22
22
  });
23
23
  it("supports INTERVAL values", function (done) {
24
- db.prepare(`SELECT
24
+ db.prepare(
25
+ `SELECT
25
26
  INTERVAL 1 MINUTE as minutes,
26
27
  INTERVAL 5 DAY as days,
27
28
  INTERVAL 4 MONTH as months,
@@ -43,6 +44,7 @@ describe("data type support", function () {
43
44
  done();
44
45
  });
45
46
  });
47
+
46
48
  it("supports STRUCT values", function (done) {
47
49
  db.prepare(`SELECT {'x': 1, 'y': 2, 'z': {'a': 'b'}} as struct`).each(
48
50
  (err, row) => {
@@ -51,12 +53,76 @@ describe("data type support", function () {
51
53
  }
52
54
  );
53
55
  });
56
+
57
+ it("supports STRUCT values with NULL", function (done) {
58
+ db.run("CREATE TABLE struct_table (s STRUCT(a VARCHAR, b BOOLEAN))");
59
+ db.run("INSERT INTO struct_table VALUES ({'a': 'hello', 'b': true})");
60
+ db.run("INSERT INTO struct_table VALUES ({'a': 'goodbye', 'b': false})");
61
+ db.run("INSERT INTO struct_table VALUES ({'a': 'aloha', 'b': NULL})");
62
+ db.prepare("SELECT s from struct_table;").all((err, res) => {
63
+ assert(err === null);
64
+ assert.deepEqual(res, [
65
+ { s: { a: "hello", b: true } },
66
+ { s: { a: "goodbye", b: false } },
67
+ { s: { a: "aloha", b: null } },
68
+ ]);
69
+ done();
70
+ });
71
+ });
72
+
73
+ it("recursively supports NULL values", function (done) {
74
+ db.run(
75
+ `CREATE TABLE recursive_struct AS SELECT [
76
+ { 'a': 42, 'b': [1, 2, 3]},
77
+ NULL,
78
+ { 'a': NULL, 'b': [4, NULL, 6]},
79
+ {'a': 43, 'b': NULL}
80
+ ] l UNION ALL SELECT NULL`
81
+ );
82
+ db.prepare("SELECT l from recursive_struct").all((err, res) => {
83
+ assert(err === null);
84
+ assert.deepEqual(res, [
85
+ {
86
+ l: [
87
+ {
88
+ a: 42,
89
+ b: [1, 2, 3],
90
+ },
91
+ null,
92
+ {
93
+ a: null,
94
+ b: [4, null, 6],
95
+ },
96
+ {
97
+ a: 43,
98
+ b: null,
99
+ },
100
+ ],
101
+ },
102
+ {
103
+ l: null,
104
+ },
105
+ ]);
106
+ done();
107
+ });
108
+ });
109
+
54
110
  it("supports LIST values", function (done) {
55
111
  db.prepare(`SELECT ['duck', 'duck', 'goose'] as list`).each((err, row) => {
112
+ assert(err === null);
56
113
  assert.deepEqual(row.list, ["duck", "duck", "goose"]);
57
114
  done();
58
115
  });
59
116
  });
117
+
118
+ it("supports LIST with NULL values", function (done) {
119
+ db.prepare(`SELECT ['duck', 'duck', NULL] as list`).each((err, row) => {
120
+ assert(err === null);
121
+ assert.deepEqual(row.list, ["duck", "duck", null]);
122
+ done();
123
+ });
124
+ });
125
+
60
126
  it("supports DATE values", function (done) {
61
127
  db.prepare(`SELECT '2021-01-01'::DATE as dt;`).each((err, row) => {
62
128
  assert(err === null);
@@ -65,23 +131,27 @@ describe("data type support", function () {
65
131
  });
66
132
  });
67
133
  it("supports TIMESTAMP values", function (done) {
68
- db.prepare(`SELECT '2021-01-01T00:00:00'::TIMESTAMP as ts;`).each((err, row) => {
69
- assert(err === null);
70
- assert.deepEqual(row.ts, new Date(Date.UTC(2021, 0, 1)));
71
- done();
72
- });
134
+ db.prepare(`SELECT '2021-01-01T00:00:00'::TIMESTAMP as ts;`).each(
135
+ (err, row) => {
136
+ assert(err === null);
137
+ assert.deepEqual(row.ts, new Date(Date.UTC(2021, 0, 1)));
138
+ done();
139
+ }
140
+ );
73
141
  });
74
142
  it("supports TIMESTAMP WITH TIME ZONE values", function (done) {
75
- db.prepare(`SELECT '2021-01-01T00:00:00Z'::TIMESTAMPTZ as tstz;`).each((err, row) => {
76
- assert(err === null);
77
- assert.deepEqual(row.tstz, new Date(Date.UTC(2021, 0, 1)));
78
- done();
79
- });
143
+ db.prepare(`SELECT '2021-01-01T00:00:00Z'::TIMESTAMPTZ as tstz;`).each(
144
+ (err, row) => {
145
+ assert(err === null);
146
+ assert.deepEqual(row.tstz, new Date(Date.UTC(2021, 0, 1)));
147
+ done();
148
+ }
149
+ );
80
150
  });
81
151
  it("supports DECIMAL values", function (done) {
82
152
  db.run("CREATE TABLE decimal_table (d DECIMAL(24, 6))");
83
153
  const stmt = db.prepare("INSERT INTO decimal_table VALUES (?)");
84
- const values = [0, -1, 23534642362547.543463];
154
+ const values = [0, -1, 23534642362547.543463, null];
85
155
  values.forEach((d) => {
86
156
  stmt.run(d);
87
157
  });
@@ -67,7 +67,7 @@ describe('Extension loading', function() {
67
67
  var db;
68
68
 
69
69
  before(function(done) {
70
- db = new duckdb.Database(':memory:', done);
70
+ db = new duckdb.Database(':memory:', {"allow_unsigned_extensions":"true"}, done);
71
71
  });
72
72
 
73
73
  for (ext of extension_paths) {
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Intended to be similar to stubtest for python
3
+ */
4
+
5
+ var duckdb = require("..");
6
+ var jsdoc = require("jsdoc3-parser");
7
+ const { expect } = require('chai');
8
+ const { promisify } = require('util');
9
+
10
+ function lastDot(string) {
11
+ if (string.endsWith(')')) {
12
+ string = string.substr(0, string.length - 1);
13
+ }
14
+ const array = string.split('.');
15
+ return array[array.length - 1];
16
+ }
17
+
18
+ /**
19
+ * @typedef {Object} Node
20
+ * @property {string} name
21
+ * @property {string} memberof
22
+ * @property {string} longname
23
+ * @property {string} scope
24
+ */
25
+
26
+ describe("JSDoc contains all methods", () => {
27
+ /**
28
+ * @type {Node[]}
29
+ */
30
+ let docs;
31
+ before(async () => {
32
+ docs = await promisify(jsdoc)(require.resolve("../lib/duckdb"));
33
+ })
34
+
35
+ function checkDocs(obj, scope) {
36
+ const symbols = Object.getOwnPropertySymbols(obj).map(i => lastDot(i.toString()));
37
+ const expected = Object
38
+ .getOwnPropertyNames(obj)
39
+ .concat(symbols)
40
+ .sort()
41
+ .filter(name => name !== 'constructor');
42
+
43
+ const actual = docs
44
+ .filter((node) => node.memberof === scope && !node.undocumented)
45
+ .map((node) => lastDot(node.name))
46
+ .sort();
47
+
48
+ expect(expected).to.deep.equals(actual, 'items missing from documentation');
49
+ }
50
+
51
+ for (const clazz of ['Database', 'QueryResult', 'Connection', 'Statement']) {
52
+ it(clazz, () => {
53
+ checkDocs(duckdb[clazz].prototype, `module:duckdb~${clazz}`);
54
+ });
55
+ }
56
+
57
+ it('module root', () => {
58
+ checkDocs(duckdb, 'module:duckdb');
59
+ });
60
+ });
@@ -0,0 +1,23 @@
1
+ var duckdb = require('..');
2
+ var assert = require('assert');
3
+
4
+ describe('QueryResult', () => {
5
+ const total = 1000;
6
+
7
+ let db;
8
+ let conn;
9
+ before((done) => {
10
+ db = new duckdb.Database(':memory:', () => {
11
+ conn = new duckdb.Connection(db, done);
12
+ });
13
+ });
14
+
15
+ it('streams results', async () => {
16
+ let retrieved = 0;
17
+ const stream = conn.stream('SELECT * FROM range(0, ?)', total);
18
+ for await (const row of stream) {
19
+ retrieved++;
20
+ }
21
+ assert.equal(total, retrieved)
22
+ })
23
+ })
@@ -0,0 +1,16 @@
1
+ var duckdb = require('..');
2
+ var assert = require('assert');
3
+
4
+ describe('exec', function() {
5
+ var db;
6
+ before(function(done) {
7
+ db = new duckdb.Database(':memory:', done);
8
+ });
9
+
10
+ it("doesn't crash on a syntax error", function(done) {
11
+ db.exec("syntax error", function(err) {
12
+ assert.notEqual(err, null, "Expected an error")
13
+ done();
14
+ });
15
+ });
16
+ });