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.
@@ -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 &connection_, Napi::Function callback_) : Task(connection_, callback_) {
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
- static Napi::Value transform_vector(Napi::Env env, duckdb::Vector &vec, duckdb::idx_t rows, bool copy) {
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
- // set up descriptor and data arrays
154
- Napi::Array args_descr(Napi::Array::New(env, jsargs->args->ColumnCount()));
155
- for (duckdb::idx_t col_idx = 0; col_idx < jsargs->args->ColumnCount(); col_idx++) {
156
- auto &vec = jsargs->args->data[col_idx];
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
- descr.Set("args", args_descr);
165
- descr.Set("ret", ret_descr);
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 = ret_descr.ToObject()
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
- if (jsargs->result->GetType().id() == duckdb::LogicalTypeId::VARCHAR) {
194
- auto return_string_array = ret_descr.ToObject()
195
- .Get("data_buffers")
196
- .ToObject()
197
- .Get(ret_descr.ToObject().Get("data_buffer"))
198
- .ToObject();
199
- auto return_string_vec_ptr = duckdb::FlatVector::GetData<duckdb::string_t>(*jsargs->result);
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
- if (!return_validity[row_idx]) {
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
- } else {
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 &connection_, std::string name_, std::string return_type_name_, Napi::Function callback_)
226
- : Task(connection_, callback_), name(name_), return_type_name(return_type_name_) {
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.Normalify();
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 = DuckDBNodeUDFFUnction::New(env, udf_callback, "duckdb_node_udf" + name, 0, 1, nullptr,
286
- [](Napi::Env, void *, nullptr_t *ctx) {});
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 &connection_, std::string name_, Napi::Function callback_)
301
- : Task(connection_, callback_), name(name_) {
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 &connection_, std::string sql_, Napi::Function callback_)
344
- : Task(connection_, callback_), sql(sql_) {
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
- auto statements = connection.connection->ExtractStatements(sql);
352
- if (statements.size() == 0) {
353
- return;
354
- }
324
+ try {
325
+ auto statements = connection.connection->ExtractStatements(sql);
326
+ if (statements.empty()) {
327
+ return;
328
+ }
355
329
 
356
- // thanks Mark
357
- for (duckdb::idx_t i = 0; i < statements.size(); i++) {
358
- auto res = connection.connection->Query(move(statements[i]));
359
- if (!res->success) {
360
- success = false;
361
- error = res->error;
362
- break;
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_, bool read_only_, Napi::Function callback_)
26
- : Task(database_, callback_), filename(filename_), read_only(read_only_) {
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::DBConfig config;
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
- bool read_only = false;
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) : Napi::ObjectWrap<Database>(info), task_inflight(false) {
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, mode == DUCKDB_NODEJS_READONLY, callback));
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 task_execute(napi_env e, void *data) {
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 task_complete(napi_env e, napi_status status, void *data) {
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
- if (holder->task->callback.Value().IsFunction()) {
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, NULL, Napi::String::New(env, "duckdb.Database.Task"), task_execute, task_complete,
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 &database_, Napi::Function callback_) : Task(database_, callback_) {
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 &database_, Napi::Function callback_) : Task(database_, callback_) {
222
+ CloseTask(Database &database, Napi::Function callback) : Task(database, callback) {
175
223
  }
176
224
 
177
225
  void DoWork() override {