duckdb 0.4.1-dev960.0 → 0.5.1-dev101.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.
@@ -34,6 +34,10 @@ public:
34
34
 
35
35
  #include "duckdb.hpp"
36
36
  #ifndef DUCKDB_AMALGAMATION
37
+ #include "duckdb/planner/table_filter.hpp"
38
+ #include "duckdb/planner/filter/constant_filter.hpp"
39
+ #include "duckdb/planner/filter/null_filter.hpp"
40
+ #include "duckdb/planner/filter/conjunction_filter.hpp"
37
41
  #include "duckdb/common/common.hpp"
38
42
  #include "duckdb/common/exception.hpp"
39
43
  #include "duckdb/common/string_util.hpp"
@@ -6926,7 +6930,7 @@ struct ReadHead {
6926
6930
  uint64_t size;
6927
6931
 
6928
6932
  // Current info
6929
- unique_ptr<AllocatedData> data;
6933
+ AllocatedData data;
6930
6934
  bool data_isset = false;
6931
6935
 
6932
6936
  idx_t GetEnd() const {
@@ -7021,7 +7025,7 @@ struct ReadAheadBuffer {
7021
7025
  throw std::runtime_error("Prefetch registered requested for bytes outside file");
7022
7026
  }
7023
7027
 
7024
- handle.Read(read_head.data->get(), read_head.size, read_head.location);
7028
+ handle.Read(read_head.data.get(), read_head.size, read_head.location);
7025
7029
  read_head.data_isset = true;
7026
7030
  }
7027
7031
  }
@@ -7043,16 +7047,16 @@ public:
7043
7047
 
7044
7048
  if (!prefetch_buffer->data_isset) {
7045
7049
  prefetch_buffer->Allocate(allocator);
7046
- handle.Read(prefetch_buffer->data->get(), prefetch_buffer->size, prefetch_buffer->location);
7050
+ handle.Read(prefetch_buffer->data.get(), prefetch_buffer->size, prefetch_buffer->location);
7047
7051
  prefetch_buffer->data_isset = true;
7048
7052
  }
7049
- memcpy(buf, prefetch_buffer->data->get() + location - prefetch_buffer->location, len);
7053
+ memcpy(buf, prefetch_buffer->data.get() + location - prefetch_buffer->location, len);
7050
7054
  } else {
7051
7055
  if (prefetch_mode && len < PREFETCH_FALLBACK_BUFFERSIZE && len > 0) {
7052
7056
  Prefetch(location, MinValue<uint64_t>(PREFETCH_FALLBACK_BUFFERSIZE, handle.GetFileSize() - location));
7053
7057
  auto prefetch_buffer_fallback = ra_buffer.GetReadHead(location);
7054
7058
  D_ASSERT(location - prefetch_buffer_fallback->location + len <= prefetch_buffer_fallback->size);
7055
- memcpy(buf, prefetch_buffer_fallback->data->get() + location - prefetch_buffer_fallback->location, len);
7059
+ memcpy(buf, prefetch_buffer_fallback->data.get() + location - prefetch_buffer_fallback->location, len);
7056
7060
  } else {
7057
7061
  handle.Read(buf, len, location);
7058
7062
  }
@@ -7193,12 +7197,12 @@ public:
7193
7197
  if (new_size > alloc_len) {
7194
7198
  alloc_len = new_size;
7195
7199
  allocated_data = allocator.Allocate(alloc_len);
7196
- ptr = (char *)allocated_data->get();
7200
+ ptr = (char *)allocated_data.get();
7197
7201
  }
7198
7202
  }
7199
7203
 
7200
7204
  private:
7201
- unique_ptr<AllocatedData> allocated_data;
7205
+ AllocatedData allocated_data;
7202
7206
  idx_t alloc_len = 0;
7203
7207
  };
7204
7208
 
@@ -7701,7 +7705,6 @@ class FileMetaData;
7701
7705
  namespace duckdb {
7702
7706
  class Allocator;
7703
7707
  class ClientContext;
7704
- class ChunkCollection;
7705
7708
  class BaseStatistics;
7706
7709
  class TableFilterSet;
7707
7710
 
@@ -7738,6 +7741,10 @@ struct ParquetOptions {
7738
7741
  bool binary_as_string = false;
7739
7742
  bool filename = false;
7740
7743
  bool hive_partitioning = false;
7744
+
7745
+ public:
7746
+ void Serialize(FieldWriter &writer) const;
7747
+ void Deserialize(FieldReader &reader);
7741
7748
  };
7742
7749
 
7743
7750
  class ParquetReader {
@@ -7834,7 +7841,7 @@ private:
7834
7841
  #include "duckdb/common/exception.hpp"
7835
7842
  #include "duckdb/common/mutex.hpp"
7836
7843
  #include "duckdb/common/serializer/buffered_file_writer.hpp"
7837
- #include "duckdb/common/types/chunk_collection.hpp"
7844
+ #include "duckdb/common/types/column_data_collection.hpp"
7838
7845
  #endif
7839
7846
 
7840
7847
 
@@ -7950,7 +7957,7 @@ public:
7950
7957
  vector<string> names, duckdb_parquet::format::CompressionCodec::type codec);
7951
7958
 
7952
7959
  public:
7953
- void Flush(ChunkCollection &buffer);
7960
+ void Flush(ColumnDataCollection &buffer);
7954
7961
  void Finalize();
7955
7962
 
7956
7963
  static duckdb_parquet::format::Type::type DuckDBTypeToParquetType(const LogicalType &duckdb_type);
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,23 +130,45 @@ 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: {
132
140
  value = Napi::Boolean::New(env, duckdb::BooleanValue::Get(dval));
133
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;
134
148
  case duckdb::LogicalTypeId::INTEGER: {
135
149
  value = Napi::Number::New(env, duckdb::IntegerValue::Get(dval));
136
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;
137
166
  case duckdb::LogicalTypeId::FLOAT: {
138
167
  value = Napi::Number::New(env, duckdb::FloatValue::Get(dval));
139
168
  } break;
140
169
  case duckdb::LogicalTypeId::DOUBLE: {
141
170
  value = Napi::Number::New(env, duckdb::DoubleValue::Get(dval));
142
171
  } break;
143
- case duckdb::LogicalTypeId::BIGINT: {
144
- value = Napi::Number::New(env, duckdb::BigIntValue::Get(dval));
145
- } break;
146
172
  case duckdb::LogicalTypeId::HUGEINT: {
147
173
  value = Napi::Number::New(env, dval.GetValue<double>());
148
174
  } break;
@@ -221,11 +247,6 @@ static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names,
221
247
 
222
248
  for (duckdb::idx_t col_idx = 0; col_idx < chunk.ColumnCount(); col_idx++) {
223
249
  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
250
  row_result.Set(node_names[col_idx], convert_col_val(env, dval, chunk.data[col_idx].GetType().id()));
230
251
  }
231
252
  result.Set(row_idx, row_result);
@@ -250,7 +271,7 @@ struct RunPreparedTask : public Task {
250
271
  void DoWork() override {
251
272
  auto &statement = Get<Statement>();
252
273
  // ignorant folk arrive here without caring about the prepare callback error
253
- if (!statement.statement || !statement.statement->success) {
274
+ if (!statement.statement || statement.statement->HasError()) {
254
275
  return;
255
276
  }
256
277
 
@@ -268,16 +289,12 @@ struct RunPreparedTask : public Task {
268
289
  cb.MakeCallback(statement.Value(), {Utils::CreateError(env, "statement was finalized")});
269
290
  return;
270
291
  }
271
- if (!statement.statement->success) {
272
- 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())});
273
294
  return;
274
295
  }
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)});
296
+ if (result->HasError()) {
297
+ cb.MakeCallback(statement.Value(), {Utils::CreateError(env, result->GetError())});
281
298
  return;
282
299
  }
283
300
 
@@ -312,7 +329,7 @@ struct RunPreparedTask : public Task {
312
329
  }
313
330
  case RunType::ALL: {
314
331
  auto materialized_result = (duckdb::MaterializedQueryResult *)result.get();
315
- Napi::Array result_arr(Napi::Array::New(env, materialized_result->collection.Count()));
332
+ Napi::Array result_arr(Napi::Array::New(env, materialized_result->RowCount()));
316
333
 
317
334
  duckdb::idx_t out_idx = 0;
318
335
  while (true) {
@@ -340,6 +357,45 @@ struct RunPreparedTask : public Task {
340
357
  RunType run_type;
341
358
  };
342
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
+
343
399
  duckdb::unique_ptr<StatementParam> Statement::HandleArgs(const Napi::CallbackInfo &info) {
344
400
  size_t start_idx = ignore_first_param ? 1 : 0;
345
401
  auto params = duckdb::make_unique<StatementParam>();
@@ -369,19 +425,24 @@ Napi::Value Statement::All(const Napi::CallbackInfo &info) {
369
425
  }
370
426
 
371
427
  Napi::Value Statement::Run(const Napi::CallbackInfo &info) {
372
- auto params = HandleArgs(info);
373
428
  connection_ref->database_ref->Schedule(info.Env(),
374
429
  duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::RUN));
375
430
  return info.This();
376
431
  }
377
432
 
378
433
  Napi::Value Statement::Each(const Napi::CallbackInfo &info) {
379
- auto params = HandleArgs(info);
380
434
  connection_ref->database_ref->Schedule(
381
435
  info.Env(), duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::EACH));
382
436
  return info.This();
383
437
  }
384
438
 
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
+
385
446
  struct FinishTask : public Task {
386
447
  FinishTask(Statement &statement, Napi::Function callback) : Task(statement, callback) {
387
448
  }
@@ -406,4 +467,67 @@ Napi::Value Statement::Finish(const Napi::CallbackInfo &info) {
406
467
  return env.Null();
407
468
  }
408
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
+
409
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
  });
@@ -74,6 +74,10 @@ describe('Extension loading', function() {
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
+ })