duckdb 0.4.1-dev182.0 → 0.4.1-dev1896.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 +70002 -31503
- package/src/duckdb.hpp +7965 -4400
- package/src/duckdb_node.cpp +1 -0
- package/src/duckdb_node.hpp +37 -1
- package/src/parquet-amalgamation.cpp +36019 -35374
- package/src/parquet-amalgamation.hpp +112 -114
- 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
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
|
+
});
|