duckdb 0.3.3-dev449.0 → 0.3.3-dev475.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 -0
- package/binding.gyp +8 -8
- package/lib/duckdb.js +108 -20
- package/package.json +1 -1
- package/src/connection.cpp +274 -1
- package/src/database.cpp +1 -0
- package/src/duckdb.cpp +27 -6
- package/src/duckdb.hpp +2 -2
- package/src/duckdb_node.hpp +9 -0
- package/src/parquet-amalgamation.cpp +33427 -33427
- package/test/udf.test.js +149 -0
package/Makefile
CHANGED
package/binding.gyp
CHANGED
|
@@ -61,14 +61,14 @@
|
|
|
61
61
|
},
|
|
62
62
|
], # OS=="win"
|
|
63
63
|
], # conditions
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
# uncomment this to build against existing lib
|
|
65
|
+
# "libraries": [
|
|
66
|
+
# "/Users/hannes/source/duckdb/build/release/src/libduckdb_static.a",
|
|
67
|
+
# "/Users/hannes/source/duckdb/build/release/third_party/fmt/libduckdb_fmt.a",
|
|
68
|
+
# "/Users/hannes/source/duckdb/build/release/third_party/libpg_query/libduckdb_pg_query.a",
|
|
69
|
+
# "/Users/hannes/source/duckdb/build/release/third_party/utf8proc/libduckdb_utf8proc.a",
|
|
70
|
+
# "/Users/hannes/source/duckdb/build/release/third_party/re2/libduckdb_re2.a"
|
|
71
|
+
# ]
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
"target_name": "action_after_build",
|
package/lib/duckdb.js
CHANGED
|
@@ -23,42 +23,130 @@ Connection.prototype.each = function(sql) {
|
|
|
23
23
|
return statement.each.apply(statement, arguments);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
function ptr_to_arr(buffer, ptype, n) {
|
|
27
|
+
// TODO can we create those on the C++ side of things already?
|
|
28
|
+
switch(ptype) {
|
|
29
|
+
case 'BOOL':
|
|
30
|
+
case 'UINT8':
|
|
31
|
+
return new Uint8Array(buffer.buffer, 0, n);
|
|
32
|
+
case 'INT8':
|
|
33
|
+
return new Int8Array(buffer.buffer, 0, n);
|
|
34
|
+
case 'INT16':
|
|
35
|
+
return new Int16Array(buffer.buffer, 0, n);
|
|
36
|
+
case 'UINT16':
|
|
37
|
+
return new UInt16Array(buffer.buffer, 0, n);
|
|
38
|
+
case 'INT32':
|
|
39
|
+
return new Int32Array(buffer.buffer, 0, n);
|
|
40
|
+
case 'UINT32':
|
|
41
|
+
return new UInt32Array(buffer.buffer, 0, n);
|
|
42
|
+
case 'FLOAT':
|
|
43
|
+
return new Float32Array(buffer.buffer, 0, n);
|
|
44
|
+
case 'DOUBLE':
|
|
45
|
+
return new Float64Array(buffer.buffer, 0, n);
|
|
46
|
+
case 'VARCHAR': // we already have created a string array on the C++ side for this
|
|
47
|
+
return buffer;
|
|
48
|
+
default:
|
|
49
|
+
return new Array<string>(0); // cough
|
|
29
50
|
}
|
|
30
|
-
return this.default_connection.prepare.apply(this.default_connection, arguments);
|
|
31
51
|
}
|
|
32
52
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
53
|
+
// this follows the wasm udfs somewhat but is simpler because we can pass data much more cleanly
|
|
54
|
+
Connection.prototype.register = function(name, return_type, fun) {
|
|
55
|
+
// TODO what if this throws an error somewhere? do we need a try/catch?
|
|
56
|
+
return this.register_bulk(name, return_type, function(descr) {
|
|
57
|
+
try {
|
|
58
|
+
const data_arr = [];
|
|
59
|
+
const validity_arr = [];
|
|
60
|
+
|
|
61
|
+
for (const idx in descr.args) {
|
|
62
|
+
const arg = descr.args[idx];
|
|
63
|
+
validity_arr.push(arg.data_buffers[arg.validity_buffer]);
|
|
64
|
+
data_arr.push(ptr_to_arr(arg.data_buffers[arg.data_buffer], arg.physical_type, descr.rows));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const out_data = ptr_to_arr(descr.ret.data_buffers[descr.ret.data_buffer], descr.ret.physical_type, descr.rows);
|
|
68
|
+
const out_validity = descr.ret.data_buffers[descr.ret.validity_buffer];
|
|
69
|
+
|
|
70
|
+
switch (descr.args.length) {
|
|
71
|
+
case 0:
|
|
72
|
+
for (let i = 0; i < descr.rows; ++i) {
|
|
73
|
+
const res = fun();
|
|
74
|
+
out_data[i] = res;
|
|
75
|
+
out_validity[i] = res == undefined || res == null ? 0 : 1;
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
case 1:
|
|
79
|
+
for (let i = 0; i < descr.rows; ++i) {
|
|
80
|
+
const res = fun(validity_arr[0][i] ? data_arr[0][i] : undefined);
|
|
81
|
+
out_data[i] = res;
|
|
82
|
+
out_validity[i] = res == undefined || res == null ? 0 : 1;
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
case 2:
|
|
86
|
+
for (let i = 0; i < descr.rows; ++i) {
|
|
87
|
+
const res = fun(validity_arr[0][i] ? data_arr[0][i] : undefined, validity_arr[1][i] ? data_arr[1][i] : undefined);
|
|
88
|
+
out_data[i] = res;
|
|
89
|
+
out_validity[i] = res == undefined || res == null ? 0 : 1;
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
case 3:
|
|
93
|
+
for (let i = 0; i < descr.rows; ++i) {
|
|
94
|
+
const res = fun(validity_arr[0][i] ? data_arr[0][i] : undefined, validity_arr[1][i] ? data_arr[1][i] : undefined, validity_arr[2][i] ? data_arr[2][i] : undefined);
|
|
95
|
+
out_data[i] = res;
|
|
96
|
+
out_validity[i] = res == undefined || res == null ? 0 : 1;
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
throw "Unsupported argument count";
|
|
101
|
+
}
|
|
102
|
+
} catch(error) { // work around recently fixed napi bug https://github.com/nodejs/node-addon-api/issues/912
|
|
103
|
+
msg = error;
|
|
104
|
+
if (typeof error == 'object' && 'message' in error) {
|
|
105
|
+
msg = error.message
|
|
106
|
+
}
|
|
107
|
+
throw {name: 'DuckDB-UDF-Exception', message : msg};
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
default_connection = function(o) {
|
|
113
|
+
if (o.default_connection == undefined) {
|
|
114
|
+
o.default_connection = new duckdb.Connection(o);
|
|
36
115
|
}
|
|
37
|
-
|
|
116
|
+
return(o.default_connection);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
Database.prototype.prepare = function() {
|
|
120
|
+
return default_connection(this).prepare.apply(this.default_connection, arguments);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
Database.prototype.run = function() {
|
|
124
|
+
default_connection(this).run.apply(this.default_connection, arguments);
|
|
38
125
|
return this;
|
|
39
126
|
}
|
|
40
127
|
|
|
41
128
|
Database.prototype.each = function() {
|
|
42
|
-
|
|
43
|
-
this.default_connection = new Connection(this);
|
|
44
|
-
}
|
|
45
|
-
this.default_connection.each.apply(this.default_connection, arguments);
|
|
129
|
+
default_connection(this).each.apply(this.default_connection, arguments);
|
|
46
130
|
return this;
|
|
47
131
|
}
|
|
48
132
|
|
|
49
133
|
Database.prototype.all = function() {
|
|
50
|
-
|
|
51
|
-
this.default_connection = new Connection(this);
|
|
52
|
-
}
|
|
53
|
-
this.default_connection.all.apply(this.default_connection, arguments);
|
|
134
|
+
default_connection(this).all.apply(this.default_connection, arguments);
|
|
54
135
|
return this;
|
|
55
136
|
}
|
|
56
137
|
|
|
57
138
|
Database.prototype.exec = function() {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
139
|
+
default_connection(this).exec.apply(this.default_connection, arguments);
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Database.prototype.register = function() {
|
|
144
|
+
default_connection(this).register.apply(this.default_connection, arguments);
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
Database.prototype.unregister = function() {
|
|
149
|
+
default_connection(this).unregister.apply(this.default_connection, arguments);
|
|
62
150
|
return this;
|
|
63
151
|
}
|
|
64
152
|
|
package/package.json
CHANGED
package/src/connection.cpp
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#include "duckdb_node.hpp"
|
|
2
2
|
|
|
3
|
+
#include <thread>
|
|
4
|
+
|
|
3
5
|
namespace node_duckdb {
|
|
4
6
|
|
|
5
7
|
Napi::FunctionReference Connection::constructor;
|
|
@@ -9,7 +11,9 @@ Napi::Object Connection::Init(Napi::Env env, Napi::Object exports) {
|
|
|
9
11
|
|
|
10
12
|
Napi::Function t =
|
|
11
13
|
DefineClass(env, "Connection",
|
|
12
|
-
{InstanceMethod("prepare", &Connection::Prepare), InstanceMethod("exec", &Connection::Exec)
|
|
14
|
+
{InstanceMethod("prepare", &Connection::Prepare), InstanceMethod("exec", &Connection::Exec),
|
|
15
|
+
InstanceMethod("register_bulk", &Connection::Register),
|
|
16
|
+
InstanceMethod("unregister", &Connection::Unregister)});
|
|
13
17
|
|
|
14
18
|
constructor = Napi::Persistent(t);
|
|
15
19
|
constructor.SuppressDestruct();
|
|
@@ -66,6 +70,275 @@ Napi::Value Connection::Prepare(const Napi::CallbackInfo &info) {
|
|
|
66
70
|
return res->Value();
|
|
67
71
|
}
|
|
68
72
|
|
|
73
|
+
struct JSArgs {
|
|
74
|
+
duckdb::idx_t rows;
|
|
75
|
+
duckdb::DataChunk *args;
|
|
76
|
+
duckdb::Vector *result;
|
|
77
|
+
bool done;
|
|
78
|
+
std::string error;
|
|
79
|
+
};
|
|
80
|
+
|
|
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) {
|
|
151
|
+
try { // if we dont catch exceptions here we terminate node if one happens ^^
|
|
152
|
+
|
|
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));
|
|
163
|
+
descr.Set("rows", jsargs->rows);
|
|
164
|
+
descr.Set("args", args_descr);
|
|
165
|
+
descr.Set("ret", ret_descr);
|
|
166
|
+
|
|
167
|
+
// actually call the UDF, or rather its vectorized wrapper from duckdb.js/Connection.prototype.register wrapper
|
|
168
|
+
jsudf({descr});
|
|
169
|
+
|
|
170
|
+
if (env.IsExceptionPending()) {
|
|
171
|
+
// bit of a dance to get a nice error message if possible
|
|
172
|
+
auto exception = env.GetAndClearPendingException();
|
|
173
|
+
std::string msg = exception.Message();
|
|
174
|
+
if (msg.empty()) {
|
|
175
|
+
auto exception_value = exception.Value();
|
|
176
|
+
if (exception_value.IsObject() && exception_value.Has("message")) {
|
|
177
|
+
msg = exception_value.Get("message").ToString().Utf8Value();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
throw duckdb::IOException("UDF Execution Error: " + msg);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 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>>();
|
|
189
|
+
for (duckdb::idx_t row_idx = 0; row_idx < jsargs->rows; row_idx++) {
|
|
190
|
+
duckdb::FlatVector::SetNull(*jsargs->result, row_idx, !return_validity[row_idx]);
|
|
191
|
+
}
|
|
192
|
+
|
|
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
|
+
|
|
201
|
+
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
|
+
}
|
|
208
|
+
}
|
|
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()));
|
|
217
|
+
}
|
|
218
|
+
} catch (const std::exception &e) {
|
|
219
|
+
jsargs->error = e.what();
|
|
220
|
+
}
|
|
221
|
+
jsargs->done = true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
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_) {
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
void DoWork() override {
|
|
230
|
+
auto &connection = Get<Connection>();
|
|
231
|
+
auto &udf_ptr = connection.udfs[name];
|
|
232
|
+
duckdb::scalar_function_t udf_function = [&udf_ptr](duckdb::DataChunk &args, duckdb::ExpressionState &state,
|
|
233
|
+
duckdb::Vector &result) -> void {
|
|
234
|
+
// here we can do only DuckDB stuff because we do not have a functioning env
|
|
235
|
+
|
|
236
|
+
// Flatten all args to simplify udfs
|
|
237
|
+
args.Normalify();
|
|
238
|
+
|
|
239
|
+
JSArgs jsargs;
|
|
240
|
+
jsargs.rows = args.size();
|
|
241
|
+
jsargs.args = &args;
|
|
242
|
+
jsargs.result = &result;
|
|
243
|
+
jsargs.done = false;
|
|
244
|
+
|
|
245
|
+
udf_ptr.BlockingCall(&jsargs);
|
|
246
|
+
while (!jsargs.done) {
|
|
247
|
+
std::this_thread::yield();
|
|
248
|
+
}
|
|
249
|
+
if (!jsargs.error.empty()) {
|
|
250
|
+
throw duckdb::IOException(jsargs.error);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
auto expr = duckdb::Parser::ParseExpressionList(duckdb::StringUtil::Format("asdf::%s", return_type_name));
|
|
255
|
+
auto &cast = (duckdb::CastExpression &)*expr[0];
|
|
256
|
+
auto return_type = cast.cast_type;
|
|
257
|
+
|
|
258
|
+
connection.connection->CreateVectorizedFunction(name, std::vector<duckdb::LogicalType> {}, return_type,
|
|
259
|
+
udf_function, duckdb::LogicalType::ANY);
|
|
260
|
+
}
|
|
261
|
+
std::string name;
|
|
262
|
+
std::string return_type_name;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
Napi::Value Connection::Register(const Napi::CallbackInfo &info) {
|
|
266
|
+
auto env = info.Env();
|
|
267
|
+
if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsFunction()) {
|
|
268
|
+
Napi::TypeError::New(env, "Holding it wrong").ThrowAsJavaScriptException();
|
|
269
|
+
return env.Null();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
std::string name = info[0].As<Napi::String>();
|
|
273
|
+
std::string return_type_name = info[1].As<Napi::String>();
|
|
274
|
+
Napi::Function udf_callback = info[2].As<Napi::Function>();
|
|
275
|
+
Napi::Function completion_callback;
|
|
276
|
+
if (info.Length() > 3 && info[3].IsFunction()) {
|
|
277
|
+
completion_callback = info[3].As<Napi::Function>();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (udfs.find(name) != udfs.end()) {
|
|
281
|
+
Napi::TypeError::New(env, "UDF with this name already exists").ThrowAsJavaScriptException();
|
|
282
|
+
return env.Null();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
auto udf = DuckDBNodeUDFFUnction::New(env, udf_callback, "duckdb_node_udf" + name, 0, 1, nullptr,
|
|
286
|
+
[](Napi::Env, void *, nullptr_t *ctx) {});
|
|
287
|
+
|
|
288
|
+
// we have to unref the udf because otherwise there is a circular ref with the connection somehow(?)
|
|
289
|
+
// this took far too long to figure out
|
|
290
|
+
udf.Unref(env);
|
|
291
|
+
udfs[name] = udf;
|
|
292
|
+
|
|
293
|
+
database_ref->Schedule(info.Env(),
|
|
294
|
+
duckdb::make_unique<RegisterTask>(*this, name, return_type_name, completion_callback));
|
|
295
|
+
|
|
296
|
+
return Value();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
struct UnregisterTask : public Task {
|
|
300
|
+
UnregisterTask(Connection &connection_, std::string name_, Napi::Function callback_)
|
|
301
|
+
: Task(connection_, callback_), name(name_) {
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
void DoWork() override {
|
|
305
|
+
auto &connection = Get<Connection>();
|
|
306
|
+
if (connection.udfs.find(name) == connection.udfs.end()) { // silently ignore
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
connection.udfs[name].Release();
|
|
311
|
+
connection.udfs.erase(name);
|
|
312
|
+
auto &con = *connection.connection;
|
|
313
|
+
con.BeginTransaction();
|
|
314
|
+
auto &context = *con.context;
|
|
315
|
+
auto &catalog = duckdb::Catalog::GetCatalog(context);
|
|
316
|
+
duckdb::DropInfo info;
|
|
317
|
+
info.type = duckdb::CatalogType::SCALAR_FUNCTION_ENTRY;
|
|
318
|
+
info.name = name;
|
|
319
|
+
catalog.DropEntry(context, &info);
|
|
320
|
+
con.Commit();
|
|
321
|
+
}
|
|
322
|
+
std::string name;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
Napi::Value Connection::Unregister(const Napi::CallbackInfo &info) {
|
|
326
|
+
auto env = info.Env();
|
|
327
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
328
|
+
Napi::TypeError::New(env, "Holding it wrong").ThrowAsJavaScriptException();
|
|
329
|
+
return env.Null();
|
|
330
|
+
}
|
|
331
|
+
std::string name = info[0].As<Napi::String>();
|
|
332
|
+
|
|
333
|
+
Napi::Function callback;
|
|
334
|
+
if (info.Length() > 1 && info[1].IsFunction()) {
|
|
335
|
+
callback = info[1].As<Napi::Function>();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
database_ref->Schedule(info.Env(), duckdb::make_unique<UnregisterTask>(*this, name, callback));
|
|
339
|
+
return Value();
|
|
340
|
+
}
|
|
341
|
+
|
|
69
342
|
struct ExecTask : public Task {
|
|
70
343
|
ExecTask(Connection &connection_, std::string sql_, Napi::Function callback_)
|
|
71
344
|
: Task(connection_, callback_), sql(sql_) {
|
package/src/database.cpp
CHANGED
package/src/duckdb.cpp
CHANGED
|
@@ -44384,6 +44384,9 @@ static bool BlobCastSwitch(Vector &source, Vector &result, idx_t count, string *
|
|
|
44384
44384
|
// blob to varchar
|
|
44385
44385
|
VectorStringCast<string_t, duckdb::CastFromBlob>(source, result, count);
|
|
44386
44386
|
break;
|
|
44387
|
+
case LogicalTypeId::AGGREGATE_STATE:
|
|
44388
|
+
result.Reinterpret(source);
|
|
44389
|
+
break;
|
|
44387
44390
|
default:
|
|
44388
44391
|
return TryVectorNullCast(source, result, count, error_message);
|
|
44389
44392
|
}
|
|
@@ -44572,6 +44575,15 @@ static bool EnumCastSwitch(Vector &source, Vector &result, idx_t count, string *
|
|
|
44572
44575
|
return true;
|
|
44573
44576
|
}
|
|
44574
44577
|
|
|
44578
|
+
static bool AggregateStateToBlobCast(Vector &source, Vector &result, idx_t count, string *error_message, bool strict) {
|
|
44579
|
+
if (result.GetType().id() != LogicalTypeId::BLOB) {
|
|
44580
|
+
throw TypeMismatchException(source.GetType(), result.GetType(),
|
|
44581
|
+
"Cannot cast AGGREGATE_STATE to anything but BLOB");
|
|
44582
|
+
}
|
|
44583
|
+
result.Reinterpret(source);
|
|
44584
|
+
return true;
|
|
44585
|
+
}
|
|
44586
|
+
|
|
44575
44587
|
static bool StructCastSwitch(Vector &source, Vector &result, idx_t count, string *error_message) {
|
|
44576
44588
|
switch (result.GetType().id()) {
|
|
44577
44589
|
case LogicalTypeId::STRUCT:
|
|
@@ -44687,6 +44699,8 @@ bool VectorOperations::TryCast(Vector &source, Vector &result, idx_t count, stri
|
|
|
44687
44699
|
return ListCastSwitch(source, result, count, error_message);
|
|
44688
44700
|
case LogicalTypeId::ENUM:
|
|
44689
44701
|
return EnumCastSwitch(source, result, count, error_message, strict);
|
|
44702
|
+
case LogicalTypeId::AGGREGATE_STATE:
|
|
44703
|
+
return AggregateStateToBlobCast(source, result, count, error_message, strict);
|
|
44690
44704
|
default:
|
|
44691
44705
|
return TryVectorNullCast(source, result, count, error_message);
|
|
44692
44706
|
}
|
|
@@ -71269,7 +71283,7 @@ struct FirstFunctionString : public FirstFunctionBase {
|
|
|
71269
71283
|
if (!state->is_set || state->is_null) {
|
|
71270
71284
|
mask.SetInvalid(idx);
|
|
71271
71285
|
} else {
|
|
71272
|
-
target[idx] = StringVector::
|
|
71286
|
+
target[idx] = StringVector::AddStringOrBlob(result, state->value);
|
|
71273
71287
|
}
|
|
71274
71288
|
}
|
|
71275
71289
|
|
|
@@ -92927,9 +92941,13 @@ static void AggregateStateCombine(DataChunk &input, ExpressionState &state_p, Ve
|
|
|
92927
92941
|
|
|
92928
92942
|
D_ASSERT(input.data.size() == 2);
|
|
92929
92943
|
D_ASSERT(input.data[0].GetType().id() == LogicalTypeId::AGGREGATE_STATE);
|
|
92930
|
-
D_ASSERT(input.data[0].GetType() == input.data[1].GetType());
|
|
92931
92944
|
D_ASSERT(input.data[0].GetType() == result.GetType());
|
|
92932
92945
|
|
|
92946
|
+
if (input.data[0].GetType().InternalType() != input.data[1].GetType().InternalType()) {
|
|
92947
|
+
throw IOException("Aggregate state combine type mismatch, expect %s, got %s",
|
|
92948
|
+
input.data[0].GetType().ToString(), input.data[1].GetType().ToString());
|
|
92949
|
+
}
|
|
92950
|
+
|
|
92933
92951
|
VectorData state0_data, state1_data;
|
|
92934
92952
|
input.data[0].Orrify(input.size(), state0_data);
|
|
92935
92953
|
input.data[1].Orrify(input.size(), state1_data);
|
|
@@ -92960,8 +92978,10 @@ static void AggregateStateCombine(DataChunk &input, ExpressionState &state_p, Ve
|
|
|
92960
92978
|
}
|
|
92961
92979
|
|
|
92962
92980
|
// we actually have to combine
|
|
92963
|
-
|
|
92964
|
-
|
|
92981
|
+
if (state0.GetSize() != bind_data.state_size || state1.GetSize() != bind_data.state_size) {
|
|
92982
|
+
throw IOException("Aggregate state size mismatch, expect %llu, got %llu and %llu", bind_data.state_size,
|
|
92983
|
+
state0.GetSize(), state1.GetSize());
|
|
92984
|
+
}
|
|
92965
92985
|
|
|
92966
92986
|
memcpy(local_state.state_buffer0.get(), state0.GetDataUnsafe(), bind_data.state_size);
|
|
92967
92987
|
memcpy(local_state.state_buffer1.get(), state1.GetDataUnsafe(), bind_data.state_size);
|
|
@@ -92988,7 +93008,8 @@ static unique_ptr<FunctionData> BindAggregateState(ClientContext &context, Scala
|
|
|
92988
93008
|
throw BinderException("Can only FINALIZE aggregate state, not %s", arg_return_type.ToString());
|
|
92989
93009
|
}
|
|
92990
93010
|
// combine
|
|
92991
|
-
if (arguments.size() == 2 && arguments[0]->return_type != arguments[1]->return_type
|
|
93011
|
+
if (arguments.size() == 2 && arguments[0]->return_type != arguments[1]->return_type &&
|
|
93012
|
+
arguments[1]->return_type.id() != LogicalTypeId::BLOB) {
|
|
92992
93013
|
throw BinderException("Cannot COMBINE aggregate states from different functions, %s <> %s",
|
|
92993
93014
|
arguments[0]->return_type.ToString(), arguments[1]->return_type.ToString());
|
|
92994
93015
|
}
|
|
@@ -93094,7 +93115,7 @@ ScalarFunction ExportAggregateFunction::GetFinalize() {
|
|
|
93094
93115
|
}
|
|
93095
93116
|
|
|
93096
93117
|
ScalarFunction ExportAggregateFunction::GetCombine() {
|
|
93097
|
-
return ScalarFunction("combine", {LogicalTypeId::AGGREGATE_STATE, LogicalTypeId::
|
|
93118
|
+
return ScalarFunction("combine", {LogicalTypeId::AGGREGATE_STATE, LogicalTypeId::ANY},
|
|
93098
93119
|
LogicalTypeId::AGGREGATE_STATE, AggregateStateCombine, false, BindAggregateState, nullptr,
|
|
93099
93120
|
nullptr, InitCombineState);
|
|
93100
93121
|
}
|
package/src/duckdb.hpp
CHANGED
|
@@ -11,8 +11,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|
|
11
11
|
#pragma once
|
|
12
12
|
#define DUCKDB_AMALGAMATION 1
|
|
13
13
|
#define DUCKDB_AMALGAMATION_EXTENDED 1
|
|
14
|
-
#define DUCKDB_SOURCE_ID "
|
|
15
|
-
#define DUCKDB_VERSION "v0.3.3-
|
|
14
|
+
#define DUCKDB_SOURCE_ID "18c35bc32"
|
|
15
|
+
#define DUCKDB_VERSION "v0.3.3-dev475"
|
|
16
16
|
//===----------------------------------------------------------------------===//
|
|
17
17
|
// DuckDB
|
|
18
18
|
//
|
package/src/duckdb_node.hpp
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#define NODE_ADDON_API_DISABLE_DEPRECATED
|
|
3
3
|
#include <napi.h>
|
|
4
4
|
#include <queue>
|
|
5
|
+
#include <unordered_map>
|
|
5
6
|
|
|
6
7
|
#include "duckdb.hpp"
|
|
7
8
|
|
|
@@ -76,6 +77,11 @@ private:
|
|
|
76
77
|
static Napi::FunctionReference constructor;
|
|
77
78
|
};
|
|
78
79
|
|
|
80
|
+
struct JSArgs;
|
|
81
|
+
void DuckDBNodeUDFLauncher(Napi::Env env, Napi::Function jsudf, nullptr_t *, JSArgs *data);
|
|
82
|
+
|
|
83
|
+
typedef Napi::TypedThreadSafeFunction<nullptr_t, JSArgs, DuckDBNodeUDFLauncher> DuckDBNodeUDFFUnction;
|
|
84
|
+
|
|
79
85
|
class Connection : public Napi::ObjectWrap<Connection> {
|
|
80
86
|
public:
|
|
81
87
|
Connection(const Napi::CallbackInfo &info);
|
|
@@ -85,6 +91,8 @@ public:
|
|
|
85
91
|
public:
|
|
86
92
|
Napi::Value Prepare(const Napi::CallbackInfo &info);
|
|
87
93
|
Napi::Value Exec(const Napi::CallbackInfo &info);
|
|
94
|
+
Napi::Value Register(const Napi::CallbackInfo &info);
|
|
95
|
+
Napi::Value Unregister(const Napi::CallbackInfo &info);
|
|
88
96
|
|
|
89
97
|
static bool HasInstance(Napi::Value val) {
|
|
90
98
|
Napi::Env env = val.Env();
|
|
@@ -99,6 +107,7 @@ public:
|
|
|
99
107
|
static Napi::FunctionReference constructor;
|
|
100
108
|
std::unique_ptr<duckdb::Connection> connection;
|
|
101
109
|
Database *database_ref;
|
|
110
|
+
std::unordered_map<std::string, DuckDBNodeUDFFUnction> udfs;
|
|
102
111
|
};
|
|
103
112
|
|
|
104
113
|
struct StatementParam;
|