duckdb 0.4.1-dev182.0 → 0.4.1-dev1896.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,5 +1,8 @@
1
+ #include "duckdb.hpp"
1
2
  #include "duckdb_node.hpp"
3
+ #include "napi.h"
2
4
 
5
+ #include <algorithm>
3
6
  #include <cassert>
4
7
 
5
8
  namespace node_duckdb {
@@ -12,7 +15,8 @@ Napi::Object Statement::Init(Napi::Env env, Napi::Object exports) {
12
15
  Napi::Function t =
13
16
  DefineClass(env, "Statement",
14
17
  {InstanceMethod("run", &Statement::Run), InstanceMethod("all", &Statement::All),
15
- InstanceMethod("each", &Statement::Each), InstanceMethod("finalize", &Statement::Finish)});
18
+ InstanceMethod("each", &Statement::Each), InstanceMethod("finalize", &Statement::Finish),
19
+ InstanceMethod("stream", &Statement::Stream)});
16
20
 
17
21
  constructor = Napi::Persistent(t);
18
22
  constructor.SuppressDestruct();
@@ -36,8 +40,8 @@ struct PrepareTask : public Task {
36
40
  Napi::HandleScope scope(env);
37
41
 
38
42
  auto cb = callback.Value();
39
- if (!statement.statement->success) {
40
- cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error)});
43
+ if (statement.statement->HasError()) {
44
+ cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error.Message())});
41
45
  return;
42
46
  }
43
47
  cb.MakeCallback(statement.Value(), {env.Null(), statement.Value()});
@@ -126,6 +130,10 @@ static duckdb::Value BindParameter(const Napi::Value source) {
126
130
  static Napi::Value convert_col_val(Napi::Env &env, duckdb::Value dval, duckdb::LogicalTypeId id) {
127
131
  Napi::Value value;
128
132
 
133
+ if (dval.IsNull()) {
134
+ return env.Null();
135
+ }
136
+
129
137
  // TODO templateroo here
130
138
  switch (id) {
131
139
  case duckdb::LogicalTypeId::BOOLEAN: {
@@ -221,11 +229,6 @@ static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names,
221
229
 
222
230
  for (duckdb::idx_t col_idx = 0; col_idx < chunk.ColumnCount(); col_idx++) {
223
231
  duckdb::Value dval = chunk.GetValue(col_idx, row_idx);
224
- if (dval.IsNull()) {
225
- row_result.Set(node_names[col_idx], env.Null());
226
- continue;
227
- }
228
-
229
232
  row_result.Set(node_names[col_idx], convert_col_val(env, dval, chunk.data[col_idx].GetType().id()));
230
233
  }
231
234
  result.Set(row_idx, row_result);
@@ -250,7 +253,7 @@ struct RunPreparedTask : public Task {
250
253
  void DoWork() override {
251
254
  auto &statement = Get<Statement>();
252
255
  // ignorant folk arrive here without caring about the prepare callback error
253
- if (!statement.statement || !statement.statement->success) {
256
+ if (!statement.statement || statement.statement->HasError()) {
254
257
  return;
255
258
  }
256
259
 
@@ -268,16 +271,12 @@ struct RunPreparedTask : public Task {
268
271
  cb.MakeCallback(statement.Value(), {Utils::CreateError(env, "statement was finalized")});
269
272
  return;
270
273
  }
271
- if (!statement.statement->success) {
272
- cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error)});
274
+ if (statement.statement->HasError()) {
275
+ cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->GetError())});
273
276
  return;
274
277
  }
275
- if (!statement.statement->success) {
276
- cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error)});
277
- return;
278
- }
279
- if (!result->success) {
280
- cb.MakeCallback(statement.Value(), {Utils::CreateError(env, result->error)});
278
+ if (result->HasError()) {
279
+ cb.MakeCallback(statement.Value(), {Utils::CreateError(env, result->GetError())});
281
280
  return;
282
281
  }
283
282
 
@@ -288,6 +287,8 @@ struct RunPreparedTask : public Task {
288
287
  case RunType::EACH: {
289
288
  duckdb::idx_t count = 0;
290
289
  while (true) {
290
+ Napi::HandleScope scope(env);
291
+
291
292
  auto chunk = result->Fetch();
292
293
  if (!chunk || chunk->size() == 0) {
293
294
  break;
@@ -310,7 +311,7 @@ struct RunPreparedTask : public Task {
310
311
  }
311
312
  case RunType::ALL: {
312
313
  auto materialized_result = (duckdb::MaterializedQueryResult *)result.get();
313
- Napi::Array result_arr(Napi::Array::New(env, materialized_result->collection.Count()));
314
+ Napi::Array result_arr(Napi::Array::New(env, materialized_result->RowCount()));
314
315
 
315
316
  duckdb::idx_t out_idx = 0;
316
317
  while (true) {
@@ -338,6 +339,45 @@ struct RunPreparedTask : public Task {
338
339
  RunType run_type;
339
340
  };
340
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->HasError()) {
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->HasError()) {
364
+ deferred.Reject(Utils::CreateError(env, statement.statement->GetError()));
365
+ } else if (result->HasError()) {
366
+ deferred.Reject(Utils::CreateError(env, result->GetError()));
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
+
341
381
  duckdb::unique_ptr<StatementParam> Statement::HandleArgs(const Napi::CallbackInfo &info) {
342
382
  size_t start_idx = ignore_first_param ? 1 : 0;
343
383
  auto params = duckdb::make_unique<StatementParam>();
@@ -367,19 +407,24 @@ Napi::Value Statement::All(const Napi::CallbackInfo &info) {
367
407
  }
368
408
 
369
409
  Napi::Value Statement::Run(const Napi::CallbackInfo &info) {
370
- auto params = HandleArgs(info);
371
410
  connection_ref->database_ref->Schedule(info.Env(),
372
411
  duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::RUN));
373
412
  return info.This();
374
413
  }
375
414
 
376
415
  Napi::Value Statement::Each(const Napi::CallbackInfo &info) {
377
- auto params = HandleArgs(info);
378
416
  connection_ref->database_ref->Schedule(
379
417
  info.Env(), duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::EACH));
380
418
  return info.This();
381
419
  }
382
420
 
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
+
383
428
  struct FinishTask : public Task {
384
429
  FinishTask(Statement &statement, Napi::Function callback) : Task(statement, callback) {
385
430
  }
@@ -404,4 +449,67 @@ Napi::Value Statement::Finish(const Napi::CallbackInfo &info) {
404
449
  return env.Null();
405
450
  }
406
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
+
407
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
+ });