duckdb 0.6.2-dev1060.0 → 0.6.2-dev1070.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.d.ts +13 -0
- package/lib/duckdb.js +9 -0
- package/package.json +2 -1
- package/src/database.cpp +112 -2
- package/src/duckdb/extension/icu/icu-dateadd.cpp +14 -1
- package/src/duckdb/src/function/table/version/pragma_version.cpp +2 -2
- package/src/duckdb_node.hpp +8 -0
- package/src/statement.cpp +1 -46
- package/src/utils.cpp +42 -0
- package/test/replacement_scan.test.ts +144 -0
package/lib/duckdb.d.ts
CHANGED
|
@@ -61,6 +61,15 @@ export class IpcResultStreamIterator implements AsyncIterator<Uint8Array>, Async
|
|
|
61
61
|
toArray(): Promise<ArrowArray>;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
export interface ReplacementScanResult {
|
|
65
|
+
function: string;
|
|
66
|
+
parameters: Array<unknown>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type ReplacementScanCallback = (
|
|
70
|
+
table: string
|
|
71
|
+
) => ReplacementScanResult | null;
|
|
72
|
+
|
|
64
73
|
export class Database {
|
|
65
74
|
constructor(path: string, accessMode?: number | Record<string,string>, callback?: Callback<any>);
|
|
66
75
|
constructor(path: string, callback?: Callback<any>);
|
|
@@ -99,6 +108,10 @@ export class Database {
|
|
|
99
108
|
register_buffer(name: string, array: ArrowIterable, force: boolean, callback?: Callback<void>): void;
|
|
100
109
|
|
|
101
110
|
unregister_buffer(name: string, callback?: Callback<void>): void;
|
|
111
|
+
|
|
112
|
+
registerReplacementScan(
|
|
113
|
+
replacementScan: ReplacementScanCallback
|
|
114
|
+
): Promise<void>;
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
export class Statement {
|
package/lib/duckdb.js
CHANGED
|
@@ -609,6 +609,15 @@ Database.prototype.unregister_udf = function () {
|
|
|
609
609
|
return this;
|
|
610
610
|
}
|
|
611
611
|
|
|
612
|
+
/**
|
|
613
|
+
* Register a table replace scan function
|
|
614
|
+
* @method
|
|
615
|
+
* @arg fun Replacement scan function
|
|
616
|
+
* @return {this}
|
|
617
|
+
*/
|
|
618
|
+
|
|
619
|
+
Database.prototype.registerReplacementScan;
|
|
620
|
+
|
|
612
621
|
/**
|
|
613
622
|
* Not implemented
|
|
614
623
|
*/
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "duckdb",
|
|
3
3
|
"main": "./lib/duckdb.js",
|
|
4
4
|
"types": "./lib/duckdb.d.ts",
|
|
5
|
-
"version": "0.6.2-
|
|
5
|
+
"version": "0.6.2-dev1070.0",
|
|
6
6
|
"description": "DuckDB node.js API",
|
|
7
7
|
"gypfile": true,
|
|
8
8
|
"dependencies": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"test": "test"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
+
"@types/chai": "^4.3.4",
|
|
29
30
|
"@types/mocha": "^10.0.0",
|
|
30
31
|
"@types/node": "^18.11.0",
|
|
31
32
|
"apache-arrow": "^9.0.0",
|
package/src/database.cpp
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
#include "
|
|
1
|
+
#include "duckdb/parser/expression/constant_expression.hpp"
|
|
2
|
+
#include "duckdb/parser/expression/function_expression.hpp"
|
|
3
|
+
#include "duckdb/parser/tableref/table_function_ref.hpp"
|
|
2
4
|
#include "duckdb/storage/buffer_manager.hpp"
|
|
5
|
+
#include "duckdb_node.hpp"
|
|
3
6
|
#include "napi.h"
|
|
4
7
|
|
|
8
|
+
#include <iostream>
|
|
9
|
+
#include <thread>
|
|
10
|
+
|
|
5
11
|
namespace node_duckdb {
|
|
6
12
|
|
|
7
13
|
Napi::FunctionReference Database::constructor;
|
|
@@ -13,7 +19,8 @@ Napi::Object Database::Init(Napi::Env env, Napi::Object exports) {
|
|
|
13
19
|
env, "Database",
|
|
14
20
|
{InstanceMethod("close_internal", &Database::Close), InstanceMethod("wait", &Database::Wait),
|
|
15
21
|
InstanceMethod("serialize", &Database::Serialize), InstanceMethod("parallelize", &Database::Parallelize),
|
|
16
|
-
InstanceMethod("connect", &Database::Connect), InstanceMethod("interrupt", &Database::Interrupt)
|
|
22
|
+
InstanceMethod("connect", &Database::Connect), InstanceMethod("interrupt", &Database::Interrupt),
|
|
23
|
+
InstanceMethod("registerReplacementScan", &Database::RegisterReplacementScan)});
|
|
17
24
|
|
|
18
25
|
constructor = Napi::Persistent(t);
|
|
19
26
|
constructor.SuppressDestruct();
|
|
@@ -267,4 +274,107 @@ Napi::Value Database::Connect(const Napi::CallbackInfo &info) {
|
|
|
267
274
|
return Connection::constructor.New({Value()});
|
|
268
275
|
}
|
|
269
276
|
|
|
277
|
+
struct JSRSArgs {
|
|
278
|
+
std::string table = "";
|
|
279
|
+
std::string function = "";
|
|
280
|
+
std::vector<duckdb::Value> parameters;
|
|
281
|
+
bool done = false;
|
|
282
|
+
duckdb::PreservedError error;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
struct NodeReplacementScanData : duckdb::ReplacementScanData {
|
|
286
|
+
NodeReplacementScanData(duckdb_node_rs_function_t rs) : rs(std::move(rs)) {};
|
|
287
|
+
duckdb_node_rs_function_t rs;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
void DuckDBNodeRSLauncher(Napi::Env env, Napi::Function jsrs, std::nullptr_t *, JSRSArgs *jsargs) {
|
|
291
|
+
try {
|
|
292
|
+
Napi::EscapableHandleScope scope(env);
|
|
293
|
+
auto arg = Napi::String::New(env, jsargs->table);
|
|
294
|
+
auto result = jsrs({arg});
|
|
295
|
+
if (result && result.IsObject()) {
|
|
296
|
+
auto obj = result.As<Napi::Object>();
|
|
297
|
+
jsargs->function = obj.Get("function").ToString().Utf8Value();
|
|
298
|
+
auto parameters = obj.Get("parameters");
|
|
299
|
+
if (parameters.IsArray()) {
|
|
300
|
+
auto paramArray = parameters.As<Napi::Array>();
|
|
301
|
+
for (uint32_t i = 0; i < paramArray.Length(); i++) {
|
|
302
|
+
jsargs->parameters.push_back(Utils::BindParameter(paramArray.Get(i)));
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
throw duckdb::Exception("Expected parameter array");
|
|
306
|
+
}
|
|
307
|
+
} else if (!result.IsNull()) {
|
|
308
|
+
throw duckdb::Exception("Invalid scan replacement result");
|
|
309
|
+
}
|
|
310
|
+
} catch (const duckdb::Exception &e) {
|
|
311
|
+
jsargs->error = duckdb::PreservedError(e);
|
|
312
|
+
} catch (const std::exception &e) {
|
|
313
|
+
jsargs->error = duckdb::PreservedError(e);
|
|
314
|
+
}
|
|
315
|
+
jsargs->done = true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
static duckdb::unique_ptr<duckdb::TableFunctionRef>
|
|
319
|
+
ScanReplacement(duckdb::ClientContext &context, const std::string &table_name, duckdb::ReplacementScanData *data) {
|
|
320
|
+
JSRSArgs jsargs;
|
|
321
|
+
jsargs.table = table_name;
|
|
322
|
+
((NodeReplacementScanData *)data)->rs.BlockingCall(&jsargs);
|
|
323
|
+
while (!jsargs.done) {
|
|
324
|
+
std::this_thread::yield();
|
|
325
|
+
}
|
|
326
|
+
if (jsargs.error) {
|
|
327
|
+
jsargs.error.Throw();
|
|
328
|
+
}
|
|
329
|
+
if (jsargs.function != "") {
|
|
330
|
+
auto table_function = duckdb::make_unique<duckdb::TableFunctionRef>();
|
|
331
|
+
std::vector<std::unique_ptr<duckdb::ParsedExpression>> children;
|
|
332
|
+
for (auto ¶m : jsargs.parameters) {
|
|
333
|
+
children.push_back(duckdb::make_unique<duckdb::ConstantExpression>(std::move(param)));
|
|
334
|
+
}
|
|
335
|
+
table_function->function =
|
|
336
|
+
duckdb::make_unique<duckdb::FunctionExpression>(jsargs.function, std::move(children));
|
|
337
|
+
return table_function;
|
|
338
|
+
}
|
|
339
|
+
return nullptr;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
struct RegisterRsTask : public Task {
|
|
343
|
+
RegisterRsTask(Database &database, duckdb_node_rs_function_t rs, Napi::Promise::Deferred deferred)
|
|
344
|
+
: Task(database), rs(std::move(rs)), deferred(deferred) {
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
void DoWork() override {
|
|
348
|
+
auto &database = Get<Database>();
|
|
349
|
+
if (database.database) {
|
|
350
|
+
database.database->instance->config.replacement_scans.emplace_back(
|
|
351
|
+
ScanReplacement, duckdb::make_unique<NodeReplacementScanData>(rs));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
void DoCallback() override {
|
|
356
|
+
deferred.Resolve(deferred.Env().Undefined());
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
duckdb_node_rs_function_t rs;
|
|
360
|
+
Napi::Promise::Deferred deferred;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
Napi::Value Database::RegisterReplacementScan(const Napi::CallbackInfo &info) {
|
|
364
|
+
auto env = info.Env();
|
|
365
|
+
auto deferred = Napi::Promise::Deferred::New(info.Env());
|
|
366
|
+
if (info.Length() < 1) {
|
|
367
|
+
Napi::TypeError::New(env, "Replacement scan callback expected").ThrowAsJavaScriptException();
|
|
368
|
+
return env.Null();
|
|
369
|
+
}
|
|
370
|
+
Napi::Function rs_callback = info[0].As<Napi::Function>();
|
|
371
|
+
auto rs = duckdb_node_rs_function_t::New(env, rs_callback, "duckdb_node_rs_" + (replacement_scan_count++), 0, 1,
|
|
372
|
+
nullptr, [](Napi::Env, void *, std::nullptr_t *ctx) {});
|
|
373
|
+
rs.Unref(env);
|
|
374
|
+
|
|
375
|
+
Schedule(info.Env(), duckdb::make_unique<RegisterRsTask>(*this, rs, deferred));
|
|
376
|
+
|
|
377
|
+
return deferred.Promise();
|
|
378
|
+
}
|
|
379
|
+
|
|
270
380
|
} // namespace node_duckdb
|
|
@@ -58,7 +58,20 @@ timestamp_t ICUCalendarAdd::Operation(timestamp_t timestamp, interval_t interval
|
|
|
58
58
|
calendar->setTime(udate, status);
|
|
59
59
|
|
|
60
60
|
// Add interval fields from lowest to highest
|
|
61
|
-
|
|
61
|
+
|
|
62
|
+
// Break units apart to avoid overflow
|
|
63
|
+
auto remaining = interval.micros / Interval::MICROS_PER_MSEC;
|
|
64
|
+
calendar->add(UCAL_MILLISECOND, remaining % Interval::MSECS_PER_SEC, status);
|
|
65
|
+
|
|
66
|
+
remaining /= Interval::MSECS_PER_SEC;
|
|
67
|
+
calendar->add(UCAL_SECOND, remaining % Interval::SECS_PER_MINUTE, status);
|
|
68
|
+
|
|
69
|
+
remaining /= Interval::SECS_PER_MINUTE;
|
|
70
|
+
calendar->add(UCAL_MINUTE, remaining % Interval::MINS_PER_HOUR, status);
|
|
71
|
+
|
|
72
|
+
remaining /= Interval::MINS_PER_HOUR;
|
|
73
|
+
calendar->add(UCAL_HOUR, remaining, status);
|
|
74
|
+
|
|
62
75
|
calendar->add(UCAL_DATE, interval.days, status);
|
|
63
76
|
calendar->add(UCAL_MONTH, interval.months, status);
|
|
64
77
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#ifndef DUCKDB_VERSION
|
|
2
|
-
#define DUCKDB_VERSION "0.6.2-
|
|
2
|
+
#define DUCKDB_VERSION "0.6.2-dev1070"
|
|
3
3
|
#endif
|
|
4
4
|
#ifndef DUCKDB_SOURCE_ID
|
|
5
|
-
#define DUCKDB_SOURCE_ID "
|
|
5
|
+
#define DUCKDB_SOURCE_ID "e0cff6a3dd"
|
|
6
6
|
#endif
|
|
7
7
|
#include "duckdb/function/table/system_functions.hpp"
|
|
8
8
|
#include "duckdb/main/database.hpp"
|
package/src/duckdb_node.hpp
CHANGED
|
@@ -56,6 +56,11 @@ struct Task {
|
|
|
56
56
|
|
|
57
57
|
class Connection;
|
|
58
58
|
|
|
59
|
+
struct JSRSArgs;
|
|
60
|
+
void DuckDBNodeRSLauncher(Napi::Env env, Napi::Function jsrs, std::nullptr_t *, JSRSArgs *data);
|
|
61
|
+
|
|
62
|
+
typedef Napi::TypedThreadSafeFunction<std::nullptr_t, JSRSArgs, DuckDBNodeRSLauncher> duckdb_node_rs_function_t;
|
|
63
|
+
|
|
59
64
|
class Database : public Napi::ObjectWrap<Database> {
|
|
60
65
|
public:
|
|
61
66
|
explicit Database(const Napi::CallbackInfo &info);
|
|
@@ -83,6 +88,7 @@ public:
|
|
|
83
88
|
Napi::Value Parallelize(const Napi::CallbackInfo &info);
|
|
84
89
|
Napi::Value Interrupt(const Napi::CallbackInfo &info);
|
|
85
90
|
Napi::Value Close(const Napi::CallbackInfo &info);
|
|
91
|
+
Napi::Value RegisterReplacementScan(const Napi::CallbackInfo &info);
|
|
86
92
|
|
|
87
93
|
public:
|
|
88
94
|
constexpr static int DUCKDB_NODEJS_ERROR = -1;
|
|
@@ -97,6 +103,7 @@ private:
|
|
|
97
103
|
static Napi::FunctionReference constructor;
|
|
98
104
|
Napi::Env env;
|
|
99
105
|
int64_t bytes_allocated = 0;
|
|
106
|
+
int replacement_scan_count = 0;
|
|
100
107
|
};
|
|
101
108
|
|
|
102
109
|
struct JSArgs;
|
|
@@ -199,6 +206,7 @@ public:
|
|
|
199
206
|
auto obj = T::constructor.New(args);
|
|
200
207
|
return Napi::ObjectWrap<T>::Unwrap(obj);
|
|
201
208
|
}
|
|
209
|
+
static duckdb::Value BindParameter(const Napi::Value source);
|
|
202
210
|
};
|
|
203
211
|
|
|
204
212
|
Napi::Array EncodeDataChunk(Napi::Env env, duckdb::DataChunk &chunk, bool with_types, bool with_data);
|
package/src/statement.cpp
CHANGED
|
@@ -85,51 +85,6 @@ Statement::~Statement() {
|
|
|
85
85
|
connection_ref = nullptr;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
// A Napi InstanceOf for Javascript Objects "Date" and "RegExp"
|
|
89
|
-
static bool OtherInstanceOf(Napi::Object source, const char *object_type) {
|
|
90
|
-
if (strcmp(object_type, "Date") == 0) {
|
|
91
|
-
return source.InstanceOf(source.Env().Global().Get(object_type).As<Napi::Function>());
|
|
92
|
-
} else if (strcmp(object_type, "RegExp") == 0) {
|
|
93
|
-
return source.InstanceOf(source.Env().Global().Get(object_type).As<Napi::Function>());
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
static duckdb::Value BindParameter(const Napi::Value source) {
|
|
100
|
-
if (source.IsString()) {
|
|
101
|
-
return duckdb::Value(source.As<Napi::String>().Utf8Value());
|
|
102
|
-
} else if (OtherInstanceOf(source.As<Napi::Object>(), "RegExp")) {
|
|
103
|
-
return duckdb::Value(source.ToString().Utf8Value());
|
|
104
|
-
} else if (source.IsNumber()) {
|
|
105
|
-
if (Utils::OtherIsInt(source.As<Napi::Number>())) {
|
|
106
|
-
return duckdb::Value::INTEGER(source.As<Napi::Number>().Int32Value());
|
|
107
|
-
} else {
|
|
108
|
-
return duckdb::Value::DOUBLE(source.As<Napi::Number>().DoubleValue());
|
|
109
|
-
}
|
|
110
|
-
} else if (source.IsBoolean()) {
|
|
111
|
-
return duckdb::Value::BOOLEAN(source.As<Napi::Boolean>().Value());
|
|
112
|
-
} else if (source.IsNull()) {
|
|
113
|
-
return duckdb::Value();
|
|
114
|
-
} else if (source.IsBuffer()) {
|
|
115
|
-
Napi::Buffer<char> buffer = source.As<Napi::Buffer<char>>();
|
|
116
|
-
return duckdb::Value::BLOB(std::string(buffer.Data(), buffer.Length()));
|
|
117
|
-
#if (NAPI_VERSION > 4)
|
|
118
|
-
} else if (source.IsDate()) {
|
|
119
|
-
const auto micros = int64_t(source.As<Napi::Date>().ValueOf()) * duckdb::Interval::MICROS_PER_MSEC;
|
|
120
|
-
if (micros % duckdb::Interval::MICROS_PER_DAY) {
|
|
121
|
-
return duckdb::Value::TIMESTAMP(duckdb::timestamp_t(micros));
|
|
122
|
-
} else {
|
|
123
|
-
const auto days = int32_t(micros / duckdb::Interval::MICROS_PER_DAY);
|
|
124
|
-
return duckdb::Value::DATE(duckdb::date_t(days));
|
|
125
|
-
}
|
|
126
|
-
#endif
|
|
127
|
-
} else if (source.IsObject()) {
|
|
128
|
-
return duckdb::Value(source.ToString().Utf8Value());
|
|
129
|
-
}
|
|
130
|
-
return duckdb::Value();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
88
|
static Napi::Value convert_col_val(Napi::Env &env, duckdb::Value dval, duckdb::LogicalTypeId id) {
|
|
134
89
|
Napi::Value value;
|
|
135
90
|
|
|
@@ -472,7 +427,7 @@ duckdb::unique_ptr<StatementParam> Statement::HandleArgs(const Napi::CallbackInf
|
|
|
472
427
|
if (p.IsUndefined()) {
|
|
473
428
|
continue;
|
|
474
429
|
}
|
|
475
|
-
params->params.push_back(BindParameter(p));
|
|
430
|
+
params->params.push_back(Utils::BindParameter(p));
|
|
476
431
|
}
|
|
477
432
|
return params;
|
|
478
433
|
}
|
package/src/utils.cpp
CHANGED
|
@@ -20,5 +20,47 @@ Napi::Value Utils::CreateError(Napi::Env env, std::string msg) {
|
|
|
20
20
|
|
|
21
21
|
return obj;
|
|
22
22
|
}
|
|
23
|
+
// A Napi InstanceOf for Javascript Objects "Date" and "RegExp"
|
|
24
|
+
static bool OtherInstanceOf(Napi::Object source, const char *object_type) {
|
|
25
|
+
if (strcmp(object_type, "Date") == 0) {
|
|
26
|
+
return source.InstanceOf(source.Env().Global().Get(object_type).As<Napi::Function>());
|
|
27
|
+
} else if (strcmp(object_type, "RegExp") == 0) {
|
|
28
|
+
return source.InstanceOf(source.Env().Global().Get(object_type).As<Napi::Function>());
|
|
29
|
+
}
|
|
23
30
|
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
duckdb::Value Utils::BindParameter(const Napi::Value source) {
|
|
34
|
+
if (source.IsString()) {
|
|
35
|
+
return duckdb::Value(source.As<Napi::String>().Utf8Value());
|
|
36
|
+
} else if (OtherInstanceOf(source.As<Napi::Object>(), "RegExp")) {
|
|
37
|
+
return duckdb::Value(source.ToString().Utf8Value());
|
|
38
|
+
} else if (source.IsNumber()) {
|
|
39
|
+
if (Utils::OtherIsInt(source.As<Napi::Number>())) {
|
|
40
|
+
return duckdb::Value::INTEGER(source.As<Napi::Number>().Int32Value());
|
|
41
|
+
} else {
|
|
42
|
+
return duckdb::Value::DOUBLE(source.As<Napi::Number>().DoubleValue());
|
|
43
|
+
}
|
|
44
|
+
} else if (source.IsBoolean()) {
|
|
45
|
+
return duckdb::Value::BOOLEAN(source.As<Napi::Boolean>().Value());
|
|
46
|
+
} else if (source.IsNull()) {
|
|
47
|
+
return duckdb::Value();
|
|
48
|
+
} else if (source.IsBuffer()) {
|
|
49
|
+
Napi::Buffer<char> buffer = source.As<Napi::Buffer<char>>();
|
|
50
|
+
return duckdb::Value::BLOB(std::string(buffer.Data(), buffer.Length()));
|
|
51
|
+
#if (NAPI_VERSION > 4)
|
|
52
|
+
} else if (source.IsDate()) {
|
|
53
|
+
const auto micros = int64_t(source.As<Napi::Date>().ValueOf()) * duckdb::Interval::MICROS_PER_MSEC;
|
|
54
|
+
if (micros % duckdb::Interval::MICROS_PER_DAY) {
|
|
55
|
+
return duckdb::Value::TIMESTAMP(duckdb::timestamp_t(micros));
|
|
56
|
+
} else {
|
|
57
|
+
const auto days = int32_t(micros / duckdb::Interval::MICROS_PER_DAY);
|
|
58
|
+
return duckdb::Value::DATE(duckdb::date_t(days));
|
|
59
|
+
}
|
|
60
|
+
#endif
|
|
61
|
+
} else if (source.IsObject()) {
|
|
62
|
+
return duckdb::Value(source.ToString().Utf8Value());
|
|
63
|
+
}
|
|
64
|
+
return duckdb::Value();
|
|
65
|
+
}
|
|
24
66
|
} // namespace node_duckdb
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import * as sqlite3 from "../lib/duckdb";
|
|
2
|
+
import type { TableData } from "../lib/duckdb";
|
|
3
|
+
import { expect } from "chai";
|
|
4
|
+
|
|
5
|
+
const replacementScan = (table: string) => {
|
|
6
|
+
if (table.endsWith(".csv")) {
|
|
7
|
+
return null;
|
|
8
|
+
} else {
|
|
9
|
+
return {
|
|
10
|
+
function: "read_csv_auto",
|
|
11
|
+
parameters: [`test/support/${table}.csv`],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const invalidTableFunction = (table: string) => {
|
|
17
|
+
return {
|
|
18
|
+
function: "foo",
|
|
19
|
+
parameters: ["bar"],
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const invalidResultType = (table: string) => {
|
|
24
|
+
return "hello" as unknown as sqlite3.ReplacementScanResult;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const invalidResultKeys = (table: string) => {
|
|
28
|
+
return {
|
|
29
|
+
foo: "foo",
|
|
30
|
+
bar: "bar",
|
|
31
|
+
} as unknown as sqlite3.ReplacementScanResult;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
describe("replacement scan", () => {
|
|
35
|
+
var db: sqlite3.Database;
|
|
36
|
+
describe("without replacement scan", () => {
|
|
37
|
+
before(function (done) {
|
|
38
|
+
db = new sqlite3.Database(":memory:", done);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("is not found", (done) => {
|
|
42
|
+
db.all(
|
|
43
|
+
"SELECT * FROM 'prepare' LIMIT 5",
|
|
44
|
+
function (err: null | Error, rows: TableData) {
|
|
45
|
+
expect(err).not.to.be.null;
|
|
46
|
+
expect(err!.message).to.match(
|
|
47
|
+
/Table with name prepare does not exist/
|
|
48
|
+
);
|
|
49
|
+
done();
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("with replacement scan", () => {
|
|
56
|
+
before((done) => {
|
|
57
|
+
db = new sqlite3.Database(":memory:", () => {
|
|
58
|
+
db.registerReplacementScan(replacementScan).then(done);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("is found when pattern matches", (done) => {
|
|
63
|
+
db.all(
|
|
64
|
+
"SELECT * FROM 'prepare' LIMIT 5",
|
|
65
|
+
function (err: null | Error, rows: TableData) {
|
|
66
|
+
expect(rows.length).to.equal(5);
|
|
67
|
+
done();
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("handles null response", (done) => {
|
|
73
|
+
db.all(
|
|
74
|
+
"SELECT * FROM 'test/support/prepare.csv' LIMIT 5",
|
|
75
|
+
function (err: null | Error, rows: TableData) {
|
|
76
|
+
expect(rows.length).to.equal(5);
|
|
77
|
+
done();
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("errors with invalid table", (done) => {
|
|
83
|
+
db.all(
|
|
84
|
+
"SELECT * FROM 'missing' LIMIT 5",
|
|
85
|
+
function (err: null | Error, rows: TableData) {
|
|
86
|
+
expect(err).not.to.be.null;
|
|
87
|
+
expect(err!.message).to.match(
|
|
88
|
+
/No files found that match the pattern "test\/support\/missing.csv"/
|
|
89
|
+
);
|
|
90
|
+
done();
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("with invalid replacement scan functions", () => {
|
|
97
|
+
it("does not crash with bad return values", (done) => {
|
|
98
|
+
db = new sqlite3.Database(":memory:", () => {
|
|
99
|
+
db.registerReplacementScan(invalidTableFunction).then(() => {
|
|
100
|
+
db.all(
|
|
101
|
+
"SELECT * FROM 'missing' LIMIT 5",
|
|
102
|
+
function (err: null | Error, rows: TableData) {
|
|
103
|
+
expect(err).not.to.be.null;
|
|
104
|
+
expect(err!.message).to.match(
|
|
105
|
+
/Table Function with name foo does not exist/
|
|
106
|
+
);
|
|
107
|
+
done();
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("does not crash with invalid response", (done) => {
|
|
115
|
+
db = new sqlite3.Database(":memory:", () => {
|
|
116
|
+
db.registerReplacementScan(invalidResultType).then(() => {
|
|
117
|
+
db.all(
|
|
118
|
+
"SELECT * FROM 'missing' LIMIT 5",
|
|
119
|
+
function (err: null | Error, rows: TableData) {
|
|
120
|
+
expect(err).not.to.be.null;
|
|
121
|
+
expect(err!.message).to.match(/Invalid scan replacement result/);
|
|
122
|
+
done();
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("does not crash with invalid response object", (done) => {
|
|
130
|
+
db = new sqlite3.Database(":memory:", () => {
|
|
131
|
+
db.registerReplacementScan(invalidResultKeys).then(() => {
|
|
132
|
+
db.all(
|
|
133
|
+
"SELECT * FROM 'missing' LIMIT 5",
|
|
134
|
+
function (err: null | Error, rows: TableData) {
|
|
135
|
+
expect(err).not.to.be.null;
|
|
136
|
+
expect(err!.message).to.match(/Expected parameter array/);
|
|
137
|
+
done();
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|