duckdb 0.4.1-dev98.0 → 0.5.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 {
@@ -34,8 +40,8 @@ struct PrepareTask : public Task {
34
40
  Napi::HandleScope scope(env);
35
41
 
36
42
  auto cb = callback.Value();
37
- if (!statement.statement->success) {
38
- 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())});
39
45
  return;
40
46
  }
41
47
  cb.MakeCallback(statement.Value(), {env.Null(), statement.Value()});
@@ -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,23 +130,45 @@ 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: {
130
140
  value = Napi::Boolean::New(env, duckdb::BooleanValue::Get(dval));
131
141
  } break;
142
+ case duckdb::LogicalTypeId::TINYINT: {
143
+ value = Napi::Number::New(env, duckdb::TinyIntValue::Get(dval));
144
+ } break;
145
+ case duckdb::LogicalTypeId::SMALLINT: {
146
+ value = Napi::Number::New(env, duckdb::SmallIntValue::Get(dval));
147
+ } break;
132
148
  case duckdb::LogicalTypeId::INTEGER: {
133
149
  value = Napi::Number::New(env, duckdb::IntegerValue::Get(dval));
134
150
  } break;
151
+ case duckdb::LogicalTypeId::BIGINT: {
152
+ value = Napi::Number::New(env, duckdb::BigIntValue::Get(dval));
153
+ } break;
154
+ case duckdb::LogicalTypeId::UTINYINT: {
155
+ value = Napi::Number::New(env, duckdb::UTinyIntValue::Get(dval));
156
+ } break;
157
+ case duckdb::LogicalTypeId::USMALLINT: {
158
+ value = Napi::Number::New(env, duckdb::USmallIntValue::Get(dval));
159
+ } break;
160
+ case duckdb::LogicalTypeId::UINTEGER: {
161
+ value = Napi::Number::New(env, duckdb::UIntegerValue::Get(dval));
162
+ } break;
163
+ case duckdb::LogicalTypeId::UBIGINT: {
164
+ value = Napi::Number::New(env, duckdb::UBigIntValue::Get(dval));
165
+ } break;
135
166
  case duckdb::LogicalTypeId::FLOAT: {
136
167
  value = Napi::Number::New(env, duckdb::FloatValue::Get(dval));
137
168
  } break;
138
169
  case duckdb::LogicalTypeId::DOUBLE: {
139
170
  value = Napi::Number::New(env, duckdb::DoubleValue::Get(dval));
140
171
  } break;
141
- case duckdb::LogicalTypeId::BIGINT: {
142
- value = Napi::Number::New(env, duckdb::BigIntValue::Get(dval));
143
- } break;
144
172
  case duckdb::LogicalTypeId::HUGEINT: {
145
173
  value = Napi::Number::New(env, dval.GetValue<double>());
146
174
  } break;
@@ -208,6 +236,7 @@ static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names,
208
236
  Napi::EscapableHandleScope scope(env);
209
237
  std::vector<Napi::String> node_names;
210
238
  assert(names.size() == chunk.ColumnCount());
239
+ node_names.reserve(names.size());
211
240
  for (auto &name : names) {
212
241
  node_names.push_back(Napi::String::New(env, name));
213
242
  }
@@ -218,11 +247,6 @@ static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names,
218
247
 
219
248
  for (duckdb::idx_t col_idx = 0; col_idx < chunk.ColumnCount(); col_idx++) {
220
249
  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
250
  row_result.Set(node_names[col_idx], convert_col_val(env, dval, chunk.data[col_idx].GetType().id()));
227
251
  }
228
252
  result.Set(row_idx, row_result);
@@ -240,14 +264,14 @@ struct StatementParam {
240
264
  };
241
265
 
242
266
  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_) {
267
+ RunPreparedTask(Statement &statement, duckdb::unique_ptr<StatementParam> params, RunType run_type)
268
+ : Task(statement, params->callback), params(move(params)), run_type(run_type) {
245
269
  }
246
270
 
247
271
  void DoWork() override {
248
272
  auto &statement = Get<Statement>();
249
273
  // ignorant folk arrive here without caring about the prepare callback error
250
- if (!statement.statement || !statement.statement->success) {
274
+ if (!statement.statement || statement.statement->HasError()) {
251
275
  return;
252
276
  }
253
277
 
@@ -265,16 +289,12 @@ struct RunPreparedTask : public Task {
265
289
  cb.MakeCallback(statement.Value(), {Utils::CreateError(env, "statement was finalized")});
266
290
  return;
267
291
  }
268
- if (!statement.statement->success) {
269
- cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error)});
270
- return;
271
- }
272
- if (!statement.statement->success) {
273
- cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error)});
292
+ if (statement.statement->HasError()) {
293
+ cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->GetError())});
274
294
  return;
275
295
  }
276
- if (!result->success) {
277
- cb.MakeCallback(statement.Value(), {Utils::CreateError(env, result->error)});
296
+ if (result->HasError()) {
297
+ cb.MakeCallback(statement.Value(), {Utils::CreateError(env, result->GetError())});
278
298
  return;
279
299
  }
280
300
 
@@ -285,6 +305,8 @@ struct RunPreparedTask : public Task {
285
305
  case RunType::EACH: {
286
306
  duckdb::idx_t count = 0;
287
307
  while (true) {
308
+ Napi::HandleScope scope(env);
309
+
288
310
  auto chunk = result->Fetch();
289
311
  if (!chunk || chunk->size() == 0) {
290
312
  break;
@@ -307,7 +329,7 @@ struct RunPreparedTask : public Task {
307
329
  }
308
330
  case RunType::ALL: {
309
331
  auto materialized_result = (duckdb::MaterializedQueryResult *)result.get();
310
- Napi::Array result_arr(Napi::Array::New(env, materialized_result->collection.Count()));
332
+ Napi::Array result_arr(Napi::Array::New(env, materialized_result->RowCount()));
311
333
 
312
334
  duckdb::idx_t out_idx = 0;
313
335
  while (true) {
@@ -335,6 +357,45 @@ struct RunPreparedTask : public Task {
335
357
  RunType run_type;
336
358
  };
337
359
 
360
+ struct RunQueryTask : public Task {
361
+ RunQueryTask(Statement &statement, duckdb::unique_ptr<StatementParam> params, Napi::Promise::Deferred deferred)
362
+ : Task(statement), deferred(deferred), params(move(params)) {
363
+ }
364
+
365
+ void DoWork() override {
366
+ auto &statement = Get<Statement>();
367
+ if (!statement.statement || statement.statement->HasError()) {
368
+ return;
369
+ }
370
+
371
+ result = statement.statement->Execute(params->params, true);
372
+ }
373
+
374
+ void DoCallback() override {
375
+ auto &statement = Get<Statement>();
376
+ Napi::Env env = statement.Env();
377
+ Napi::HandleScope scope(env);
378
+
379
+ if (!statement.statement) {
380
+ deferred.Reject(Utils::CreateError(env, "statement was finalized"));
381
+ } else if (statement.statement->HasError()) {
382
+ deferred.Reject(Utils::CreateError(env, statement.statement->GetError()));
383
+ } else if (result->HasError()) {
384
+ deferred.Reject(Utils::CreateError(env, result->GetError()));
385
+ } else {
386
+ auto db = statement.connection_ref->database_ref->Value();
387
+ auto query_result = QueryResult::constructor.New({db});
388
+ auto unwrapped = Napi::ObjectWrap<QueryResult>::Unwrap(query_result);
389
+ unwrapped->result = move(result);
390
+ deferred.Resolve(query_result);
391
+ }
392
+ }
393
+
394
+ Napi::Promise::Deferred deferred;
395
+ std::unique_ptr<duckdb::QueryResult> result;
396
+ duckdb::unique_ptr<StatementParam> params;
397
+ };
398
+
338
399
  duckdb::unique_ptr<StatementParam> Statement::HandleArgs(const Napi::CallbackInfo &info) {
339
400
  size_t start_idx = ignore_first_param ? 1 : 0;
340
401
  auto params = duckdb::make_unique<StatementParam>();
@@ -352,7 +413,7 @@ duckdb::unique_ptr<StatementParam> Statement::HandleArgs(const Napi::CallbackInf
352
413
  if (p.IsUndefined()) {
353
414
  continue;
354
415
  }
355
- params->params.push_back(bind_parameter(p));
416
+ params->params.push_back(BindParameter(p));
356
417
  }
357
418
  return params;
358
419
  }
@@ -364,21 +425,26 @@ Napi::Value Statement::All(const Napi::CallbackInfo &info) {
364
425
  }
365
426
 
366
427
  Napi::Value Statement::Run(const Napi::CallbackInfo &info) {
367
- auto params = HandleArgs(info);
368
428
  connection_ref->database_ref->Schedule(info.Env(),
369
429
  duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::RUN));
370
430
  return info.This();
371
431
  }
372
432
 
373
433
  Napi::Value Statement::Each(const Napi::CallbackInfo &info) {
374
- auto params = HandleArgs(info);
375
434
  connection_ref->database_ref->Schedule(
376
435
  info.Env(), duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::EACH));
377
436
  return info.This();
378
437
  }
379
438
 
380
- struct FinalizeTask : public Task {
381
- FinalizeTask(Statement &statement_, Napi::Function callback_) : Task(statement_, callback_) {
439
+ Napi::Value Statement::Stream(const Napi::CallbackInfo &info) {
440
+ auto deferred = Napi::Promise::Deferred::New(info.Env());
441
+ connection_ref->database_ref->Schedule(info.Env(),
442
+ duckdb::make_unique<RunQueryTask>(*this, HandleArgs(info), deferred));
443
+ return deferred.Promise();
444
+ }
445
+
446
+ struct FinishTask : public Task {
447
+ FinishTask(Statement &statement, Napi::Function callback) : Task(statement, callback) {
382
448
  }
383
449
 
384
450
  void DoWork() override {
@@ -387,7 +453,7 @@ struct FinalizeTask : public Task {
387
453
  }
388
454
  };
389
455
 
390
- Napi::Value Statement::Finalize_(const Napi::CallbackInfo &info) {
456
+ Napi::Value Statement::Finish(const Napi::CallbackInfo &info) {
391
457
  Napi::Env env = info.Env();
392
458
  Napi::HandleScope scope(env);
393
459
 
@@ -397,8 +463,71 @@ Napi::Value Statement::Finalize_(const Napi::CallbackInfo &info) {
397
463
  callback = info[0].As<Napi::Function>();
398
464
  }
399
465
 
400
- connection_ref->database_ref->Schedule(env, duckdb::make_unique<FinalizeTask>(*this, callback));
466
+ connection_ref->database_ref->Schedule(env, duckdb::make_unique<FinishTask>(*this, callback));
401
467
  return env.Null();
402
468
  }
403
469
 
470
+ Napi::FunctionReference QueryResult::constructor;
471
+
472
+ Napi::Object QueryResult::Init(Napi::Env env, Napi::Object exports) {
473
+ Napi::HandleScope scope(env);
474
+
475
+ Napi::Function t = DefineClass(env, "QueryResult", {InstanceMethod("nextChunk", &QueryResult::NextChunk)});
476
+
477
+ constructor = Napi::Persistent(t);
478
+ constructor.SuppressDestruct();
479
+
480
+ exports.Set("QueryResult", t);
481
+ return exports;
482
+ }
483
+
484
+ QueryResult::QueryResult(const Napi::CallbackInfo &info) : Napi::ObjectWrap<QueryResult>(info) {
485
+ database_ref = Napi::ObjectWrap<Database>::Unwrap(info[0].As<Napi::Object>());
486
+ database_ref->Ref();
487
+ }
488
+
489
+ QueryResult::~QueryResult() {
490
+ database_ref->Unref();
491
+ database_ref = nullptr;
492
+ }
493
+
494
+ struct GetChunkTask : public Task {
495
+ GetChunkTask(QueryResult &query_result, Napi::Promise::Deferred deferred) : Task(query_result), deferred(deferred) {
496
+ }
497
+
498
+ void DoWork() override {
499
+ auto &query_result = Get<QueryResult>();
500
+ chunk = query_result.result->Fetch();
501
+ }
502
+
503
+ void DoCallback() override {
504
+ auto &query_result = Get<QueryResult>();
505
+ Napi::Env env = query_result.Env();
506
+ Napi::HandleScope scope(env);
507
+
508
+ if (chunk == nullptr || chunk->size() == 0) {
509
+ deferred.Resolve(env.Null());
510
+ return;
511
+ }
512
+
513
+ auto chunk_converted = convert_chunk(env, query_result.result->names, *chunk).ToObject();
514
+ if (!chunk_converted.IsArray()) {
515
+ deferred.Reject(Utils::CreateError(env, "internal error: chunk is not array"));
516
+ } else {
517
+ deferred.Resolve(chunk_converted);
518
+ }
519
+ }
520
+
521
+ Napi::Promise::Deferred deferred;
522
+ std::unique_ptr<duckdb::DataChunk> chunk;
523
+ };
524
+
525
+ Napi::Value QueryResult::NextChunk(const Napi::CallbackInfo &info) {
526
+ auto env = info.Env();
527
+ auto deferred = Napi::Promise::Deferred::New(env);
528
+ database_ref->Schedule(env, duckdb::make_unique<GetChunkTask>(*this, deferred));
529
+
530
+ return deferred.Promise();
531
+ }
532
+
404
533
  } // 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
  });
@@ -20,8 +20,36 @@ describe("data type support", function () {
20
20
  done();
21
21
  });
22
22
  });
23
+
24
+ it("supports INTEGER values", function (done) {
25
+ db.run("CREATE TABLE integer_table (a TINYINT, b SMALLINT, c INTEGER, d BIGINT, e UTINYINT, f USMALLINT, g UINTEGER, h UBIGINT)");
26
+ const stmt = db.prepare("INSERT INTO integer_table VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
27
+
28
+ // Numerical limits
29
+ signedMinValue = (bitWidth) => Math.max(-(2**(bitWidth-1)-1)-1, Number.MIN_SAFE_INTEGER);
30
+ signedMaxValue = (bitWidth) => Math.min(2**(bitWidth-1)-1, Number.MAX_SAFE_INTEGER);
31
+ unsignedMaxValue = (bitWidth) => Math.min(2**(bitWidth)-1, Number.MAX_SAFE_INTEGER);
32
+ let minValues = [signedMinValue(8), signedMinValue(16), signedMinValue(32), signedMinValue(64), 0, 0, 0, 0];
33
+ let maxValues = [signedMinValue(8), signedMinValue(16), signedMinValue(32), signedMinValue(64), unsignedMaxValue(8), unsignedMaxValue(16), unsignedMaxValue(32), unsignedMaxValue(64)];
34
+
35
+ // Insert values
36
+ stmt.run(...minValues);
37
+ stmt.run(...maxValues);
38
+
39
+ db.prepare("SELECT * from integer_table;").all((err, res) => {
40
+ assert(err === null);
41
+ assert(res.length === 2);
42
+ assert(Object.entries(res[0]).length === 8);
43
+ assert(Object.entries(res[1]).length === 8);
44
+ assert.deepEqual(Object.entries(res[0]).map(v => v[1]), minValues);
45
+ assert.deepEqual(Object.entries(res[1]).map(v => v[1]), maxValues);
46
+ done();
47
+ });
48
+ });
49
+
23
50
  it("supports INTERVAL values", function (done) {
24
- db.prepare(`SELECT
51
+ db.prepare(
52
+ `SELECT
25
53
  INTERVAL 1 MINUTE as minutes,
26
54
  INTERVAL 5 DAY as days,
27
55
  INTERVAL 4 MONTH as months,
@@ -43,6 +71,7 @@ describe("data type support", function () {
43
71
  done();
44
72
  });
45
73
  });
74
+
46
75
  it("supports STRUCT values", function (done) {
47
76
  db.prepare(`SELECT {'x': 1, 'y': 2, 'z': {'a': 'b'}} as struct`).each(
48
77
  (err, row) => {
@@ -51,12 +80,76 @@ describe("data type support", function () {
51
80
  }
52
81
  );
53
82
  });
83
+
84
+ it("supports STRUCT values with NULL", function (done) {
85
+ db.run("CREATE TABLE struct_table (s STRUCT(a VARCHAR, b BOOLEAN))");
86
+ db.run("INSERT INTO struct_table VALUES ({'a': 'hello', 'b': true})");
87
+ db.run("INSERT INTO struct_table VALUES ({'a': 'goodbye', 'b': false})");
88
+ db.run("INSERT INTO struct_table VALUES ({'a': 'aloha', 'b': NULL})");
89
+ db.prepare("SELECT s from struct_table;").all((err, res) => {
90
+ assert(err === null);
91
+ assert.deepEqual(res, [
92
+ { s: { a: "hello", b: true } },
93
+ { s: { a: "goodbye", b: false } },
94
+ { s: { a: "aloha", b: null } },
95
+ ]);
96
+ done();
97
+ });
98
+ });
99
+
100
+ it("recursively supports NULL values", function (done) {
101
+ db.run(
102
+ `CREATE TABLE recursive_struct AS SELECT [
103
+ { 'a': 42, 'b': [1, 2, 3]},
104
+ NULL,
105
+ { 'a': NULL, 'b': [4, NULL, 6]},
106
+ {'a': 43, 'b': NULL}
107
+ ] l UNION ALL SELECT NULL`
108
+ );
109
+ db.prepare("SELECT l from recursive_struct").all((err, res) => {
110
+ assert(err === null);
111
+ assert.deepEqual(res, [
112
+ {
113
+ l: [
114
+ {
115
+ a: 42,
116
+ b: [1, 2, 3],
117
+ },
118
+ null,
119
+ {
120
+ a: null,
121
+ b: [4, null, 6],
122
+ },
123
+ {
124
+ a: 43,
125
+ b: null,
126
+ },
127
+ ],
128
+ },
129
+ {
130
+ l: null,
131
+ },
132
+ ]);
133
+ done();
134
+ });
135
+ });
136
+
54
137
  it("supports LIST values", function (done) {
55
138
  db.prepare(`SELECT ['duck', 'duck', 'goose'] as list`).each((err, row) => {
139
+ assert(err === null);
56
140
  assert.deepEqual(row.list, ["duck", "duck", "goose"]);
57
141
  done();
58
142
  });
59
143
  });
144
+
145
+ it("supports LIST with NULL values", function (done) {
146
+ db.prepare(`SELECT ['duck', 'duck', NULL] as list`).each((err, row) => {
147
+ assert(err === null);
148
+ assert.deepEqual(row.list, ["duck", "duck", null]);
149
+ done();
150
+ });
151
+ });
152
+
60
153
  it("supports DATE values", function (done) {
61
154
  db.prepare(`SELECT '2021-01-01'::DATE as dt;`).each((err, row) => {
62
155
  assert(err === null);
@@ -65,23 +158,27 @@ describe("data type support", function () {
65
158
  });
66
159
  });
67
160
  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
- });
161
+ db.prepare(`SELECT '2021-01-01T00:00:00'::TIMESTAMP as ts;`).each(
162
+ (err, row) => {
163
+ assert(err === null);
164
+ assert.deepEqual(row.ts, new Date(Date.UTC(2021, 0, 1)));
165
+ done();
166
+ }
167
+ );
73
168
  });
74
169
  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
- });
170
+ db.prepare(`SELECT '2021-01-01T00:00:00Z'::TIMESTAMPTZ as tstz;`).each(
171
+ (err, row) => {
172
+ assert(err === null);
173
+ assert.deepEqual(row.tstz, new Date(Date.UTC(2021, 0, 1)));
174
+ done();
175
+ }
176
+ );
80
177
  });
81
178
  it("supports DECIMAL values", function (done) {
82
179
  db.run("CREATE TABLE decimal_table (d DECIMAL(24, 6))");
83
180
  const stmt = db.prepare("INSERT INTO decimal_table VALUES (?)");
84
- const values = [0, -1, 23534642362547.543463];
181
+ const values = [0, -1, 23534642362547.543463, null];
85
182
  values.forEach((d) => {
86
183
  stmt.run(d);
87
184
  });
@@ -67,13 +67,17 @@ 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) {
74
74
  const extension_path = ext;
75
75
  const extension_name = ext.replace(/^.*[\\\/]/, '');
76
76
 
77
+ if (extension_name.startsWith('parquet')) { // Parquet is built-in in the Node client, so skip
78
+ continue;
79
+ }
80
+
77
81
  it(extension_name, function(done) {
78
82
  db.run(`LOAD '${extension_path}';`, function(err) {
79
83
  if (err) {
@@ -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
+ });