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/connection.cpp
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
#include "duckdb.hpp"
|
|
1
2
|
#include "duckdb_node.hpp"
|
|
3
|
+
#include "napi.h"
|
|
2
4
|
|
|
5
|
+
#include <iostream>
|
|
3
6
|
#include <thread>
|
|
4
7
|
|
|
5
8
|
namespace node_duckdb {
|
|
@@ -23,7 +26,7 @@ Napi::Object Connection::Init(Napi::Env env, Napi::Object exports) {
|
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
struct ConnectTask : public Task {
|
|
26
|
-
ConnectTask(Connection &
|
|
29
|
+
ConnectTask(Connection &connection, Napi::Function callback) : Task(connection, callback) {
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
void DoWork() override {
|
|
@@ -78,91 +81,25 @@ struct JSArgs {
|
|
|
78
81
|
std::string error;
|
|
79
82
|
};
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
Napi::EscapableHandleScope scope(env);
|
|
83
|
-
|
|
84
|
-
Napi::Array data_buffers(Napi::Array::New(env));
|
|
85
|
-
|
|
86
|
-
auto data_buffer_index = -1;
|
|
87
|
-
auto length_buffer_index = -1;
|
|
88
|
-
auto validity_buffer_index = -1;
|
|
89
|
-
|
|
90
|
-
auto validity_buffer = Napi::Buffer<uint8_t>::New(env, rows);
|
|
91
|
-
auto validity = duckdb::FlatVector::Validity(vec);
|
|
92
|
-
auto validity_ptr = validity_buffer.Data();
|
|
93
|
-
for (duckdb::idx_t row_idx = 0; row_idx < rows; row_idx++) {
|
|
94
|
-
validity_ptr[row_idx] = validity.RowIsValid(row_idx);
|
|
95
|
-
}
|
|
96
|
-
data_buffers.Set(data_buffers.Length(), validity_buffer);
|
|
97
|
-
validity_buffer_index = data_buffers.Length() - 1;
|
|
98
|
-
|
|
99
|
-
auto &vec_type = vec.GetType();
|
|
100
|
-
|
|
101
|
-
switch (vec_type.id()) {
|
|
102
|
-
case duckdb::LogicalTypeId::BOOLEAN:
|
|
103
|
-
case duckdb::LogicalTypeId::UTINYINT:
|
|
104
|
-
case duckdb::LogicalTypeId::TINYINT:
|
|
105
|
-
case duckdb::LogicalTypeId::USMALLINT:
|
|
106
|
-
case duckdb::LogicalTypeId::SMALLINT:
|
|
107
|
-
case duckdb::LogicalTypeId::INTEGER:
|
|
108
|
-
case duckdb::LogicalTypeId::UINTEGER:
|
|
109
|
-
case duckdb::LogicalTypeId::FLOAT:
|
|
110
|
-
case duckdb::LogicalTypeId::DOUBLE: {
|
|
111
|
-
auto data_buffer = Napi::Buffer<uint8_t>::New(env, rows * duckdb::GetTypeIdSize(vec_type.InternalType()));
|
|
112
|
-
// TODO this is technically not neccessary but it fixes a crash in Node 14. Some weird data ownership issue.
|
|
113
|
-
memcpy(data_buffer.Data(), duckdb::FlatVector::GetData<uint8_t>(vec),
|
|
114
|
-
rows * duckdb::GetTypeIdSize(vec_type.InternalType()));
|
|
115
|
-
|
|
116
|
-
data_buffers.Set(data_buffers.Length(), data_buffer);
|
|
117
|
-
data_buffer_index = data_buffers.Length() - 1;
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
case duckdb::LogicalTypeId::VARCHAR: {
|
|
121
|
-
Napi::Array string_buffers(Napi::Array::New(env, rows));
|
|
122
|
-
if (copy) {
|
|
123
|
-
auto string_vec_ptr = duckdb::FlatVector::GetData<duckdb::string_t>(vec);
|
|
124
|
-
for (duckdb::idx_t row_idx = 0; row_idx < rows; row_idx++) {
|
|
125
|
-
string_buffers.Set(row_idx, validity_ptr[row_idx]
|
|
126
|
-
? Napi::String::New(env, string_vec_ptr[row_idx].GetDataUnsafe(),
|
|
127
|
-
string_vec_ptr[row_idx].GetSize())
|
|
128
|
-
: Napi::Value());
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
data_buffers.Set(data_buffers.Length(), string_buffers);
|
|
132
|
-
data_buffer_index = data_buffers.Length() - 1;
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
default:
|
|
136
|
-
throw duckdb::NotImplementedException(vec_type.ToString());
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
Napi::Object desc(Napi::Object::New(env));
|
|
140
|
-
desc.Set("logical_type", vec_type.ToString());
|
|
141
|
-
desc.Set("physical_type", TypeIdToString(vec_type.InternalType()));
|
|
142
|
-
desc.Set("validity_buffer", validity_buffer_index);
|
|
143
|
-
desc.Set("data_buffer", data_buffer_index);
|
|
144
|
-
desc.Set("length_buffer", length_buffer_index);
|
|
145
|
-
desc.Set("data_buffers", data_buffers);
|
|
146
|
-
|
|
147
|
-
return scope.Escape(desc);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
void DuckDBNodeUDFLauncher(Napi::Env env, Napi::Function jsudf, nullptr_t *, JSArgs *jsargs) {
|
|
84
|
+
void DuckDBNodeUDFLauncher(Napi::Env env, Napi::Function jsudf, std::nullptr_t *, JSArgs *jsargs) {
|
|
151
85
|
try { // if we dont catch exceptions here we terminate node if one happens ^^
|
|
86
|
+
Napi::EscapableHandleScope scope(env);
|
|
152
87
|
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
auto arg_descr = transform_vector(env, vec, jsargs->rows, true);
|
|
158
|
-
args_descr.Set(col_idx, arg_descr);
|
|
159
|
-
}
|
|
160
|
-
auto ret_descr = transform_vector(env, *jsargs->result, jsargs->rows, false);
|
|
161
|
-
|
|
162
|
-
Napi::Object descr(Napi::Object::New(env));
|
|
88
|
+
// Set up descriptor and data arrays
|
|
89
|
+
auto descr = Napi::Object::New(env);
|
|
90
|
+
auto chunk = EncodeDataChunk(env, *jsargs->args, true, true);
|
|
91
|
+
descr.Set("args", scope.Escape(chunk));
|
|
163
92
|
descr.Set("rows", jsargs->rows);
|
|
164
|
-
|
|
165
|
-
|
|
93
|
+
auto ret = Napi::Object::New(env);
|
|
94
|
+
ret.Set("sqlType", jsargs->result->GetType().ToString());
|
|
95
|
+
auto ret_type = jsargs->result->GetType().InternalType();
|
|
96
|
+
#if NAPI_VERSION <= 5
|
|
97
|
+
if (ret_type == duckdb::PhysicalType::INT64 || ret_type == duckdb::PhysicalType::UINT64) {
|
|
98
|
+
ret_type = duckdb::PhysicalType::DOUBLE;
|
|
99
|
+
}
|
|
100
|
+
#endif
|
|
101
|
+
ret.Set("physicalType", TypeIdToString(ret_type));
|
|
102
|
+
descr.Set("ret", ret);
|
|
166
103
|
|
|
167
104
|
// actually call the UDF, or rather its vectorized wrapper from duckdb.js/Connection.prototype.register wrapper
|
|
168
105
|
jsudf({descr});
|
|
@@ -181,39 +118,75 @@ void DuckDBNodeUDFLauncher(Napi::Env env, Napi::Function jsudf, nullptr_t *, JSA
|
|
|
181
118
|
}
|
|
182
119
|
|
|
183
120
|
// transform the result back to a vector
|
|
184
|
-
auto return_validity =
|
|
185
|
-
.Get("data_buffers")
|
|
186
|
-
.ToObject()
|
|
187
|
-
.Get(ret_descr.ToObject().Get("validity_buffer"))
|
|
188
|
-
.As<Napi::Buffer<uint8_t>>();
|
|
121
|
+
auto return_validity = ret.ToObject().Get("validity").As<Napi::Uint8Array>();
|
|
189
122
|
for (duckdb::idx_t row_idx = 0; row_idx < jsargs->rows; row_idx++) {
|
|
190
123
|
duckdb::FlatVector::SetNull(*jsargs->result, row_idx, !return_validity[row_idx]);
|
|
191
124
|
}
|
|
192
125
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
126
|
+
switch (jsargs->result->GetType().id()) {
|
|
127
|
+
case duckdb::LogicalTypeId::TINYINT: {
|
|
128
|
+
auto data = ret.Get("data").As<Napi::Int8Array>();
|
|
129
|
+
auto out = duckdb::FlatVector::GetData<int8_t>(*jsargs->result);
|
|
130
|
+
memcpy(out, data.Data(), jsargs->rows * duckdb::GetTypeIdSize(ret_type));
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case duckdb::LogicalTypeId::SMALLINT: {
|
|
134
|
+
auto data = ret.Get("data").As<Napi::Int16Array>();
|
|
135
|
+
auto out = duckdb::FlatVector::GetData<int16_t>(*jsargs->result);
|
|
136
|
+
memcpy(out, data.Data(), jsargs->rows * duckdb::GetTypeIdSize(ret_type));
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case duckdb::LogicalTypeId::INTEGER: {
|
|
140
|
+
auto data = ret.Get("data").As<Napi::Int32Array>();
|
|
141
|
+
auto out = duckdb::FlatVector::GetData<int32_t>(*jsargs->result);
|
|
142
|
+
memcpy(out, data.Data(), jsargs->rows * duckdb::GetTypeIdSize(ret_type));
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case duckdb::LogicalTypeId::DOUBLE: {
|
|
146
|
+
auto data = ret.Get("data").As<Napi::Float64Array>();
|
|
147
|
+
auto out = duckdb::FlatVector::GetData<double>(*jsargs->result);
|
|
148
|
+
memcpy(out, data.Data(), jsargs->rows * duckdb::GetTypeIdSize(ret_type));
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case duckdb::LogicalTypeId::TIME:
|
|
152
|
+
case duckdb::LogicalTypeId::TIMESTAMP:
|
|
153
|
+
case duckdb::LogicalTypeId::TIMESTAMP_MS:
|
|
154
|
+
case duckdb::LogicalTypeId::TIMESTAMP_SEC:
|
|
155
|
+
case duckdb::LogicalTypeId::TIMESTAMP_NS:
|
|
156
|
+
case duckdb::LogicalTypeId::BIGINT: {
|
|
157
|
+
#if NAPI_VERSION > 5
|
|
158
|
+
auto data = ret.Get("data").As<Napi::BigInt64Array>();
|
|
159
|
+
#else
|
|
160
|
+
auto data = ret.Get("data").As<Napi::Float64Array>();
|
|
161
|
+
#endif
|
|
162
|
+
auto out = duckdb::FlatVector::GetData<int64_t>(*jsargs->result);
|
|
163
|
+
memcpy(out, data.Data(), jsargs->rows * duckdb::GetTypeIdSize(ret_type));
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case duckdb::LogicalTypeId::UBIGINT: {
|
|
167
|
+
#if NAPI_VERSION > 5
|
|
168
|
+
auto data = ret.Get("data").As<Napi::BigUint64Array>();
|
|
169
|
+
#else
|
|
170
|
+
auto data = ret.Get("data").As<Napi::Float64Array>();
|
|
171
|
+
#endif
|
|
172
|
+
auto out = duckdb::FlatVector::GetData<uint64_t>(*jsargs->result);
|
|
173
|
+
memcpy(out, data.Data(), jsargs->rows * duckdb::GetTypeIdSize(ret_type));
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case duckdb::LogicalTypeId::BLOB:
|
|
177
|
+
case duckdb::LogicalTypeId::VARCHAR: {
|
|
178
|
+
auto data = ret.Get("data").As<Napi::Array>();
|
|
179
|
+
auto out = duckdb::FlatVector::GetData<duckdb::string_t>(*jsargs->result);
|
|
180
|
+
for (size_t i = 0; i < data.Length(); ++i) {
|
|
181
|
+
out[i] = duckdb::string_t(data.Get(i).ToString());
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
default: {
|
|
201
186
|
for (duckdb::idx_t row_idx = 0; row_idx < jsargs->rows; row_idx++) {
|
|
202
|
-
|
|
203
|
-
duckdb::FlatVector::SetNull(*jsargs->result, row_idx, true);
|
|
204
|
-
} else {
|
|
205
|
-
auto str = return_string_array.Get(row_idx).As<Napi::String>();
|
|
206
|
-
return_string_vec_ptr[row_idx] = duckdb::StringVector::AddString(*jsargs->result, str);
|
|
207
|
-
}
|
|
187
|
+
duckdb::FlatVector::SetNull(*jsargs->result, row_idx, false);
|
|
208
188
|
}
|
|
209
|
-
}
|
|
210
|
-
auto return_data = ret_descr.ToObject()
|
|
211
|
-
.Get("data_buffers")
|
|
212
|
-
.ToObject()
|
|
213
|
-
.Get(ret_descr.ToObject().Get("data_buffer"))
|
|
214
|
-
.As<Napi::Buffer<uint8_t>>();
|
|
215
|
-
memcpy(duckdb::FlatVector::GetData<uint8_t>(*jsargs->result), return_data.Data(),
|
|
216
|
-
jsargs->rows * duckdb::GetTypeIdSize(jsargs->result->GetType().InternalType()));
|
|
189
|
+
}
|
|
217
190
|
}
|
|
218
191
|
} catch (const std::exception &e) {
|
|
219
192
|
jsargs->error = e.what();
|
|
@@ -222,8 +195,8 @@ void DuckDBNodeUDFLauncher(Napi::Env env, Napi::Function jsudf, nullptr_t *, JSA
|
|
|
222
195
|
}
|
|
223
196
|
|
|
224
197
|
struct RegisterTask : public Task {
|
|
225
|
-
RegisterTask(Connection &
|
|
226
|
-
: Task(
|
|
198
|
+
RegisterTask(Connection &connection, std::string name, std::string return_type_name, Napi::Function callback)
|
|
199
|
+
: Task(connection, callback), name(std::move(name)), return_type_name(std::move(return_type_name)) {
|
|
227
200
|
}
|
|
228
201
|
|
|
229
202
|
void DoWork() override {
|
|
@@ -234,7 +207,7 @@ struct RegisterTask : public Task {
|
|
|
234
207
|
// here we can do only DuckDB stuff because we do not have a functioning env
|
|
235
208
|
|
|
236
209
|
// Flatten all args to simplify udfs
|
|
237
|
-
args.
|
|
210
|
+
args.Flatten();
|
|
238
211
|
|
|
239
212
|
JSArgs jsargs;
|
|
240
213
|
jsargs.rows = args.size();
|
|
@@ -282,8 +255,8 @@ Napi::Value Connection::Register(const Napi::CallbackInfo &info) {
|
|
|
282
255
|
return env.Null();
|
|
283
256
|
}
|
|
284
257
|
|
|
285
|
-
auto udf =
|
|
286
|
-
|
|
258
|
+
auto udf = duckdb_node_udf_function_t::New(env, udf_callback, "duckdb_node_udf" + name, 0, 1, nullptr,
|
|
259
|
+
[](Napi::Env, void *, std::nullptr_t *ctx) {});
|
|
287
260
|
|
|
288
261
|
// we have to unref the udf because otherwise there is a circular ref with the connection somehow(?)
|
|
289
262
|
// this took far too long to figure out
|
|
@@ -297,8 +270,8 @@ Napi::Value Connection::Register(const Napi::CallbackInfo &info) {
|
|
|
297
270
|
}
|
|
298
271
|
|
|
299
272
|
struct UnregisterTask : public Task {
|
|
300
|
-
UnregisterTask(Connection &
|
|
301
|
-
: Task(
|
|
273
|
+
UnregisterTask(Connection &connection, std::string name, Napi::Function callback)
|
|
274
|
+
: Task(connection, callback), name(std::move(name)) {
|
|
302
275
|
}
|
|
303
276
|
|
|
304
277
|
void DoWork() override {
|
|
@@ -340,27 +313,33 @@ Napi::Value Connection::Unregister(const Napi::CallbackInfo &info) {
|
|
|
340
313
|
}
|
|
341
314
|
|
|
342
315
|
struct ExecTask : public Task {
|
|
343
|
-
ExecTask(Connection &
|
|
344
|
-
: Task(
|
|
316
|
+
ExecTask(Connection &connection, std::string sql, Napi::Function callback)
|
|
317
|
+
: Task(connection, callback), sql(std::move(sql)) {
|
|
345
318
|
}
|
|
346
319
|
|
|
347
320
|
void DoWork() override {
|
|
348
321
|
auto &connection = Get<Connection>();
|
|
349
322
|
|
|
350
323
|
success = true;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
324
|
+
try {
|
|
325
|
+
auto statements = connection.connection->ExtractStatements(sql);
|
|
326
|
+
if (statements.empty()) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
355
329
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
330
|
+
// thanks Mark
|
|
331
|
+
for (duckdb::idx_t i = 0; i < statements.size(); i++) {
|
|
332
|
+
auto res = connection.connection->Query(move(statements[i]));
|
|
333
|
+
if (!res->success) {
|
|
334
|
+
success = false;
|
|
335
|
+
error = res->error;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
363
338
|
}
|
|
339
|
+
} catch (duckdb::ParserException &e) {
|
|
340
|
+
success = false;
|
|
341
|
+
error = e.what();
|
|
342
|
+
return;
|
|
364
343
|
}
|
|
365
344
|
}
|
|
366
345
|
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#include "duckdb.hpp"
|
|
2
|
+
#include "duckdb_node.hpp"
|
|
3
|
+
#include "napi.h"
|
|
4
|
+
|
|
5
|
+
#include <thread>
|
|
6
|
+
|
|
7
|
+
namespace node_duckdb {
|
|
8
|
+
|
|
9
|
+
Napi::Array EncodeDataChunk(Napi::Env env, duckdb::DataChunk &chunk, bool with_types, bool with_data) {
|
|
10
|
+
Napi::Array col_descs(Napi::Array::New(env, chunk.ColumnCount()));
|
|
11
|
+
for (idx_t col_idx = 0; col_idx < chunk.ColumnCount(); col_idx++) {
|
|
12
|
+
auto col_desc = Napi::Object::New(env);
|
|
13
|
+
|
|
14
|
+
// Make sure we only have flat vectors hereafter (for now)
|
|
15
|
+
auto &chunk_vec = chunk.data[col_idx];
|
|
16
|
+
if (with_data) {
|
|
17
|
+
chunk_vec.Flatten(chunk.size());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Do a post-order DFS traversal
|
|
21
|
+
std::vector<std::tuple<bool, duckdb::Vector *, Napi::Object, size_t, size_t>> pending;
|
|
22
|
+
pending.emplace_back(false, &chunk_vec, Napi::Object::New(env), 0, 0);
|
|
23
|
+
|
|
24
|
+
while (!pending.empty()) {
|
|
25
|
+
// Unpack DFS node
|
|
26
|
+
auto &back = pending.back();
|
|
27
|
+
auto &visited = std::get<0>(back);
|
|
28
|
+
auto &vec = std::get<1>(back);
|
|
29
|
+
auto &desc = std::get<2>(back);
|
|
30
|
+
auto &parent_idx = std::get<3>(back);
|
|
31
|
+
auto &idx_in_parent = std::get<4>(back);
|
|
32
|
+
|
|
33
|
+
// Already visited?
|
|
34
|
+
if (visited) {
|
|
35
|
+
if (pending.size() == 1) {
|
|
36
|
+
col_desc = desc;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
std::get<2>(pending[parent_idx]).Get("children").As<Napi::Array>().Set(idx_in_parent, desc);
|
|
40
|
+
pending.pop_back();
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
visited = true;
|
|
44
|
+
auto current_idx = pending.size() - 1;
|
|
45
|
+
|
|
46
|
+
// Store types
|
|
47
|
+
auto &vec_type = vec->GetType();
|
|
48
|
+
if (with_types) {
|
|
49
|
+
desc.Set("sqlType", vec_type.ToString());
|
|
50
|
+
desc.Set("physicalType", TypeIdToString(vec_type.InternalType()));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Create validity vector
|
|
54
|
+
if (with_data) {
|
|
55
|
+
vec->Flatten(chunk.size());
|
|
56
|
+
auto &validity = duckdb::FlatVector::Validity(*vec);
|
|
57
|
+
auto validity_buffer = Napi::Uint8Array::New(env, chunk.size());
|
|
58
|
+
for (idx_t row_idx = 0; row_idx < chunk.size(); row_idx++) {
|
|
59
|
+
validity_buffer[row_idx] = validity.RowIsValid(row_idx);
|
|
60
|
+
}
|
|
61
|
+
desc.Set("validity", validity_buffer);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Create data buffer
|
|
65
|
+
switch (vec_type.id()) {
|
|
66
|
+
case duckdb::LogicalTypeId::TINYINT: {
|
|
67
|
+
if (with_data) {
|
|
68
|
+
auto array = Napi::Int8Array::New(env, chunk.size());
|
|
69
|
+
auto data = duckdb::FlatVector::GetData<int8_t>(*vec);
|
|
70
|
+
for (size_t i = 0; i < chunk.size(); ++i) {
|
|
71
|
+
array[i] = data[i];
|
|
72
|
+
}
|
|
73
|
+
desc.Set("data", array);
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case duckdb::LogicalTypeId::SMALLINT: {
|
|
78
|
+
if (with_data) {
|
|
79
|
+
auto array = Napi::Int16Array::New(env, chunk.size());
|
|
80
|
+
auto data = duckdb::FlatVector::GetData<int16_t>(*vec);
|
|
81
|
+
for (size_t i = 0; i < chunk.size(); ++i) {
|
|
82
|
+
array[i] = data[i];
|
|
83
|
+
}
|
|
84
|
+
desc.Set("data", array);
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case duckdb::LogicalTypeId::INTEGER: {
|
|
89
|
+
if (with_data) {
|
|
90
|
+
auto array = Napi::Int32Array::New(env, chunk.size());
|
|
91
|
+
auto data = duckdb::FlatVector::GetData<int32_t>(*vec);
|
|
92
|
+
for (size_t i = 0; i < chunk.size(); ++i) {
|
|
93
|
+
array[i] = data[i];
|
|
94
|
+
}
|
|
95
|
+
desc.Set("data", array);
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case duckdb::LogicalTypeId::DOUBLE: {
|
|
100
|
+
if (with_data) {
|
|
101
|
+
auto array = Napi::Float64Array::New(env, chunk.size());
|
|
102
|
+
auto data = duckdb::FlatVector::GetData<double>(*vec);
|
|
103
|
+
for (size_t i = 0; i < chunk.size(); ++i) {
|
|
104
|
+
array[i] = data[i];
|
|
105
|
+
}
|
|
106
|
+
desc.Set("data", array);
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case duckdb::LogicalTypeId::BIGINT:
|
|
111
|
+
case duckdb::LogicalTypeId::TIME:
|
|
112
|
+
case duckdb::LogicalTypeId::TIME_TZ:
|
|
113
|
+
case duckdb::LogicalTypeId::TIMESTAMP_MS:
|
|
114
|
+
case duckdb::LogicalTypeId::TIMESTAMP_NS:
|
|
115
|
+
case duckdb::LogicalTypeId::TIMESTAMP_SEC:
|
|
116
|
+
case duckdb::LogicalTypeId::TIMESTAMP: {
|
|
117
|
+
if (with_data) {
|
|
118
|
+
#if NAPI_VERSION > 5
|
|
119
|
+
auto array = Napi::BigInt64Array::New(env, chunk.size());
|
|
120
|
+
auto data = duckdb::FlatVector::GetData<int64_t>(*vec);
|
|
121
|
+
#else
|
|
122
|
+
auto array = Napi::Float64Array::New(env, chunk.size());
|
|
123
|
+
auto data = duckdb::FlatVector::GetData<int64_t>(*vec);
|
|
124
|
+
#endif
|
|
125
|
+
for (size_t i = 0; i < chunk.size(); ++i) {
|
|
126
|
+
array[i] = data[i];
|
|
127
|
+
}
|
|
128
|
+
desc.Set("data", array);
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case duckdb::LogicalTypeId::UBIGINT: {
|
|
133
|
+
if (with_data) {
|
|
134
|
+
#if NAPI_VERSION > 5
|
|
135
|
+
auto array = Napi::BigUint64Array::New(env, chunk.size());
|
|
136
|
+
auto data = duckdb::FlatVector::GetData<uint64_t>(*vec);
|
|
137
|
+
#else
|
|
138
|
+
auto array = Napi::Float64Array::New(env, chunk.size());
|
|
139
|
+
auto data = duckdb::FlatVector::GetData<int64_t>(*vec);
|
|
140
|
+
#endif
|
|
141
|
+
for (size_t i = 0; i < chunk.size(); ++i) {
|
|
142
|
+
array[i] = data[i];
|
|
143
|
+
}
|
|
144
|
+
desc.Set("data", array);
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case duckdb::LogicalTypeId::BLOB:
|
|
149
|
+
case duckdb::LogicalTypeId::VARCHAR: {
|
|
150
|
+
if (with_data) {
|
|
151
|
+
auto array = Napi::Array::New(env, chunk.size());
|
|
152
|
+
auto data = duckdb::FlatVector::GetData<duckdb::string_t>(*vec);
|
|
153
|
+
for (size_t i = 0; i < chunk.size(); ++i) {
|
|
154
|
+
array.Set(i, data[i].GetString());
|
|
155
|
+
}
|
|
156
|
+
desc.Set("data", array);
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case duckdb::LogicalTypeId::STRUCT: {
|
|
161
|
+
auto child_count = duckdb::StructType::GetChildCount(vec_type);
|
|
162
|
+
auto &entries = duckdb::StructVector::GetEntries(*vec);
|
|
163
|
+
desc.Set("children", Napi::Array::New(env, child_count));
|
|
164
|
+
for (size_t i = 0; i < child_count; ++i) {
|
|
165
|
+
auto c = child_count - 1 - i;
|
|
166
|
+
auto &entry = entries[c];
|
|
167
|
+
auto desc = Napi::Object::New(env);
|
|
168
|
+
auto name = duckdb::StructType::GetChildName(vec_type, c);
|
|
169
|
+
desc.Set("name", name);
|
|
170
|
+
pending.emplace_back(false, entry.get(), desc, current_idx, i);
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
default:
|
|
175
|
+
Napi::TypeError::New(env, "Unsupported UDF argument type " + vec->GetType().ToString())
|
|
176
|
+
.ThrowAsJavaScriptException();
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
col_descs.Set(col_idx, col_desc);
|
|
181
|
+
}
|
|
182
|
+
return col_descs;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
} // namespace node_duckdb
|
package/src/database.cpp
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include "duckdb_node.hpp"
|
|
2
|
+
#include "napi.h"
|
|
2
3
|
#include "parquet-amalgamation.hpp"
|
|
3
4
|
|
|
4
5
|
namespace node_duckdb {
|
|
@@ -22,17 +23,39 @@ Napi::Object Database::Init(Napi::Env env, Napi::Object exports) {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
struct OpenTask : public Task {
|
|
25
|
-
OpenTask(Database &database_, std::string filename_,
|
|
26
|
-
|
|
26
|
+
OpenTask(Database &database_, std::string filename_, duckdb::AccessMode access_mode_, Napi::Object config_,
|
|
27
|
+
Napi::Function callback_)
|
|
28
|
+
: Task(database_, callback_), filename(filename_) {
|
|
29
|
+
|
|
30
|
+
duckdb_config.options.access_mode = access_mode_;
|
|
31
|
+
Napi::Env env = database_.Env();
|
|
32
|
+
Napi::HandleScope scope(env);
|
|
33
|
+
|
|
34
|
+
if (!config_.IsUndefined()) {
|
|
35
|
+
const Napi::Array config_names = config_.GetPropertyNames();
|
|
36
|
+
|
|
37
|
+
for (duckdb::idx_t config_idx = 0; config_idx < config_names.Length(); config_idx++) {
|
|
38
|
+
std::string key = config_names.Get(config_idx).As<Napi::String>();
|
|
39
|
+
std::string val = config_.Get(key).As<Napi::String>();
|
|
40
|
+
auto config_property = duckdb::DBConfig::GetOptionByName(key);
|
|
41
|
+
if (!config_property) {
|
|
42
|
+
Napi::TypeError::New(env, "Unrecognized configuration property" + key).ThrowAsJavaScriptException();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
duckdb_config.SetOption(*config_property, duckdb::Value(val));
|
|
47
|
+
} catch (std::exception &e) {
|
|
48
|
+
Napi::TypeError::New(env, "Failed to set configuration option " + key + ": " + e.what())
|
|
49
|
+
.ThrowAsJavaScriptException();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
27
54
|
}
|
|
28
55
|
|
|
29
56
|
void DoWork() override {
|
|
30
57
|
try {
|
|
31
|
-
duckdb::
|
|
32
|
-
if (read_only) {
|
|
33
|
-
config.access_mode = duckdb::AccessMode::READ_ONLY;
|
|
34
|
-
}
|
|
35
|
-
Get<Database>().database = duckdb::make_unique<duckdb::DuckDB>(filename, &config);
|
|
58
|
+
Get<Database>().database = duckdb::make_unique<duckdb::DuckDB>(filename, &duckdb_config);
|
|
36
59
|
duckdb::ParquetExtension extension;
|
|
37
60
|
extension.Load(*Get<Database>().database);
|
|
38
61
|
success = true;
|
|
@@ -59,22 +82,35 @@ struct OpenTask : public Task {
|
|
|
59
82
|
}
|
|
60
83
|
|
|
61
84
|
std::string filename;
|
|
62
|
-
|
|
85
|
+
duckdb::DBConfig duckdb_config;
|
|
63
86
|
std::string error = "";
|
|
64
87
|
bool success = false;
|
|
65
88
|
};
|
|
66
89
|
|
|
67
|
-
Database::Database(const Napi::CallbackInfo &info)
|
|
90
|
+
Database::Database(const Napi::CallbackInfo &info)
|
|
91
|
+
: Napi::ObjectWrap<Database>(info), task_inflight(false), env(info.Env()) {
|
|
68
92
|
auto env = info.Env();
|
|
93
|
+
|
|
69
94
|
if (info.Length() < 1 || !info[0].IsString()) {
|
|
70
95
|
Napi::TypeError::New(env, "Database location expected").ThrowAsJavaScriptException();
|
|
71
96
|
return;
|
|
72
97
|
}
|
|
73
98
|
std::string filename = info[0].As<Napi::String>();
|
|
74
99
|
unsigned int pos = 1;
|
|
100
|
+
|
|
101
|
+
duckdb::AccessMode access_mode = duckdb::AccessMode::AUTOMATIC;
|
|
102
|
+
|
|
75
103
|
int mode = 0;
|
|
76
104
|
if (info.Length() >= pos && info[pos].IsNumber() && Utils::OtherIsInt(info[pos].As<Napi::Number>())) {
|
|
77
105
|
mode = info[pos++].As<Napi::Number>().Int32Value();
|
|
106
|
+
if (mode == DUCKDB_NODEJS_READONLY) {
|
|
107
|
+
access_mode = duckdb::AccessMode::READ_ONLY;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Napi::Object config;
|
|
112
|
+
if (info.Length() >= pos && info[pos].IsObject() && !info[pos].IsFunction()) {
|
|
113
|
+
config = info[pos++].As<Napi::Object>();
|
|
78
114
|
}
|
|
79
115
|
|
|
80
116
|
Napi::Function callback;
|
|
@@ -82,7 +118,11 @@ Database::Database(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Database>(
|
|
|
82
118
|
callback = info[pos++].As<Napi::Function>();
|
|
83
119
|
}
|
|
84
120
|
|
|
85
|
-
Schedule(env, duckdb::make_unique<OpenTask>(*this, filename,
|
|
121
|
+
Schedule(env, duckdb::make_unique<OpenTask>(*this, filename, access_mode, config, callback));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Database::~Database() {
|
|
125
|
+
Napi::MemoryManagement::AdjustExternalMemory(env, -bytes_allocated);
|
|
86
126
|
}
|
|
87
127
|
|
|
88
128
|
void Database::Schedule(Napi::Env env, std::unique_ptr<Task> task) {
|
|
@@ -93,17 +133,15 @@ void Database::Schedule(Napi::Env env, std::unique_ptr<Task> task) {
|
|
|
93
133
|
Process(env);
|
|
94
134
|
}
|
|
95
135
|
|
|
96
|
-
static void
|
|
136
|
+
static void TaskExecuteCallback(napi_env e, void *data) {
|
|
97
137
|
auto holder = (TaskHolder *)data;
|
|
98
138
|
holder->task->DoWork();
|
|
99
139
|
}
|
|
100
140
|
|
|
101
|
-
static void
|
|
141
|
+
static void TaskCompleteCallback(napi_env e, napi_status status, void *data) {
|
|
102
142
|
std::unique_ptr<TaskHolder> holder((TaskHolder *)data);
|
|
103
143
|
holder->db->TaskComplete(e);
|
|
104
|
-
|
|
105
|
-
holder->task->Callback();
|
|
106
|
-
}
|
|
144
|
+
holder->task->DoCallback();
|
|
107
145
|
}
|
|
108
146
|
|
|
109
147
|
void Database::TaskComplete(Napi::Env env) {
|
|
@@ -112,6 +150,16 @@ void Database::TaskComplete(Napi::Env env) {
|
|
|
112
150
|
task_inflight = false;
|
|
113
151
|
}
|
|
114
152
|
Process(env);
|
|
153
|
+
|
|
154
|
+
if (database) {
|
|
155
|
+
// Bookkeeping: tell node (and the node GC in particular) how much
|
|
156
|
+
// memory we're using, such that it can make better decisions on when to
|
|
157
|
+
// trigger collections.
|
|
158
|
+
auto &buffer_manager = duckdb::BufferManager::GetBufferManager(*database->instance);
|
|
159
|
+
auto current_bytes = buffer_manager.GetUsedMemory();
|
|
160
|
+
Napi::MemoryManagement::AdjustExternalMemory(env, current_bytes - bytes_allocated);
|
|
161
|
+
bytes_allocated = current_bytes;
|
|
162
|
+
}
|
|
115
163
|
}
|
|
116
164
|
|
|
117
165
|
void Database::Process(Napi::Env env) {
|
|
@@ -131,8 +179,8 @@ void Database::Process(Napi::Env env) {
|
|
|
131
179
|
holder->task = move(task);
|
|
132
180
|
holder->db = this;
|
|
133
181
|
|
|
134
|
-
napi_create_async_work(env,
|
|
135
|
-
holder, &holder->request);
|
|
182
|
+
napi_create_async_work(env, nullptr, Napi::String::New(env, "duckdb.Database.Task"), TaskExecuteCallback,
|
|
183
|
+
TaskCompleteCallback, holder, &holder->request);
|
|
136
184
|
|
|
137
185
|
napi_queue_async_work(env, holder->request);
|
|
138
186
|
}
|
|
@@ -157,7 +205,7 @@ Napi::Value Database::Serialize(const Napi::CallbackInfo &info) {
|
|
|
157
205
|
}
|
|
158
206
|
|
|
159
207
|
struct WaitTask : public Task {
|
|
160
|
-
WaitTask(Database &
|
|
208
|
+
WaitTask(Database &database, Napi::Function callback) : Task(database, callback) {
|
|
161
209
|
}
|
|
162
210
|
|
|
163
211
|
void DoWork() override {
|
|
@@ -171,7 +219,7 @@ Napi::Value Database::Wait(const Napi::CallbackInfo &info) {
|
|
|
171
219
|
}
|
|
172
220
|
|
|
173
221
|
struct CloseTask : public Task {
|
|
174
|
-
CloseTask(Database &
|
|
222
|
+
CloseTask(Database &database, Napi::Function callback) : Task(database, callback) {
|
|
175
223
|
}
|
|
176
224
|
|
|
177
225
|
void DoWork() override {
|