Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions doc/api/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,21 @@ be used to read `INTEGER` data using JavaScript `BigInt`s. This method has no
impact on database write operations where numbers and `BigInt`s are both
supported at all times.

### `statement.setReadNullAsUndefined(enabled)`

<!-- YAML
added:
-->

* `enabled` {boolean} Enables or disables returning SQL `NULL` values as
JavaScript `undefined` when reading from the database.

When reading from the database, SQLite `NULL` values are mapped to JavaScript
`null` by default. This method can be used to instead return `undefined` for
`NULL` values when materialising result rows. This setting only affects how
result rows are returned and does not impact values passed to user-defined
functions or aggregate functions.

### `statement.sourceSQL`

<!-- YAML
Expand Down
63 changes: 47 additions & 16 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ using v8::Value;
} \
} while (0)

#define SQLITE_VALUE_TO_JS(from, isolate, use_big_int_args, result, ...) \
#define SQLITE_VALUE_TO_JS(from, isolate, use_big_int_args, \
read_null_as_undef, result, ...) \
do { \
switch (sqlite3_##from##_type(__VA_ARGS__)) { \
case SQLITE_INTEGER: { \
Expand Down Expand Up @@ -101,7 +102,9 @@ using v8::Value;
break; \
} \
case SQLITE_NULL: { \
(result) = Null((isolate)); \
(result) = (read_null_as_undef) \
? Undefined((isolate)) \
: Null((isolate)); \
break; \
} \
case SQLITE_BLOB: { \
Expand Down Expand Up @@ -327,7 +330,7 @@ class CustomAggregate {
for (int i = 0; i < argc; ++i) {
sqlite3_value* value = argv[i];
MaybeLocal<Value> js_val;
SQLITE_VALUE_TO_JS(value, isolate, self->use_bigint_args_, js_val, value);
SQLITE_VALUE_TO_JS(value, isolate, self->use_bigint_args_, false, js_val, value);
if (js_val.IsEmpty()) {
// Ignore the SQLite error because a JavaScript exception is pending.
self->db_->SetIgnoreNextSQLiteError(true);
Expand Down Expand Up @@ -629,7 +632,7 @@ void UserDefinedFunction::xFunc(sqlite3_context* ctx,
for (int i = 0; i < argc; ++i) {
sqlite3_value* value = argv[i];
MaybeLocal<Value> js_val = MaybeLocal<Value>();
SQLITE_VALUE_TO_JS(value, isolate, self->use_bigint_args_, js_val, value);
SQLITE_VALUE_TO_JS(value, isolate, self->use_bigint_args_, false, js_val, value);
if (js_val.IsEmpty()) {
// Ignore the SQLite error because a JavaScript exception is pending.
self->db_->SetIgnoreNextSQLiteError(true);
Expand Down Expand Up @@ -2296,7 +2299,7 @@ bool StatementSync::BindValue(const Local<Value>& value, const int index) {

MaybeLocal<Value> StatementSync::ColumnToValue(const int column) {
return StatementExecutionHelper::ColumnToValue(
env(), statement_, column, use_big_ints_);
env(), statement_, column, use_big_ints_, read_null_as_undefined_);
}

MaybeLocal<Name> StatementSync::ColumnNameToName(const int column) {
Expand All @@ -2312,10 +2315,12 @@ MaybeLocal<Name> StatementSync::ColumnNameToName(const int column) {
MaybeLocal<Value> StatementExecutionHelper::ColumnToValue(Environment* env,
sqlite3_stmt* stmt,
const int column,
bool use_big_ints) {
bool use_big_ints,
bool read_null_as_undefined) {
Isolate* isolate = env->isolate();
MaybeLocal<Value> js_val = MaybeLocal<Value>();
SQLITE_VALUE_TO_JS(column, isolate, use_big_ints, js_val, stmt, column);
SQLITE_VALUE_TO_JS(
column, isolate, use_big_ints, read_null_as_undefined, js_val, stmt, column);
return js_val;
}

Expand All @@ -2337,12 +2342,13 @@ Maybe<void> ExtractRowValues(Environment* env,
sqlite3_stmt* stmt,
int num_cols,
bool use_big_ints,
bool read_null_as_undefined,
LocalVector<Value>* row_values) {
row_values->clear();
row_values->reserve(num_cols);
for (int i = 0; i < num_cols; ++i) {
Local<Value> val;
if (!StatementExecutionHelper::ColumnToValue(env, stmt, i, use_big_ints)
if (!StatementExecutionHelper::ColumnToValue(env, stmt, i, use_big_ints, read_null_as_undefined)
.ToLocal(&val)) {
return Nothing<void>();
}
Expand All @@ -2355,7 +2361,8 @@ MaybeLocal<Value> StatementExecutionHelper::All(Environment* env,
DatabaseSync* db,
sqlite3_stmt* stmt,
bool return_arrays,
bool use_big_ints) {
bool use_big_ints,
bool read_null_as_undefined) {
Isolate* isolate = env->isolate();
EscapableHandleScope scope(isolate);
int r;
Expand All @@ -2365,7 +2372,7 @@ MaybeLocal<Value> StatementExecutionHelper::All(Environment* env,
LocalVector<Name> row_keys(isolate);

while ((r = sqlite3_step(stmt)) == SQLITE_ROW) {
if (ExtractRowValues(env, stmt, num_cols, use_big_ints, &row_values)
if (ExtractRowValues(env, stmt, num_cols, use_big_ints, read_null_as_undefined, &row_values)
.IsNothing()) {
return MaybeLocal<Value>();
}
Expand Down Expand Up @@ -2470,7 +2477,8 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env,
DatabaseSync* db,
sqlite3_stmt* stmt,
bool return_arrays,
bool use_big_ints) {
bool use_big_ints,
bool read_null_as_undefined) {
Isolate* isolate = env->isolate();
EscapableHandleScope scope(isolate);
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt); });
Expand All @@ -2488,7 +2496,7 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env,
}

LocalVector<Value> row_values(isolate);
if (ExtractRowValues(env, stmt, num_cols, use_big_ints, &row_values)
if (ExtractRowValues(env, stmt, num_cols, use_big_ints, read_null_as_undefined, &row_values)
.IsNothing()) {
return MaybeLocal<Value>();
}
Expand Down Expand Up @@ -2534,7 +2542,8 @@ void StatementSync::All(const FunctionCallbackInfo<Value>& args) {
stmt->db_.get(),
stmt->statement_,
stmt->return_arrays_,
stmt->use_big_ints_)
stmt->use_big_ints_,
stmt->read_null_as_undefined_)
.ToLocal(&result)) {
args.GetReturnValue().Set(result);
}
Expand Down Expand Up @@ -2581,7 +2590,8 @@ void StatementSync::Get(const FunctionCallbackInfo<Value>& args) {
stmt->db_.get(),
stmt->statement_,
stmt->return_arrays_,
stmt->use_big_ints_)
stmt->use_big_ints_,
stmt->read_null_as_undefined_)
.ToLocal(&result)) {
args.GetReturnValue().Set(result);
}
Expand Down Expand Up @@ -2738,6 +2748,22 @@ void StatementSync::SetReadBigInts(const FunctionCallbackInfo<Value>& args) {
stmt->use_big_ints_ = args[0]->IsTrue();
}

void StatementSync::SetReadNullAsUndefined(const FunctionCallbackInfo<Value>& args) {
StatementSync* stmt;
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
Environment* env = Environment::GetCurrent(args);
THROW_AND_RETURN_ON_BAD_STATE(
env, stmt->IsFinalized(), "statement has been finalized");

if (!args[0]->IsBoolean()) {
THROW_ERR_INVALID_ARG_TYPE(
env->isolate(), "The \"readNullAsUndefined\" argument must be a boolean.");
return;
}

stmt->read_null_as_undefined_ = args[0]->IsTrue();
}

void StatementSync::SetReturnArrays(const FunctionCallbackInfo<Value>& args) {
StatementSync* stmt;
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
Expand Down Expand Up @@ -2940,7 +2966,8 @@ void SQLTagStore::Get(const FunctionCallbackInfo<Value>& args) {
stmt->db_.get(),
stmt->statement_,
stmt->return_arrays_,
stmt->use_big_ints_)
stmt->use_big_ints_,
stmt->read_null_as_undefined_)
.ToLocal(&result)) {
args.GetReturnValue().Set(result);
}
Expand Down Expand Up @@ -2980,7 +3007,8 @@ void SQLTagStore::All(const FunctionCallbackInfo<Value>& args) {
stmt->db_.get(),
stmt->statement_,
stmt->return_arrays_,
stmt->use_big_ints_)
stmt->use_big_ints_,
stmt->read_null_as_undefined_)
.ToLocal(&result)) {
args.GetReturnValue().Set(result);
}
Expand Down Expand Up @@ -3114,6 +3142,8 @@ Local<FunctionTemplate> StatementSync::GetConstructorTemplate(
isolate, tmpl, "setReadBigInts", StatementSync::SetReadBigInts);
SetProtoMethod(
isolate, tmpl, "setReturnArrays", StatementSync::SetReturnArrays);
SetProtoMethod(
isolate, tmpl, "setReadNullAsUndefined", StatementSync::SetReadNullAsUndefined);
env->set_sqlite_statement_sync_constructor_template(tmpl);
}
return tmpl;
Expand Down Expand Up @@ -3219,6 +3249,7 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) {
iter->stmt_->statement_,
num_cols,
iter->stmt_->use_big_ints_,
iter->stmt_->read_null_as_undefined_,
&row_values)
.IsNothing()) {
return;
Expand Down
24 changes: 21 additions & 3 deletions src/node_sqlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ class DatabaseOpenConfiguration {
return allow_unknown_named_params_;
}

inline void set_read_null_as_undefined(bool flag) {
read_null_as_undefined_ = flag;
}

inline bool get_read_null_as_undefined() const {
return read_null_as_undefined_;
}

inline void set_enable_defensive(bool flag) { defensive_ = flag; }

inline bool get_enable_defensive() const { return defensive_; }
Expand All @@ -79,6 +87,7 @@ class DatabaseOpenConfiguration {
bool return_arrays_ = false;
bool allow_bare_named_params_ = true;
bool allow_unknown_named_params_ = false;
bool read_null_as_undefined_ = false;
bool defensive_ = true;
};

Expand All @@ -93,7 +102,8 @@ class StatementExecutionHelper {
DatabaseSync* db,
sqlite3_stmt* stmt,
bool return_arrays,
bool use_big_ints);
bool use_big_ints,
bool read_null_as_undefined);
static v8::MaybeLocal<v8::Object> Run(Environment* env,
DatabaseSync* db,
sqlite3_stmt* stmt,
Expand All @@ -103,15 +113,17 @@ class StatementExecutionHelper {
static v8::MaybeLocal<v8::Value> ColumnToValue(Environment* env,
sqlite3_stmt* stmt,
const int column,
bool use_big_ints);
bool use_big_ints,
bool read_null_as_undefined);
static v8::MaybeLocal<v8::Name> ColumnNameToName(Environment* env,
sqlite3_stmt* stmt,
const int column);
static v8::MaybeLocal<v8::Value> Get(Environment* env,
DatabaseSync* db,
sqlite3_stmt* stmt,
bool return_arrays,
bool use_big_ints);
bool use_big_ints,
bool read_null_as_undefined);
};

class DatabaseSync : public BaseObject {
Expand Down Expand Up @@ -168,6 +180,9 @@ class DatabaseSync : public BaseObject {
bool allow_unknown_named_params() const {
return open_config_.get_allow_unknown_named_params();
}
bool read_null_as_undefined() const {
return open_config_.get_read_null_as_undefined();
}
sqlite3* Connection();

// In some situations, such as when using custom functions, it is possible
Expand Down Expand Up @@ -226,6 +241,8 @@ class StatementSync : public BaseObject {
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetReadBigInts(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetReturnArrays(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetReadNullAsUndefined(
const v8::FunctionCallbackInfo<v8::Value>& args);
v8::MaybeLocal<v8::Value> ColumnToValue(const int column);
v8::MaybeLocal<v8::Name> ColumnNameToName(const int column);
void Finalize();
Expand All @@ -242,6 +259,7 @@ class StatementSync : public BaseObject {
bool use_big_ints_;
bool allow_bare_named_params_;
bool allow_unknown_named_params_;
bool read_null_as_undefined_;
std::optional<std::map<std::string, std::string>> bare_named_params_;
bool BindParams(const v8::FunctionCallbackInfo<v8::Value>& args);
bool BindValue(const v8::Local<v8::Value>& value, const int index);
Expand Down
Loading