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.
- package/lib/duckdb.js +335 -20
- package/package.json +3 -1
- package/src/connection.cpp +32 -10
- package/src/database.cpp +7 -7
- package/src/duckdb.cpp +33508 -21255
- package/src/duckdb.hpp +4680 -2199
- package/src/duckdb_node.cpp +1 -0
- package/src/duckdb_node.hpp +34 -1
- package/src/parquet-amalgamation.cpp +37539 -37360
- package/src/parquet-amalgamation.hpp +17 -10
- package/src/statement.cpp +147 -23
- package/test/data_type_support.test.js +110 -13
- package/test/extension.test.js +4 -0
- package/test/jsdoc.test.js +60 -0
- package/test/query_result.test.js +23 -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
|
|
|
@@ -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/
|
|
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(
|
|
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 (
|
|
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 ||
|
|
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 (
|
|
272
|
-
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->
|
|
292
|
+
if (statement.statement->HasError()) {
|
|
293
|
+
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->GetError())});
|
|
273
294
|
return;
|
|
274
295
|
}
|
|
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)});
|
|
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->
|
|
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(
|
|
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(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
});
|
package/test/extension.test.js
CHANGED
|
@@ -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
|
+
})
|