Skip to content

Commit 9909463

Browse files
committed
sqlite: add support for custom functions
This commit adds support to node:sqlite for defining custom functions that can be invoked from SQL. Fixes: #54349
1 parent 1d0738a commit 9909463

File tree

4 files changed

+667
-0
lines changed

4 files changed

+667
-0
lines changed

doc/api/sqlite.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,31 @@ This method allows one or more SQL statements to be executed without returning
132132
any results. This method is useful when executing SQL statements read from a
133133
file. This method is a wrapper around [`sqlite3_exec()`][].
134134

135+
### `database.function(name[, options], function)`
136+
137+
<!-- YAML
138+
added: REPLACEME
139+
-->
140+
141+
* `name` {string} The name of the SQLite function to create.
142+
* `options` {Object} Optional configuration settings for the function. The
143+
following properties are supported:
144+
* `deterministic` {boolean} If `true`, the [`SQLITE_DETERMINISTIC`][] flag is
145+
set on the created function. **Default:** `false`.
146+
* `directOnly` {boolean} If `true`, the [`SQLITE_DIRECTONLY`][] flag is set on
147+
the created function. **Default:** `false`.
148+
* `useBigIntArguments` {boolean} If `true`, integer arguments to `function`
149+
are converted to `BigInt`s. If `false`, integer arguments are passed as
150+
JavaScript numbers. **Default:** `false`.
151+
* `varargs` {boolean} If `true`, `function` can accept a variable number of
152+
arguments. If `false`, `function` must be invoked with exactly
153+
`function.length` arguments. **Default:** `false`.
154+
* `function` {Function} The JavaScript function to call when the SQLite
155+
function is invoked.
156+
157+
This method is used to create SQLite user-defined functions. This method is a
158+
wrapper around [`sqlite3_create_function_v2()`][].
159+
135160
### `database.open()`
136161

137162
<!-- YAML
@@ -432,8 +457,11 @@ The following constants are meant for use with [`database.applyChangeset()`](#da
432457
[SQL injection]: https://en.wikipedia.org/wiki/SQL_injection
433458
[`ATTACH DATABASE`]: https://www.sqlite.org/lang_attach.html
434459
[`PRAGMA foreign_keys`]: https://www.sqlite.org/pragma.html#pragma_foreign_keys
460+
[`SQLITE_DETERMINISTIC`]: https://www.sqlite.org/c3ref/c_deterministic.html
461+
[`SQLITE_DIRECTONLY`]: https://www.sqlite.org/c3ref/c_deterministic.html
435462
[`sqlite3_changes64()`]: https://www.sqlite.org/c3ref/changes.html
436463
[`sqlite3_close_v2()`]: https://www.sqlite.org/c3ref/close.html
464+
[`sqlite3_create_function_v2()`]: https://www.sqlite.org/c3ref/create_function.html
437465
[`sqlite3_exec()`]: https://www.sqlite.org/c3ref/exec.html
438466
[`sqlite3_expanded_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html
439467
[`sqlite3_last_insert_rowid()`]: https://www.sqlite.org/c3ref/last_insert_rowid.html

src/node_sqlite.cc

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ using v8::Function;
2727
using v8::FunctionCallback;
2828
using v8::FunctionCallbackInfo;
2929
using v8::FunctionTemplate;
30+
using v8::Global;
31+
using v8::Int32;
3032
using v8::Integer;
3133
using v8::Isolate;
3234
using v8::Local;
@@ -111,6 +113,123 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, const char* message) {
111113
}
112114
}
113115

116+
class UserDefinedFunction {
117+
public:
118+
explicit UserDefinedFunction(Environment* env,
119+
Local<Function> fn,
120+
bool use_bigint_args)
121+
: env_(env), fn_(env->isolate(), fn), use_bigint_args_(use_bigint_args) {}
122+
virtual ~UserDefinedFunction() {}
123+
124+
static void xFunc(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
125+
UserDefinedFunction* self =
126+
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
127+
Environment* env = self->env_;
128+
Isolate* isolate = env->isolate();
129+
auto recv = Undefined(isolate);
130+
auto fn = self->fn_.Get(isolate);
131+
std::vector<Local<Value>> js_argv;
132+
133+
for (int i = 0; i < argc; ++i) {
134+
sqlite3_value* value = argv[i];
135+
MaybeLocal<Value> js_val;
136+
137+
switch (sqlite3_value_type(value)) {
138+
case SQLITE_INTEGER: {
139+
sqlite3_int64 val = sqlite3_value_int64(value);
140+
if (self->use_bigint_args_) {
141+
js_val = BigInt::New(isolate, val);
142+
} else if (std::abs(val) <= kMaxSafeJsInteger) {
143+
js_val = Number::New(isolate, val);
144+
} else {
145+
THROW_ERR_OUT_OF_RANGE(isolate,
146+
"Value is too large to be represented as a "
147+
"JavaScript number: %" PRId64,
148+
val);
149+
return;
150+
}
151+
break;
152+
}
153+
case SQLITE_FLOAT:
154+
js_val = Number::New(isolate, sqlite3_value_double(value));
155+
break;
156+
case SQLITE_TEXT: {
157+
const char* v =
158+
reinterpret_cast<const char*>(sqlite3_value_text(value));
159+
js_val = String::NewFromUtf8(isolate, v).As<Value>();
160+
break;
161+
}
162+
case SQLITE_NULL:
163+
js_val = Null(isolate);
164+
break;
165+
case SQLITE_BLOB: {
166+
size_t size = static_cast<size_t>(sqlite3_value_bytes(value));
167+
auto data =
168+
reinterpret_cast<const uint8_t*>(sqlite3_value_blob(value));
169+
auto store = ArrayBuffer::NewBackingStore(isolate, size);
170+
memcpy(store->Data(), data, size);
171+
auto ab = ArrayBuffer::New(isolate, std::move(store));
172+
js_val = Uint8Array::New(ab, 0, size);
173+
break;
174+
}
175+
default:
176+
UNREACHABLE("Bad SQLite value");
177+
}
178+
179+
Local<Value> local;
180+
if (!js_val.ToLocal(&local)) {
181+
return;
182+
}
183+
184+
js_argv.emplace_back(local);
185+
}
186+
187+
MaybeLocal<Value> retval =
188+
fn->Call(env->context(), recv, argc, js_argv.data());
189+
Local<Value> result;
190+
if (!retval.ToLocal(&result)) {
191+
return;
192+
}
193+
194+
if (result->IsUndefined() || result->IsNull()) {
195+
sqlite3_result_null(ctx);
196+
} else if (result->IsNumber()) {
197+
sqlite3_result_double(ctx, result.As<Number>()->Value());
198+
} else if (result->IsString()) {
199+
Utf8Value val(isolate, result.As<String>());
200+
sqlite3_result_text(ctx, *val, val.length(), SQLITE_TRANSIENT);
201+
} else if (result->IsUint8Array()) {
202+
ArrayBufferViewContents<uint8_t> buf(result);
203+
sqlite3_result_blob(ctx, buf.data(), buf.length(), SQLITE_TRANSIENT);
204+
} else if (result->IsBigInt()) {
205+
bool lossless;
206+
int64_t as_int = result.As<BigInt>()->Int64Value(&lossless);
207+
if (!lossless) {
208+
sqlite3_result_error(ctx, "BigInt value is too large for SQLite", -1);
209+
return;
210+
}
211+
sqlite3_result_int64(ctx, as_int);
212+
} else if (result->IsPromise()) {
213+
sqlite3_result_error(
214+
ctx, "Asynchronous user-defined functions are not supported", -1);
215+
} else {
216+
sqlite3_result_error(
217+
ctx,
218+
"Returned JavaScript value cannot be converted to a SQLite value",
219+
-1);
220+
}
221+
}
222+
223+
static void xDestroy(void* self) {
224+
delete static_cast<UserDefinedFunction*>(self);
225+
}
226+
227+
private:
228+
Environment* env_;
229+
Global<Function> fn_;
230+
bool use_bigint_args_;
231+
};
232+
114233
DatabaseSync::DatabaseSync(Environment* env,
115234
Local<Object> object,
116235
DatabaseOpenConfiguration&& open_config,
@@ -363,6 +482,151 @@ void DatabaseSync::Exec(const FunctionCallbackInfo<Value>& args) {
363482
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
364483
}
365484

485+
void DatabaseSync::CustomFunction(const FunctionCallbackInfo<Value>& args) {
486+
DatabaseSync* db;
487+
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
488+
Environment* env = Environment::GetCurrent(args);
489+
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
490+
491+
if (!args[0]->IsString()) {
492+
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
493+
"The \"name\" argument must be a string.");
494+
return;
495+
}
496+
497+
int fn_index = args.Length() < 3 ? 1 : 2;
498+
bool use_bigint_args = false;
499+
bool varargs = false;
500+
bool deterministic = false;
501+
bool direct_only = false;
502+
503+
if (fn_index > 1) {
504+
if (!args[1]->IsObject()) {
505+
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
506+
"The \"options\" argument must be an object.");
507+
return;
508+
}
509+
510+
Local<Object> options = args[1].As<Object>();
511+
Local<Value> use_bigint_args_v;
512+
if (!options
513+
->Get(env->context(),
514+
FIXED_ONE_BYTE_STRING(env->isolate(), "useBigIntArguments"))
515+
.ToLocal(&use_bigint_args_v)) {
516+
return;
517+
}
518+
519+
if (!use_bigint_args_v->IsUndefined()) {
520+
if (!use_bigint_args_v->IsBoolean()) {
521+
THROW_ERR_INVALID_ARG_TYPE(
522+
env->isolate(),
523+
"The \"options.useBigIntArguments\" argument must be a boolean.");
524+
return;
525+
}
526+
use_bigint_args = use_bigint_args_v.As<Boolean>()->Value();
527+
}
528+
529+
Local<Value> varargs_v;
530+
if (!options
531+
->Get(env->context(),
532+
FIXED_ONE_BYTE_STRING(env->isolate(), "varargs"))
533+
.ToLocal(&varargs_v)) {
534+
return;
535+
}
536+
537+
if (!varargs_v->IsUndefined()) {
538+
if (!varargs_v->IsBoolean()) {
539+
THROW_ERR_INVALID_ARG_TYPE(
540+
env->isolate(),
541+
"The \"options.varargs\" argument must be a boolean.");
542+
return;
543+
}
544+
varargs = varargs_v.As<Boolean>()->Value();
545+
}
546+
547+
Local<Value> deterministic_v;
548+
if (!options
549+
->Get(env->context(),
550+
FIXED_ONE_BYTE_STRING(env->isolate(), "deterministic"))
551+
.ToLocal(&deterministic_v)) {
552+
return;
553+
}
554+
555+
if (!deterministic_v->IsUndefined()) {
556+
if (!deterministic_v->IsBoolean()) {
557+
THROW_ERR_INVALID_ARG_TYPE(
558+
env->isolate(),
559+
"The \"options.deterministic\" argument must be a boolean.");
560+
return;
561+
}
562+
deterministic = deterministic_v.As<Boolean>()->Value();
563+
}
564+
565+
Local<Value> direct_only_v;
566+
if (!options
567+
->Get(env->context(),
568+
FIXED_ONE_BYTE_STRING(env->isolate(), "directOnly"))
569+
.ToLocal(&direct_only_v)) {
570+
return;
571+
}
572+
573+
if (!direct_only_v->IsUndefined()) {
574+
if (!direct_only_v->IsBoolean()) {
575+
THROW_ERR_INVALID_ARG_TYPE(
576+
env->isolate(),
577+
"The \"options.directOnly\" argument must be a boolean.");
578+
return;
579+
}
580+
direct_only = direct_only_v.As<Boolean>()->Value();
581+
}
582+
}
583+
584+
if (!args[fn_index]->IsFunction()) {
585+
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
586+
"The \"function\" argument must be a function.");
587+
return;
588+
}
589+
590+
Utf8Value name(env->isolate(), args[0].As<String>());
591+
Local<Function> fn = args[fn_index].As<Function>();
592+
593+
int argc = 0;
594+
if (varargs) {
595+
argc = -1;
596+
} else {
597+
Local<Value> js_len;
598+
if (!fn->Get(env->context(),
599+
FIXED_ONE_BYTE_STRING(env->isolate(), "length"))
600+
.ToLocal(&js_len)) {
601+
return;
602+
}
603+
argc = js_len.As<Int32>()->Value();
604+
}
605+
606+
UserDefinedFunction* user_data =
607+
new UserDefinedFunction(env, fn, use_bigint_args);
608+
int text_rep = SQLITE_UTF8;
609+
610+
if (deterministic) {
611+
text_rep |= SQLITE_DETERMINISTIC;
612+
}
613+
614+
if (direct_only) {
615+
text_rep |= SQLITE_DIRECTONLY;
616+
}
617+
618+
int r = sqlite3_create_function_v2(db->connection_,
619+
*name,
620+
argc,
621+
text_rep,
622+
user_data,
623+
UserDefinedFunction::xFunc,
624+
nullptr,
625+
nullptr,
626+
UserDefinedFunction::xDestroy);
627+
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
628+
}
629+
366630
void DatabaseSync::CreateSession(const FunctionCallbackInfo<Value>& args) {
367631
std::string table;
368632
std::string db_name = "main";
@@ -1308,6 +1572,7 @@ static void Initialize(Local<Object> target,
13081572
SetProtoMethod(isolate, db_tmpl, "close", DatabaseSync::Close);
13091573
SetProtoMethod(isolate, db_tmpl, "prepare", DatabaseSync::Prepare);
13101574
SetProtoMethod(isolate, db_tmpl, "exec", DatabaseSync::Exec);
1575+
SetProtoMethod(isolate, db_tmpl, "function", DatabaseSync::CustomFunction);
13111576
SetProtoMethod(
13121577
isolate, db_tmpl, "createSession", DatabaseSync::CreateSession);
13131578
SetProtoMethod(

src/node_sqlite.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class DatabaseSync : public BaseObject {
5656
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
5757
static void Prepare(const v8::FunctionCallbackInfo<v8::Value>& args);
5858
static void Exec(const v8::FunctionCallbackInfo<v8::Value>& args);
59+
static void CustomFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
5960
static void CreateSession(const v8::FunctionCallbackInfo<v8::Value>& args);
6061
static void ApplyChangeset(const v8::FunctionCallbackInfo<v8::Value>& args);
6162
void FinalizeStatements();

0 commit comments

Comments
 (0)