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.
- package/lib/duckdb.js +316 -20
- package/package.json +3 -1
- package/src/connection.cpp +25 -18
- package/src/data_chunk.cpp +2 -2
- package/src/database.cpp +66 -16
- package/src/duckdb.cpp +72270 -29614
- package/src/duckdb.hpp +9071 -5124
- package/src/duckdb_node.cpp +1 -0
- package/src/duckdb_node.hpp +37 -1
- package/src/parquet-amalgamation.cpp +37588 -36943
- package/src/parquet-amalgamation.hpp +41 -45
- package/src/statement.cpp +128 -20
- package/test/data_type_support.test.js +83 -13
- package/test/extension.test.js +1 -1
- package/test/jsdoc.test.js +60 -0
- package/test/query_result.test.js +23 -0
- package/test/syntax_error.test.js +16 -0
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7200
|
+
ptr = (char *)allocated_data.get();
|
|
7197
7201
|
}
|
|
7198
7202
|
}
|
|
7199
7203
|
|
|
7200
7204
|
private:
|
|
7201
|
-
|
|
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/
|
|
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
|
|
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
|
-
|
|
7917
|
-
|
|
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
|
|
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
|
-
|
|
7922
|
-
void
|
|
7923
|
-
|
|
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
|
|
7926
|
+
virtual void Prepare(ColumnWriterState &state, ColumnWriterState *parent, Vector &vector, idx_t count) = 0;
|
|
7926
7927
|
|
|
7927
|
-
|
|
7928
|
-
virtual
|
|
7929
|
-
|
|
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
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
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(
|
|
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 (
|
|
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 ||
|
|
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 (
|
|
272
|
-
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->
|
|
274
|
+
if (statement.statement->HasError()) {
|
|
275
|
+
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->GetError())});
|
|
273
276
|
return;
|
|
274
277
|
}
|
|
275
|
-
if (
|
|
276
|
-
cb.MakeCallback(statement.Value(), {Utils::CreateError(env,
|
|
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->
|
|
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(
|
|
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(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
});
|
package/test/extension.test.js
CHANGED
|
@@ -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
|
+
});
|