Skip to content

Commit b62aed9

Browse files
committed
sqlite: add statement.setReadNullAsUndefined, add options.readNullAsUndefined
1 parent f248a9e commit b62aed9

4 files changed

Lines changed: 108 additions & 27 deletions

File tree

src/node_sqlite.cc

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ using v8::Value;
6868
} \
6969
} while (0)
7070

71-
#define SQLITE_VALUE_TO_JS(from, isolate, use_big_int_args, use_null_as_undefined_, result, ...) \
71+
#define SQLITE_VALUE_TO_JS(from, isolate, \
72+
use_big_int_args, \
73+
use_null_as_undefined_, \
74+
result, ...) \
7275
do { \
7376
switch (sqlite3_##from##_type(__VA_ARGS__)) { \
7477
case SQLITE_INTEGER: { \
@@ -317,7 +320,12 @@ class CustomAggregate {
317320
for (int i = 0; i < argc; ++i) {
318321
sqlite3_value* value = argv[i];
319322
MaybeLocal<Value> js_val;
320-
SQLITE_VALUE_TO_JS(value, isolate, self->use_bigint_args_, self->use_null_as_undefined_args_, js_val, value);
323+
SQLITE_VALUE_TO_JS(value,
324+
isolate,
325+
self->use_bigint_args_,
326+
self->use_null_as_undefined_args_,
327+
js_val,
328+
value);
321329
if (js_val.IsEmpty()) {
322330
// Ignore the SQLite error because a JavaScript exception is pending.
323331
self->db_->SetIgnoreNextSQLiteError(true);
@@ -624,7 +632,12 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
624632
for (int i = 0; i < argc; ++i) {
625633
sqlite3_value* value = argv[i];
626634
MaybeLocal<Value> js_val = MaybeLocal<Value>();
627-
SQLITE_VALUE_TO_JS(value, isolate, self->use_bigint_args_, self->use_null_as_undefined_args_, js_val, value);
635+
SQLITE_VALUE_TO_JS(value,
636+
isolate,
637+
self->use_bigint_args_,
638+
self->use_null_as_undefined_args_,
639+
js_val,
640+
value);
628641
if (js_val.IsEmpty()) {
629642
// Ignore the SQLite error because a JavaScript exception is pending.
630643
self->db_->SetIgnoreNextSQLiteError(true);
@@ -1002,7 +1015,8 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
10021015
R"(The "options.readNullAsUndefined" argument must be a boolean.)");
10031016
return;
10041017
}
1005-
open_config.set_use_null_as_undefined(read_null_as_undefined_v.As<Boolean>()->Value());
1018+
open_config.set_use_null_as_undefined(read_null_as_undefined_v
1019+
.As<Boolean>()->Value());
10061020
}
10071021
}
10081022

@@ -1260,7 +1274,11 @@ void DatabaseSync::CustomFunction(const FunctionCallbackInfo<Value>& args) {
12601274
}
12611275

12621276
UserDefinedFunction* user_data =
1263-
new UserDefinedFunction(env, fn, db, use_bigint_args, use_null_as_undefined_args);
1277+
new UserDefinedFunction(env,
1278+
fn,
1279+
db,
1280+
use_bigint_args,
1281+
use_null_as_undefined_args);
12641282
int text_rep = SQLITE_UTF8;
12651283

12661284
if (deterministic) {
@@ -1474,14 +1492,15 @@ void DatabaseSync::AggregateFunction(const FunctionCallbackInfo<Value>& args) {
14741492
*name,
14751493
argc,
14761494
text_rep,
1477-
new CustomAggregate(env,
1478-
db,
1479-
use_bigint_args,
1480-
use_null_as_undefined_args,
1481-
start_v,
1482-
stepFunction,
1483-
inverseFunc,
1484-
resultFunction),
1495+
new CustomAggregate(
1496+
env,
1497+
db,
1498+
use_bigint_args,
1499+
use_null_as_undefined_args,
1500+
start_v,
1501+
stepFunction,
1502+
inverseFunc,
1503+
resultFunction),
14851504
CustomAggregate::xStep,
14861505
CustomAggregate::xFinal,
14871506
xValue,
@@ -2045,8 +2064,13 @@ bool StatementSync::BindValue(const Local<Value>& value, const int index) {
20452064
MaybeLocal<Value> StatementSync::ColumnToValue(const int column) {
20462065
Isolate* isolate = env()->isolate();
20472066
MaybeLocal<Value> js_val = MaybeLocal<Value>();
2048-
SQLITE_VALUE_TO_JS(
2049-
column, isolate, use_big_ints_, use_null_as_undefined_, js_val, statement_, column);
2067+
SQLITE_VALUE_TO_JS(column,
2068+
isolate,
2069+
use_big_ints_,
2070+
use_null_as_undefined_,
2071+
js_val,
2072+
statement_,
2073+
column);
20502074
return js_val;
20512075
}
20522076

@@ -2424,7 +2448,8 @@ void StatementSync::SetReadBigInts(const FunctionCallbackInfo<Value>& args) {
24242448
stmt->use_big_ints_ = args[0]->IsTrue();
24252449
}
24262450

2427-
void StatementSync::SetReadNullAsUndefined(const FunctionCallbackInfo<Value>& args) {
2451+
void StatementSync::SetReadNullAsUndefined(
2452+
const FunctionCallbackInfo<Value>& args) {
24282453
StatementSync* stmt;
24292454
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
24302455
Environment* env = Environment::GetCurrent(args);
@@ -2433,11 +2458,12 @@ void StatementSync::SetReadNullAsUndefined(const FunctionCallbackInfo<Value>& ar
24332458

24342459
if (!args[0]->IsBoolean()) {
24352460
THROW_ERR_INVALID_ARG_TYPE(
2436-
env->isolate(), "The \"readNullAsUndefined\" argument must be a boolean.");
2461+
env->isolate(),
2462+
"The \"readNullAsUndefined\" argument must be a boolean.");
24372463
return;
24382464
}
24392465

2440-
stmt->use_null_as_undefined_= args[0]->IsTrue();
2466+
stmt->use_null_as_undefined_ = args[0]->IsTrue();
24412467
}
24422468

24432469
void StatementSync::SetReturnArrays(const FunctionCallbackInfo<Value>& args) {
@@ -2510,7 +2536,8 @@ Local<FunctionTemplate> StatementSync::GetConstructorTemplate(
25102536
"setAllowUnknownNamedParameters",
25112537
StatementSync::SetAllowUnknownNamedParameters);
25122538
SetProtoMethod(
2513-
isolate, tmpl, "setReadNullAsUndefined", StatementSync::SetReadNullAsUndefined);
2539+
isolate, tmpl, "setReadNullAsUndefined",
2540+
StatementSync::SetReadNullAsUndefined);
25142541
SetProtoMethod(
25152542
isolate, tmpl, "setReadBigInts", StatementSync::SetReadBigInts);
25162543
SetProtoMethod(

src/node_sqlite.h

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,13 @@ class DatabaseOpenConfiguration {
4343

4444
inline bool get_use_big_ints() const { return use_big_ints_; }
4545

46-
inline void set_use_null_as_undefined(bool flag) { use_null_as_undefined_ = flag; }
46+
inline void set_use_null_as_undefined(bool flag) {
47+
use_null_as_undefined_ = flag;
48+
}
4749

48-
inline bool get_use_null_as_undefined() const { return use_null_as_undefined_; }
50+
inline bool get_use_null_as_undefined() const {
51+
return use_null_as_undefined_;
52+
}
4953

5054
inline void set_return_arrays(bool flag) { return_arrays_ = flag; }
5155

@@ -115,8 +119,12 @@ class DatabaseSync : public BaseObject {
115119
void FinalizeBackups();
116120
void UntrackStatement(StatementSync* statement);
117121
bool IsOpen();
118-
bool use_big_ints() const { return open_config_.get_use_big_ints(); }
119-
bool use_null_as_undefined() const { return open_config_.get_use_null_as_undefined(); }
122+
bool use_big_ints() const {
123+
return open_config_.get_use_big_ints();
124+
}
125+
bool use_null_as_undefined() const {
126+
return open_config_.get_use_null_as_undefined();
127+
}
120128
bool return_arrays() const { return open_config_.get_return_arrays(); }
121129
bool allow_bare_named_params() const {
122130
return open_config_.get_allow_bare_named_params();
@@ -179,7 +187,8 @@ class StatementSync : public BaseObject {
179187
static void SetAllowUnknownNamedParameters(
180188
const v8::FunctionCallbackInfo<v8::Value>& args);
181189
static void SetReadBigInts(const v8::FunctionCallbackInfo<v8::Value>& args);
182-
static void SetReadNullAsUndefined(const v8::FunctionCallbackInfo<v8::Value>& args);
190+
static void SetReadNullAsUndefined(
191+
const v8::FunctionCallbackInfo<v8::Value>& args);
183192
static void SetReturnArrays(const v8::FunctionCallbackInfo<v8::Value>& args);
184193
void Finalize();
185194
bool IsFinalized();

test/parallel/test-sqlite-database-sync.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,26 @@ suite('DatabaseSync() constructor', () => {
255255
t.assert.deepStrictEqual(query.get(), [1, 'one']);
256256
});
257257

258+
test('null to undefined in array rows with setReadNullAsUndefined()', (t) => {
259+
const db = new DatabaseSync(nextDb());
260+
t.after(() => { db.close(); });
261+
const setup = db.exec(`
262+
CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT;
263+
INSERT INTO data (key, val) VALUES (1, NULL);
264+
`);
265+
t.assert.strictEqual(setup, undefined);
266+
267+
const query = db.prepare('SELECT key, val FROM data WHERE key = 1');
268+
t.assert.deepStrictEqual(query.get(), { __proto__: null, key: 1, val: null });
269+
270+
query.setReturnArrays(true);
271+
query.setReadNullAsUndefined(true);
272+
t.assert.deepStrictEqual(query.get(), [1, undefined]);
273+
274+
query.setReturnArrays(false);
275+
t.assert.deepStrictEqual(query.get(), { __proto__: null, key: 1, val: undefined });
276+
});
277+
258278
test('throws if options.allowBareNamedParameters is provided but is not a boolean', (t) => {
259279
t.assert.throws(() => {
260280
new DatabaseSync('foo', { allowBareNamedParameters: 42 });

test/parallel/test-sqlite-statement-sync.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,13 @@ suite('StatementSync.prototype.setReadNullAsUndefined', () => {
287287

288288
const query = db.prepare('SELECT is_null FROM DATA');
289289
t.assert.deepStrictEqual(query.get(), { __proto__: null, is_null: null });
290-
t.assert.strictEqual(query.setReadNullAsUndefined(true), undefined);
290+
t.assert.strictEqual(Object.hasOwn(query.get(), 'is_null'), true);
291+
t.assert.strictEqual('is_null' in query.get(), true);
292+
293+
query.setReadNullAsUndefined(true);
291294
t.assert.deepStrictEqual(query.get(), { __proto__: null, is_null: undefined });
292-
t.assert.strictEqual(query.setReadNullAsUndefined(false), undefined);
293-
t.assert.deepStrictEqual(query.get(), { __proto__: null, is_null: null });
295+
t.assert.strictEqual(Object.hasOwn(query.get(), 'is_null'), true);
296+
t.assert.strictEqual('is_null' in query.get(), true);
294297
});
295298

296299
test('does not affect non-null values', (t) => {
@@ -304,6 +307,8 @@ suite('StatementSync.prototype.setReadNullAsUndefined', () => {
304307

305308
const query = db.prepare('SELECT is_not_null FROM DATA');
306309
t.assert.deepStrictEqual(query.get(), { __proto__: null, is_not_null: 'This is not null' });
310+
t.assert.strictEqual(
311+
Object.hasOwn(query.get(), 'is_not_null'), true);
307312
t.assert.strictEqual(query.setReadNullAsUndefined(true), undefined);
308313
t.assert.deepStrictEqual(query.get(), { __proto__: null, is_not_null: 'This is not null' });
309314
});
@@ -432,6 +437,26 @@ suite('StatementSync.prototype.get() with array output', () => {
432437
t.assert.deepStrictEqual(query.get(), { __proto__: null, key: 1, val: 'one' });
433438
});
434439

440+
test('null to undefined in array rows with setReadNullAsUndefined()', (t) => {
441+
const db = new DatabaseSync(nextDb());
442+
t.after(() => { db.close(); });
443+
const setup = db.exec(`
444+
CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT;
445+
INSERT INTO data (key, val) VALUES (1, NULL);
446+
`);
447+
t.assert.strictEqual(setup, undefined);
448+
449+
const query = db.prepare('SELECT key, val FROM data WHERE key = 1');
450+
t.assert.deepStrictEqual(query.get(), { __proto__: null, key: 1, val: null });
451+
452+
query.setReturnArrays(true);
453+
query.setReadNullAsUndefined(true);
454+
t.assert.deepStrictEqual(query.get(), [1, undefined]);
455+
456+
query.setReturnArrays(false);
457+
t.assert.deepStrictEqual(query.get(), { __proto__: null, key: 1, val: undefined });
458+
});
459+
435460
test('returns array rows with BigInts when both flags are set', (t) => {
436461
const expected = [1n, 9007199254740992n];
437462
const db = new DatabaseSync(nextDb());

0 commit comments

Comments
 (0)