duckdb 0.4.1-dev223.0 → 0.4.1-dev2253.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
 
@@ -7736,6 +7739,12 @@ struct ParquetOptions {
7736
7739
  explicit ParquetOptions(ClientContext &context);
7737
7740
 
7738
7741
  bool binary_as_string = false;
7742
+ bool filename = false;
7743
+ bool hive_partitioning = false;
7744
+
7745
+ public:
7746
+ void Serialize(FieldWriter &writer) const;
7747
+ void Deserialize(FieldReader &reader);
7739
7748
  };
7740
7749
 
7741
7750
  class ParquetReader {
@@ -7832,7 +7841,7 @@ private:
7832
7841
  #include "duckdb/common/exception.hpp"
7833
7842
  #include "duckdb/common/mutex.hpp"
7834
7843
  #include "duckdb/common/serializer/buffered_file_writer.hpp"
7835
- #include "duckdb/common/types/chunk_collection.hpp"
7844
+ #include "duckdb/common/types/column_data_collection.hpp"
7836
7845
  #endif
7837
7846
 
7838
7847
 
@@ -7853,7 +7862,7 @@ namespace duckdb {
7853
7862
  class BufferedSerializer;
7854
7863
  class ParquetWriter;
7855
7864
  class ColumnWriterPageState;
7856
- class StandardColumnWriterState;
7865
+ class BasicColumnWriterState;
7857
7866
 
7858
7867
  class ColumnWriterState {
7859
7868
  public:
@@ -7875,9 +7884,6 @@ public:
7875
7884
  };
7876
7885
 
7877
7886
  class ColumnWriter {
7878
- //! We limit the uncompressed page size to 100MB
7879
- // The max size in Parquet is 2GB, but we choose a more conservative limit
7880
- static constexpr const idx_t MAX_UNCOMPRESSED_PAGE_SIZE = 100000000;
7881
7887
 
7882
7888
  public:
7883
7889
  ColumnWriter(ParquetWriter &writer, idx_t schema_idx, vector<string> schema_path, idx_t max_repeat,
@@ -7901,46 +7907,35 @@ public:
7901
7907
  idx_t max_repeat = 0, idx_t max_define = 1,
7902
7908
  bool can_have_nulls = true);
7903
7909
 
7904
- virtual unique_ptr<ColumnWriterState> InitializeWriteState(duckdb_parquet::format::RowGroup &row_group);
7905
- virtual void Prepare(ColumnWriterState &state, ColumnWriterState *parent, Vector &vector, idx_t count);
7906
-
7907
- virtual void BeginWrite(ColumnWriterState &state);
7908
- virtual void Write(ColumnWriterState &state, Vector &vector, idx_t count);
7909
- virtual void FinalizeWrite(ColumnWriterState &state);
7910
-
7911
- protected:
7912
- void HandleDefineLevels(ColumnWriterState &state, ColumnWriterState *parent, ValidityMask &validity, idx_t count,
7913
- uint16_t define_value, uint16_t null_value);
7914
- void HandleRepeatLevels(ColumnWriterState &state_p, ColumnWriterState *parent, idx_t count, idx_t max_repeat);
7910
+ virtual unique_ptr<ColumnWriterState> InitializeWriteState(duckdb_parquet::format::RowGroup &row_group) = 0;
7915
7911
 
7916
- void WriteLevels(Serializer &temp_writer, const vector<uint16_t> &levels, idx_t max_value, idx_t start_offset,
7917
- idx_t count);
7912
+ //! indicates whether the write need to analyse the data before preparing it
7913
+ virtual bool HasAnalyze() {
7914
+ return false;
7915
+ }
7918
7916
 
7919
- virtual duckdb_parquet::format::Encoding::type GetEncoding();
7917
+ virtual void Analyze(ColumnWriterState &state, ColumnWriterState *parent, Vector &vector, idx_t count) {
7918
+ throw NotImplementedException("Writer does not need analysis");
7919
+ }
7920
7920
 
7921
- void NextPage(ColumnWriterState &state_p);
7922
- void FlushPage(ColumnWriterState &state_p);
7923
- void WriteDictionary(ColumnWriterState &state_p, unique_ptr<BufferedSerializer> temp_writer, idx_t row_count);
7921
+ //! Called after all data has been passed to Analyze
7922
+ virtual void FinalizeAnalyze(ColumnWriterState &state) {
7923
+ throw NotImplementedException("Writer does not need analysis");
7924
+ }
7924
7925
 
7925
- virtual void FlushDictionary(ColumnWriterState &state, ColumnWriterStatistics *stats);
7926
+ virtual void Prepare(ColumnWriterState &state, ColumnWriterState *parent, Vector &vector, idx_t count) = 0;
7926
7927
 
7927
- //! Initializes the state used to track statistics during writing. Only used for scalar types.
7928
- virtual unique_ptr<ColumnWriterStatistics> InitializeStatsState();
7929
- //! Retrieves the row size of a vector at the specified location. Only used for scalar types.
7930
- virtual idx_t GetRowSize(Vector &vector, idx_t index);
7931
- //! Writes a (subset of a) vector to the specified serializer. Only used for scalar types.
7932
- virtual void WriteVector(Serializer &temp_writer, ColumnWriterStatistics *stats, ColumnWriterPageState *page_state,
7933
- Vector &vector, idx_t chunk_start, idx_t chunk_end);
7928
+ virtual void BeginWrite(ColumnWriterState &state) = 0;
7929
+ virtual void Write(ColumnWriterState &state, Vector &vector, idx_t count) = 0;
7930
+ virtual void FinalizeWrite(ColumnWriterState &state) = 0;
7934
7931
 
7935
- //! Initialize the writer for a specific page. Only used for scalar types.
7936
- virtual unique_ptr<ColumnWriterPageState> InitializePageState();
7937
- //! Flushes the writer for a specific page. Only used for scalar types.
7938
- virtual void FlushPageState(Serializer &temp_writer, ColumnWriterPageState *state);
7932
+ protected:
7933
+ void HandleDefineLevels(ColumnWriterState &state, ColumnWriterState *parent, ValidityMask &validity, idx_t count,
7934
+ uint16_t define_value, uint16_t null_value);
7935
+ void HandleRepeatLevels(ColumnWriterState &state_p, ColumnWriterState *parent, idx_t count, idx_t max_repeat);
7939
7936
 
7940
7937
  void CompressPage(BufferedSerializer &temp_writer, size_t &compressed_size, data_ptr_t &compressed_data,
7941
7938
  unique_ptr<data_t[]> &compressed_buf);
7942
-
7943
- void SetParquetStatistics(StandardColumnWriterState &state, duckdb_parquet::format::ColumnChunk &column);
7944
7939
  };
7945
7940
 
7946
7941
  } // namespace duckdb
@@ -7953,6 +7948,7 @@ class FileOpener;
7953
7948
 
7954
7949
  class ParquetWriter {
7955
7950
  friend class ColumnWriter;
7951
+ friend class BasicColumnWriter;
7956
7952
  friend class ListColumnWriter;
7957
7953
  friend class StructColumnWriter;
7958
7954
 
@@ -7961,7 +7957,7 @@ public:
7961
7957
  vector<string> names, duckdb_parquet::format::CompressionCodec::type codec);
7962
7958
 
7963
7959
  public:
7964
- void Flush(ChunkCollection &buffer);
7960
+ void Flush(ColumnDataCollection &buffer);
7965
7961
  void Finalize();
7966
7962
 
7967
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,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
+ });