duckdb 0.4.1-dev98.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Makefile +2 -2
- package/binding.gyp +8 -5
- package/lib/duckdb.js +423 -80
- package/package.json +3 -1
- package/src/connection.cpp +137 -136
- package/src/data_chunk.cpp +185 -0
- package/src/database.cpp +73 -23
- package/src/duckdb.cpp +73716 -30454
- package/src/duckdb.hpp +10403 -6269
- package/src/duckdb_node.cpp +1 -0
- package/src/duckdb_node.hpp +57 -18
- package/src/parquet-amalgamation.cpp +31840 -31176
- package/src/parquet-amalgamation.hpp +112 -114
- package/src/statement.cpp +163 -34
- package/test/data_type_support.test.js +110 -13
- package/test/extension.test.js +5 -1
- package/test/jsdoc.test.js +60 -0
- package/test/query_result.test.js +23 -0
- package/test/syntax_error.test.js +16 -0
- package/test/udf.test.js +172 -107
package/src/statement.cpp
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
#include "duckdb.hpp"
|
|
1
2
|
#include "duckdb_node.hpp"
|
|
3
|
+
#include "napi.h"
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <cassert>
|
|
2
7
|
|
|
3
8
|
namespace node_duckdb {
|
|
4
9
|
|
|
@@ -10,7 +15,8 @@ Napi::Object Statement::Init(Napi::Env env, Napi::Object exports) {
|
|
|
10
15
|
Napi::Function t =
|
|
11
16
|
DefineClass(env, "Statement",
|
|
12
17
|
{InstanceMethod("run", &Statement::Run), InstanceMethod("all", &Statement::All),
|
|
13
|
-
InstanceMethod("each", &Statement::Each), InstanceMethod("finalize", &Statement::
|
|
18
|
+
InstanceMethod("each", &Statement::Each), InstanceMethod("finalize", &Statement::Finish),
|
|
19
|
+
InstanceMethod("stream", &Statement::Stream)});
|
|
14
20
|
|
|
15
21
|
constructor = Napi::Persistent(t);
|
|
16
22
|
constructor.SuppressDestruct();
|
|
@@ -20,7 +26,7 @@ Napi::Object Statement::Init(Napi::Env env, Napi::Object exports) {
|
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
struct PrepareTask : public Task {
|
|
23
|
-
PrepareTask(Statement &
|
|
29
|
+
PrepareTask(Statement &statement, Napi::Function callback) : Task(statement, callback) {
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
void DoWork() override {
|
|
@@ -34,8 +40,8 @@ struct PrepareTask : public Task {
|
|
|
34
40
|
Napi::HandleScope scope(env);
|
|
35
41
|
|
|
36
42
|
auto cb = callback.Value();
|
|
37
|
-
if (
|
|
38
|
-
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error)});
|
|
43
|
+
if (statement.statement->HasError()) {
|
|
44
|
+
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error.Message())});
|
|
39
45
|
return;
|
|
40
46
|
}
|
|
41
47
|
cb.MakeCallback(statement.Value(), {env.Null(), statement.Value()});
|
|
@@ -77,7 +83,7 @@ Statement::~Statement() {
|
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
// A Napi InstanceOf for Javascript Objects "Date" and "RegExp"
|
|
80
|
-
static bool
|
|
86
|
+
static bool OtherInstanceOf(Napi::Object source, const char *object_type) {
|
|
81
87
|
if (strcmp(object_type, "Date") == 0) {
|
|
82
88
|
return source.InstanceOf(source.Env().Global().Get(object_type).As<Napi::Function>());
|
|
83
89
|
} else if (strcmp(object_type, "RegExp") == 0) {
|
|
@@ -87,10 +93,10 @@ static bool other_instance_of(Napi::Object source, const char *object_type) {
|
|
|
87
93
|
return false;
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
static duckdb::Value
|
|
96
|
+
static duckdb::Value BindParameter(const Napi::Value source) {
|
|
91
97
|
if (source.IsString()) {
|
|
92
98
|
return duckdb::Value(source.As<Napi::String>().Utf8Value());
|
|
93
|
-
} else if (
|
|
99
|
+
} else if (OtherInstanceOf(source.As<Napi::Object>(), "RegExp")) {
|
|
94
100
|
return duckdb::Value(source.ToString().Utf8Value());
|
|
95
101
|
} else if (source.IsNumber()) {
|
|
96
102
|
if (Utils::OtherIsInt(source.As<Napi::Number>())) {
|
|
@@ -124,23 +130,45 @@ static duckdb::Value bind_parameter(const Napi::Value source) {
|
|
|
124
130
|
static Napi::Value convert_col_val(Napi::Env &env, duckdb::Value dval, duckdb::LogicalTypeId id) {
|
|
125
131
|
Napi::Value value;
|
|
126
132
|
|
|
133
|
+
if (dval.IsNull()) {
|
|
134
|
+
return env.Null();
|
|
135
|
+
}
|
|
136
|
+
|
|
127
137
|
// TODO templateroo here
|
|
128
138
|
switch (id) {
|
|
129
139
|
case duckdb::LogicalTypeId::BOOLEAN: {
|
|
130
140
|
value = Napi::Boolean::New(env, duckdb::BooleanValue::Get(dval));
|
|
131
141
|
} break;
|
|
142
|
+
case duckdb::LogicalTypeId::TINYINT: {
|
|
143
|
+
value = Napi::Number::New(env, duckdb::TinyIntValue::Get(dval));
|
|
144
|
+
} break;
|
|
145
|
+
case duckdb::LogicalTypeId::SMALLINT: {
|
|
146
|
+
value = Napi::Number::New(env, duckdb::SmallIntValue::Get(dval));
|
|
147
|
+
} break;
|
|
132
148
|
case duckdb::LogicalTypeId::INTEGER: {
|
|
133
149
|
value = Napi::Number::New(env, duckdb::IntegerValue::Get(dval));
|
|
134
150
|
} break;
|
|
151
|
+
case duckdb::LogicalTypeId::BIGINT: {
|
|
152
|
+
value = Napi::Number::New(env, duckdb::BigIntValue::Get(dval));
|
|
153
|
+
} break;
|
|
154
|
+
case duckdb::LogicalTypeId::UTINYINT: {
|
|
155
|
+
value = Napi::Number::New(env, duckdb::UTinyIntValue::Get(dval));
|
|
156
|
+
} break;
|
|
157
|
+
case duckdb::LogicalTypeId::USMALLINT: {
|
|
158
|
+
value = Napi::Number::New(env, duckdb::USmallIntValue::Get(dval));
|
|
159
|
+
} break;
|
|
160
|
+
case duckdb::LogicalTypeId::UINTEGER: {
|
|
161
|
+
value = Napi::Number::New(env, duckdb::UIntegerValue::Get(dval));
|
|
162
|
+
} break;
|
|
163
|
+
case duckdb::LogicalTypeId::UBIGINT: {
|
|
164
|
+
value = Napi::Number::New(env, duckdb::UBigIntValue::Get(dval));
|
|
165
|
+
} break;
|
|
135
166
|
case duckdb::LogicalTypeId::FLOAT: {
|
|
136
167
|
value = Napi::Number::New(env, duckdb::FloatValue::Get(dval));
|
|
137
168
|
} break;
|
|
138
169
|
case duckdb::LogicalTypeId::DOUBLE: {
|
|
139
170
|
value = Napi::Number::New(env, duckdb::DoubleValue::Get(dval));
|
|
140
171
|
} break;
|
|
141
|
-
case duckdb::LogicalTypeId::BIGINT: {
|
|
142
|
-
value = Napi::Number::New(env, duckdb::BigIntValue::Get(dval));
|
|
143
|
-
} break;
|
|
144
172
|
case duckdb::LogicalTypeId::HUGEINT: {
|
|
145
173
|
value = Napi::Number::New(env, dval.GetValue<double>());
|
|
146
174
|
} break;
|
|
@@ -208,6 +236,7 @@ static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names,
|
|
|
208
236
|
Napi::EscapableHandleScope scope(env);
|
|
209
237
|
std::vector<Napi::String> node_names;
|
|
210
238
|
assert(names.size() == chunk.ColumnCount());
|
|
239
|
+
node_names.reserve(names.size());
|
|
211
240
|
for (auto &name : names) {
|
|
212
241
|
node_names.push_back(Napi::String::New(env, name));
|
|
213
242
|
}
|
|
@@ -218,11 +247,6 @@ static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names,
|
|
|
218
247
|
|
|
219
248
|
for (duckdb::idx_t col_idx = 0; col_idx < chunk.ColumnCount(); col_idx++) {
|
|
220
249
|
duckdb::Value dval = chunk.GetValue(col_idx, row_idx);
|
|
221
|
-
if (dval.IsNull()) {
|
|
222
|
-
row_result.Set(node_names[col_idx], env.Null());
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
250
|
row_result.Set(node_names[col_idx], convert_col_val(env, dval, chunk.data[col_idx].GetType().id()));
|
|
227
251
|
}
|
|
228
252
|
result.Set(row_idx, row_result);
|
|
@@ -240,14 +264,14 @@ struct StatementParam {
|
|
|
240
264
|
};
|
|
241
265
|
|
|
242
266
|
struct RunPreparedTask : public Task {
|
|
243
|
-
RunPreparedTask(Statement &
|
|
244
|
-
: Task(
|
|
267
|
+
RunPreparedTask(Statement &statement, duckdb::unique_ptr<StatementParam> params, RunType run_type)
|
|
268
|
+
: Task(statement, params->callback), params(move(params)), run_type(run_type) {
|
|
245
269
|
}
|
|
246
270
|
|
|
247
271
|
void DoWork() override {
|
|
248
272
|
auto &statement = Get<Statement>();
|
|
249
273
|
// ignorant folk arrive here without caring about the prepare callback error
|
|
250
|
-
if (!statement.statement ||
|
|
274
|
+
if (!statement.statement || statement.statement->HasError()) {
|
|
251
275
|
return;
|
|
252
276
|
}
|
|
253
277
|
|
|
@@ -265,16 +289,12 @@ struct RunPreparedTask : public Task {
|
|
|
265
289
|
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, "statement was finalized")});
|
|
266
290
|
return;
|
|
267
291
|
}
|
|
268
|
-
if (
|
|
269
|
-
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
if (!statement.statement->success) {
|
|
273
|
-
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error)});
|
|
292
|
+
if (statement.statement->HasError()) {
|
|
293
|
+
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->GetError())});
|
|
274
294
|
return;
|
|
275
295
|
}
|
|
276
|
-
if (
|
|
277
|
-
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, result->
|
|
296
|
+
if (result->HasError()) {
|
|
297
|
+
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, result->GetError())});
|
|
278
298
|
return;
|
|
279
299
|
}
|
|
280
300
|
|
|
@@ -285,6 +305,8 @@ struct RunPreparedTask : public Task {
|
|
|
285
305
|
case RunType::EACH: {
|
|
286
306
|
duckdb::idx_t count = 0;
|
|
287
307
|
while (true) {
|
|
308
|
+
Napi::HandleScope scope(env);
|
|
309
|
+
|
|
288
310
|
auto chunk = result->Fetch();
|
|
289
311
|
if (!chunk || chunk->size() == 0) {
|
|
290
312
|
break;
|
|
@@ -307,7 +329,7 @@ struct RunPreparedTask : public Task {
|
|
|
307
329
|
}
|
|
308
330
|
case RunType::ALL: {
|
|
309
331
|
auto materialized_result = (duckdb::MaterializedQueryResult *)result.get();
|
|
310
|
-
Napi::Array result_arr(Napi::Array::New(env, materialized_result->
|
|
332
|
+
Napi::Array result_arr(Napi::Array::New(env, materialized_result->RowCount()));
|
|
311
333
|
|
|
312
334
|
duckdb::idx_t out_idx = 0;
|
|
313
335
|
while (true) {
|
|
@@ -335,6 +357,45 @@ struct RunPreparedTask : public Task {
|
|
|
335
357
|
RunType run_type;
|
|
336
358
|
};
|
|
337
359
|
|
|
360
|
+
struct RunQueryTask : public Task {
|
|
361
|
+
RunQueryTask(Statement &statement, duckdb::unique_ptr<StatementParam> params, Napi::Promise::Deferred deferred)
|
|
362
|
+
: Task(statement), deferred(deferred), params(move(params)) {
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
void DoWork() override {
|
|
366
|
+
auto &statement = Get<Statement>();
|
|
367
|
+
if (!statement.statement || statement.statement->HasError()) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
result = statement.statement->Execute(params->params, true);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
void DoCallback() override {
|
|
375
|
+
auto &statement = Get<Statement>();
|
|
376
|
+
Napi::Env env = statement.Env();
|
|
377
|
+
Napi::HandleScope scope(env);
|
|
378
|
+
|
|
379
|
+
if (!statement.statement) {
|
|
380
|
+
deferred.Reject(Utils::CreateError(env, "statement was finalized"));
|
|
381
|
+
} else if (statement.statement->HasError()) {
|
|
382
|
+
deferred.Reject(Utils::CreateError(env, statement.statement->GetError()));
|
|
383
|
+
} else if (result->HasError()) {
|
|
384
|
+
deferred.Reject(Utils::CreateError(env, result->GetError()));
|
|
385
|
+
} else {
|
|
386
|
+
auto db = statement.connection_ref->database_ref->Value();
|
|
387
|
+
auto query_result = QueryResult::constructor.New({db});
|
|
388
|
+
auto unwrapped = Napi::ObjectWrap<QueryResult>::Unwrap(query_result);
|
|
389
|
+
unwrapped->result = move(result);
|
|
390
|
+
deferred.Resolve(query_result);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
Napi::Promise::Deferred deferred;
|
|
395
|
+
std::unique_ptr<duckdb::QueryResult> result;
|
|
396
|
+
duckdb::unique_ptr<StatementParam> params;
|
|
397
|
+
};
|
|
398
|
+
|
|
338
399
|
duckdb::unique_ptr<StatementParam> Statement::HandleArgs(const Napi::CallbackInfo &info) {
|
|
339
400
|
size_t start_idx = ignore_first_param ? 1 : 0;
|
|
340
401
|
auto params = duckdb::make_unique<StatementParam>();
|
|
@@ -352,7 +413,7 @@ duckdb::unique_ptr<StatementParam> Statement::HandleArgs(const Napi::CallbackInf
|
|
|
352
413
|
if (p.IsUndefined()) {
|
|
353
414
|
continue;
|
|
354
415
|
}
|
|
355
|
-
params->params.push_back(
|
|
416
|
+
params->params.push_back(BindParameter(p));
|
|
356
417
|
}
|
|
357
418
|
return params;
|
|
358
419
|
}
|
|
@@ -364,21 +425,26 @@ Napi::Value Statement::All(const Napi::CallbackInfo &info) {
|
|
|
364
425
|
}
|
|
365
426
|
|
|
366
427
|
Napi::Value Statement::Run(const Napi::CallbackInfo &info) {
|
|
367
|
-
auto params = HandleArgs(info);
|
|
368
428
|
connection_ref->database_ref->Schedule(info.Env(),
|
|
369
429
|
duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::RUN));
|
|
370
430
|
return info.This();
|
|
371
431
|
}
|
|
372
432
|
|
|
373
433
|
Napi::Value Statement::Each(const Napi::CallbackInfo &info) {
|
|
374
|
-
auto params = HandleArgs(info);
|
|
375
434
|
connection_ref->database_ref->Schedule(
|
|
376
435
|
info.Env(), duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::EACH));
|
|
377
436
|
return info.This();
|
|
378
437
|
}
|
|
379
438
|
|
|
380
|
-
|
|
381
|
-
|
|
439
|
+
Napi::Value Statement::Stream(const Napi::CallbackInfo &info) {
|
|
440
|
+
auto deferred = Napi::Promise::Deferred::New(info.Env());
|
|
441
|
+
connection_ref->database_ref->Schedule(info.Env(),
|
|
442
|
+
duckdb::make_unique<RunQueryTask>(*this, HandleArgs(info), deferred));
|
|
443
|
+
return deferred.Promise();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
struct FinishTask : public Task {
|
|
447
|
+
FinishTask(Statement &statement, Napi::Function callback) : Task(statement, callback) {
|
|
382
448
|
}
|
|
383
449
|
|
|
384
450
|
void DoWork() override {
|
|
@@ -387,7 +453,7 @@ struct FinalizeTask : public Task {
|
|
|
387
453
|
}
|
|
388
454
|
};
|
|
389
455
|
|
|
390
|
-
Napi::Value Statement::
|
|
456
|
+
Napi::Value Statement::Finish(const Napi::CallbackInfo &info) {
|
|
391
457
|
Napi::Env env = info.Env();
|
|
392
458
|
Napi::HandleScope scope(env);
|
|
393
459
|
|
|
@@ -397,8 +463,71 @@ Napi::Value Statement::Finalize_(const Napi::CallbackInfo &info) {
|
|
|
397
463
|
callback = info[0].As<Napi::Function>();
|
|
398
464
|
}
|
|
399
465
|
|
|
400
|
-
connection_ref->database_ref->Schedule(env, duckdb::make_unique<
|
|
466
|
+
connection_ref->database_ref->Schedule(env, duckdb::make_unique<FinishTask>(*this, callback));
|
|
401
467
|
return env.Null();
|
|
402
468
|
}
|
|
403
469
|
|
|
470
|
+
Napi::FunctionReference QueryResult::constructor;
|
|
471
|
+
|
|
472
|
+
Napi::Object QueryResult::Init(Napi::Env env, Napi::Object exports) {
|
|
473
|
+
Napi::HandleScope scope(env);
|
|
474
|
+
|
|
475
|
+
Napi::Function t = DefineClass(env, "QueryResult", {InstanceMethod("nextChunk", &QueryResult::NextChunk)});
|
|
476
|
+
|
|
477
|
+
constructor = Napi::Persistent(t);
|
|
478
|
+
constructor.SuppressDestruct();
|
|
479
|
+
|
|
480
|
+
exports.Set("QueryResult", t);
|
|
481
|
+
return exports;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
QueryResult::QueryResult(const Napi::CallbackInfo &info) : Napi::ObjectWrap<QueryResult>(info) {
|
|
485
|
+
database_ref = Napi::ObjectWrap<Database>::Unwrap(info[0].As<Napi::Object>());
|
|
486
|
+
database_ref->Ref();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
QueryResult::~QueryResult() {
|
|
490
|
+
database_ref->Unref();
|
|
491
|
+
database_ref = nullptr;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
struct GetChunkTask : public Task {
|
|
495
|
+
GetChunkTask(QueryResult &query_result, Napi::Promise::Deferred deferred) : Task(query_result), deferred(deferred) {
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
void DoWork() override {
|
|
499
|
+
auto &query_result = Get<QueryResult>();
|
|
500
|
+
chunk = query_result.result->Fetch();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
void DoCallback() override {
|
|
504
|
+
auto &query_result = Get<QueryResult>();
|
|
505
|
+
Napi::Env env = query_result.Env();
|
|
506
|
+
Napi::HandleScope scope(env);
|
|
507
|
+
|
|
508
|
+
if (chunk == nullptr || chunk->size() == 0) {
|
|
509
|
+
deferred.Resolve(env.Null());
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
auto chunk_converted = convert_chunk(env, query_result.result->names, *chunk).ToObject();
|
|
514
|
+
if (!chunk_converted.IsArray()) {
|
|
515
|
+
deferred.Reject(Utils::CreateError(env, "internal error: chunk is not array"));
|
|
516
|
+
} else {
|
|
517
|
+
deferred.Resolve(chunk_converted);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
Napi::Promise::Deferred deferred;
|
|
522
|
+
std::unique_ptr<duckdb::DataChunk> chunk;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
Napi::Value QueryResult::NextChunk(const Napi::CallbackInfo &info) {
|
|
526
|
+
auto env = info.Env();
|
|
527
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
528
|
+
database_ref->Schedule(env, duckdb::make_unique<GetChunkTask>(*this, deferred));
|
|
529
|
+
|
|
530
|
+
return deferred.Promise();
|
|
531
|
+
}
|
|
532
|
+
|
|
404
533
|
} // namespace node_duckdb
|
|
@@ -10,7 +10,7 @@ describe("data type support", function () {
|
|
|
10
10
|
it("supports BOOLEAN values", function (done) {
|
|
11
11
|
db.run("CREATE TABLE boolean_table (i BOOLEAN)");
|
|
12
12
|
const stmt = db.prepare("INSERT INTO boolean_table VALUES (?)");
|
|
13
|
-
const values = [true, false];
|
|
13
|
+
const values = [true, false, null];
|
|
14
14
|
values.forEach((bool) => {
|
|
15
15
|
stmt.run(bool);
|
|
16
16
|
});
|
|
@@ -20,8 +20,36 @@ describe("data type support", function () {
|
|
|
20
20
|
done();
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
|
+
|
|
24
|
+
it("supports INTEGER values", function (done) {
|
|
25
|
+
db.run("CREATE TABLE integer_table (a TINYINT, b SMALLINT, c INTEGER, d BIGINT, e UTINYINT, f USMALLINT, g UINTEGER, h UBIGINT)");
|
|
26
|
+
const stmt = db.prepare("INSERT INTO integer_table VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
|
27
|
+
|
|
28
|
+
// Numerical limits
|
|
29
|
+
signedMinValue = (bitWidth) => Math.max(-(2**(bitWidth-1)-1)-1, Number.MIN_SAFE_INTEGER);
|
|
30
|
+
signedMaxValue = (bitWidth) => Math.min(2**(bitWidth-1)-1, Number.MAX_SAFE_INTEGER);
|
|
31
|
+
unsignedMaxValue = (bitWidth) => Math.min(2**(bitWidth)-1, Number.MAX_SAFE_INTEGER);
|
|
32
|
+
let minValues = [signedMinValue(8), signedMinValue(16), signedMinValue(32), signedMinValue(64), 0, 0, 0, 0];
|
|
33
|
+
let maxValues = [signedMinValue(8), signedMinValue(16), signedMinValue(32), signedMinValue(64), unsignedMaxValue(8), unsignedMaxValue(16), unsignedMaxValue(32), unsignedMaxValue(64)];
|
|
34
|
+
|
|
35
|
+
// Insert values
|
|
36
|
+
stmt.run(...minValues);
|
|
37
|
+
stmt.run(...maxValues);
|
|
38
|
+
|
|
39
|
+
db.prepare("SELECT * from integer_table;").all((err, res) => {
|
|
40
|
+
assert(err === null);
|
|
41
|
+
assert(res.length === 2);
|
|
42
|
+
assert(Object.entries(res[0]).length === 8);
|
|
43
|
+
assert(Object.entries(res[1]).length === 8);
|
|
44
|
+
assert.deepEqual(Object.entries(res[0]).map(v => v[1]), minValues);
|
|
45
|
+
assert.deepEqual(Object.entries(res[1]).map(v => v[1]), maxValues);
|
|
46
|
+
done();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
23
50
|
it("supports INTERVAL values", function (done) {
|
|
24
|
-
db.prepare(
|
|
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
|
@@ -67,13 +67,17 @@ describe('Extension loading', function() {
|
|
|
67
67
|
var db;
|
|
68
68
|
|
|
69
69
|
before(function(done) {
|
|
70
|
-
db = new duckdb.Database(':memory:', done);
|
|
70
|
+
db = new duckdb.Database(':memory:', {"allow_unsigned_extensions":"true"}, done);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
for (ext of extension_paths) {
|
|
74
74
|
const extension_path = ext;
|
|
75
75
|
const extension_name = ext.replace(/^.*[\\\/]/, '');
|
|
76
76
|
|
|
77
|
+
if (extension_name.startsWith('parquet')) { // Parquet is built-in in the Node client, so skip
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
77
81
|
it(extension_name, function(done) {
|
|
78
82
|
db.run(`LOAD '${extension_path}';`, function(err) {
|
|
79
83
|
if (err) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intended to be similar to stubtest for python
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
var duckdb = require("..");
|
|
6
|
+
var jsdoc = require("jsdoc3-parser");
|
|
7
|
+
const { expect } = require('chai');
|
|
8
|
+
const { promisify } = require('util');
|
|
9
|
+
|
|
10
|
+
function lastDot(string) {
|
|
11
|
+
if (string.endsWith(')')) {
|
|
12
|
+
string = string.substr(0, string.length - 1);
|
|
13
|
+
}
|
|
14
|
+
const array = string.split('.');
|
|
15
|
+
return array[array.length - 1];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} Node
|
|
20
|
+
* @property {string} name
|
|
21
|
+
* @property {string} memberof
|
|
22
|
+
* @property {string} longname
|
|
23
|
+
* @property {string} scope
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
describe("JSDoc contains all methods", () => {
|
|
27
|
+
/**
|
|
28
|
+
* @type {Node[]}
|
|
29
|
+
*/
|
|
30
|
+
let docs;
|
|
31
|
+
before(async () => {
|
|
32
|
+
docs = await promisify(jsdoc)(require.resolve("../lib/duckdb"));
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
function checkDocs(obj, scope) {
|
|
36
|
+
const symbols = Object.getOwnPropertySymbols(obj).map(i => lastDot(i.toString()));
|
|
37
|
+
const expected = Object
|
|
38
|
+
.getOwnPropertyNames(obj)
|
|
39
|
+
.concat(symbols)
|
|
40
|
+
.sort()
|
|
41
|
+
.filter(name => name !== 'constructor');
|
|
42
|
+
|
|
43
|
+
const actual = docs
|
|
44
|
+
.filter((node) => node.memberof === scope && !node.undocumented)
|
|
45
|
+
.map((node) => lastDot(node.name))
|
|
46
|
+
.sort();
|
|
47
|
+
|
|
48
|
+
expect(expected).to.deep.equals(actual, 'items missing from documentation');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const clazz of ['Database', 'QueryResult', 'Connection', 'Statement']) {
|
|
52
|
+
it(clazz, () => {
|
|
53
|
+
checkDocs(duckdb[clazz].prototype, `module:duckdb~${clazz}`);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
it('module root', () => {
|
|
58
|
+
checkDocs(duckdb, 'module:duckdb');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
var duckdb = require('..');
|
|
2
|
+
var assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('QueryResult', () => {
|
|
5
|
+
const total = 1000;
|
|
6
|
+
|
|
7
|
+
let db;
|
|
8
|
+
let conn;
|
|
9
|
+
before((done) => {
|
|
10
|
+
db = new duckdb.Database(':memory:', () => {
|
|
11
|
+
conn = new duckdb.Connection(db, done);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('streams results', async () => {
|
|
16
|
+
let retrieved = 0;
|
|
17
|
+
const stream = conn.stream('SELECT * FROM range(0, ?)', total);
|
|
18
|
+
for await (const row of stream) {
|
|
19
|
+
retrieved++;
|
|
20
|
+
}
|
|
21
|
+
assert.equal(total, retrieved)
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
var duckdb = require('..');
|
|
2
|
+
var assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('exec', function() {
|
|
5
|
+
var db;
|
|
6
|
+
before(function(done) {
|
|
7
|
+
db = new duckdb.Database(':memory:', done);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("doesn't crash on a syntax error", function(done) {
|
|
11
|
+
db.exec("syntax error", function(err) {
|
|
12
|
+
assert.notEqual(err, null, "Expected an error")
|
|
13
|
+
done();
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
});
|