duckdb 0.4.1-dev13.0 → 0.4.1-dev1328.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 +404 -80
- package/package.json +3 -1
- package/src/connection.cpp +109 -130
- package/src/data_chunk.cpp +185 -0
- package/src/database.cpp +67 -19
- package/src/duckdb.cpp +60601 -27620
- package/src/duckdb.hpp +13087 -10807
- package/src/duckdb_node.cpp +1 -0
- package/src/duckdb_node.hpp +57 -18
- package/src/parquet-amalgamation.cpp +37675 -37067
- package/src/parquet-amalgamation.hpp +99 -104
- package/src/statement.cpp +134 -23
- 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
- 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 {
|
|
@@ -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,6 +130,10 @@ 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: {
|
|
@@ -208,6 +218,7 @@ static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names,
|
|
|
208
218
|
Napi::EscapableHandleScope scope(env);
|
|
209
219
|
std::vector<Napi::String> node_names;
|
|
210
220
|
assert(names.size() == chunk.ColumnCount());
|
|
221
|
+
node_names.reserve(names.size());
|
|
211
222
|
for (auto &name : names) {
|
|
212
223
|
node_names.push_back(Napi::String::New(env, name));
|
|
213
224
|
}
|
|
@@ -218,11 +229,6 @@ static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names,
|
|
|
218
229
|
|
|
219
230
|
for (duckdb::idx_t col_idx = 0; col_idx < chunk.ColumnCount(); col_idx++) {
|
|
220
231
|
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
232
|
row_result.Set(node_names[col_idx], convert_col_val(env, dval, chunk.data[col_idx].GetType().id()));
|
|
227
233
|
}
|
|
228
234
|
result.Set(row_idx, row_result);
|
|
@@ -240,8 +246,8 @@ struct StatementParam {
|
|
|
240
246
|
};
|
|
241
247
|
|
|
242
248
|
struct RunPreparedTask : public Task {
|
|
243
|
-
RunPreparedTask(Statement &
|
|
244
|
-
: Task(
|
|
249
|
+
RunPreparedTask(Statement &statement, duckdb::unique_ptr<StatementParam> params, RunType run_type)
|
|
250
|
+
: Task(statement, params->callback), params(move(params)), run_type(run_type) {
|
|
245
251
|
}
|
|
246
252
|
|
|
247
253
|
void DoWork() override {
|
|
@@ -269,10 +275,6 @@ struct RunPreparedTask : public Task {
|
|
|
269
275
|
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error)});
|
|
270
276
|
return;
|
|
271
277
|
}
|
|
272
|
-
if (!statement.statement->success) {
|
|
273
|
-
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, statement.statement->error)});
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
278
|
if (!result->success) {
|
|
277
279
|
cb.MakeCallback(statement.Value(), {Utils::CreateError(env, result->error)});
|
|
278
280
|
return;
|
|
@@ -285,6 +287,8 @@ struct RunPreparedTask : public Task {
|
|
|
285
287
|
case RunType::EACH: {
|
|
286
288
|
duckdb::idx_t count = 0;
|
|
287
289
|
while (true) {
|
|
290
|
+
Napi::HandleScope scope(env);
|
|
291
|
+
|
|
288
292
|
auto chunk = result->Fetch();
|
|
289
293
|
if (!chunk || chunk->size() == 0) {
|
|
290
294
|
break;
|
|
@@ -335,6 +339,45 @@ struct RunPreparedTask : public Task {
|
|
|
335
339
|
RunType run_type;
|
|
336
340
|
};
|
|
337
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->success) {
|
|
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->success) {
|
|
364
|
+
deferred.Reject(Utils::CreateError(env, statement.statement->error));
|
|
365
|
+
} else if (!result->success) {
|
|
366
|
+
deferred.Reject(Utils::CreateError(env, result->error));
|
|
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
|
+
|
|
338
381
|
duckdb::unique_ptr<StatementParam> Statement::HandleArgs(const Napi::CallbackInfo &info) {
|
|
339
382
|
size_t start_idx = ignore_first_param ? 1 : 0;
|
|
340
383
|
auto params = duckdb::make_unique<StatementParam>();
|
|
@@ -352,7 +395,7 @@ duckdb::unique_ptr<StatementParam> Statement::HandleArgs(const Napi::CallbackInf
|
|
|
352
395
|
if (p.IsUndefined()) {
|
|
353
396
|
continue;
|
|
354
397
|
}
|
|
355
|
-
params->params.push_back(
|
|
398
|
+
params->params.push_back(BindParameter(p));
|
|
356
399
|
}
|
|
357
400
|
return params;
|
|
358
401
|
}
|
|
@@ -364,21 +407,26 @@ Napi::Value Statement::All(const Napi::CallbackInfo &info) {
|
|
|
364
407
|
}
|
|
365
408
|
|
|
366
409
|
Napi::Value Statement::Run(const Napi::CallbackInfo &info) {
|
|
367
|
-
auto params = HandleArgs(info);
|
|
368
410
|
connection_ref->database_ref->Schedule(info.Env(),
|
|
369
411
|
duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::RUN));
|
|
370
412
|
return info.This();
|
|
371
413
|
}
|
|
372
414
|
|
|
373
415
|
Napi::Value Statement::Each(const Napi::CallbackInfo &info) {
|
|
374
|
-
auto params = HandleArgs(info);
|
|
375
416
|
connection_ref->database_ref->Schedule(
|
|
376
417
|
info.Env(), duckdb::make_unique<RunPreparedTask>(*this, HandleArgs(info), RunType::EACH));
|
|
377
418
|
return info.This();
|
|
378
419
|
}
|
|
379
420
|
|
|
380
|
-
|
|
381
|
-
|
|
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
|
+
|
|
428
|
+
struct FinishTask : public Task {
|
|
429
|
+
FinishTask(Statement &statement, Napi::Function callback) : Task(statement, callback) {
|
|
382
430
|
}
|
|
383
431
|
|
|
384
432
|
void DoWork() override {
|
|
@@ -387,7 +435,7 @@ struct FinalizeTask : public Task {
|
|
|
387
435
|
}
|
|
388
436
|
};
|
|
389
437
|
|
|
390
|
-
Napi::Value Statement::
|
|
438
|
+
Napi::Value Statement::Finish(const Napi::CallbackInfo &info) {
|
|
391
439
|
Napi::Env env = info.Env();
|
|
392
440
|
Napi::HandleScope scope(env);
|
|
393
441
|
|
|
@@ -397,8 +445,71 @@ Napi::Value Statement::Finalize_(const Napi::CallbackInfo &info) {
|
|
|
397
445
|
callback = info[0].As<Napi::Function>();
|
|
398
446
|
}
|
|
399
447
|
|
|
400
|
-
connection_ref->database_ref->Schedule(env, duckdb::make_unique<
|
|
448
|
+
connection_ref->database_ref->Schedule(env, duckdb::make_unique<FinishTask>(*this, callback));
|
|
401
449
|
return env.Null();
|
|
402
450
|
}
|
|
403
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
|
+
|
|
404
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
|
+
});
|