Skip to content
Draft
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
102 changes: 90 additions & 12 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,56 @@ using v8::Value;
} \
} while (0)

#define SQLITE_VALUE_TO_JS_READ(from, isolate, use_big_int_args, \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This much duplication is not acceptable.

read_null_as_undef, result, ...) \
do { \
switch (sqlite3_##from##_type(__VA_ARGS__)) { \
case SQLITE_INTEGER: { \
sqlite3_int64 val = sqlite3_##from##_int64(__VA_ARGS__); \
if ((use_big_int_args)) { \
(result) = BigInt::New((isolate), val); \
} else if (std::abs(val) <= kMaxSafeJsInteger) { \
(result) = Number::New((isolate), val); \
} else { \
THROW_ERR_OUT_OF_RANGE((isolate), \
"Value is too large to be represented as a " \
"JavaScript number: %" PRId64, \
val); \
} \
break; \
} \
case SQLITE_FLOAT: { \
(result) = \
Number::New((isolate), sqlite3_##from##_double(__VA_ARGS__)); \
break; \
} \
case SQLITE_TEXT: { \
const char* v = \
reinterpret_cast<const char*>(sqlite3_##from##_text(__VA_ARGS__)); \
(result) = String::NewFromUtf8((isolate), v).As<Value>(); \
break; \
} \
case SQLITE_NULL: { \
(result) = (read_null_as_undef) ? Undefined((isolate)) : Null((isolate)); \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main difference between SQLITE_VALUE_TO_JS_READ and SQLITE_VALUE_TO_JS is just the way of handling null values? Can we simplify the code?

break; \
} \
case SQLITE_BLOB: { \
size_t size = \
static_cast<size_t>(sqlite3_##from##_bytes(__VA_ARGS__)); \
auto data = reinterpret_cast<const uint8_t*>( \
sqlite3_##from##_blob(__VA_ARGS__)); \
auto store = ArrayBuffer::NewBackingStore( \
(isolate), size, BackingStoreInitializationMode::kUninitialized); \
memcpy(store->Data(), data, size); \
auto ab = ArrayBuffer::New((isolate), std::move(store)); \
(result) = Uint8Array::New(ab, 0, size); \
break; \
} \
default: \
UNREACHABLE("Bad SQLite value"); \
} \
} while (0)

namespace {
Local<DictionaryTemplate> getLazyIterTemplate(Environment* env) {
auto iter_template = env->iter_template();
Expand Down Expand Up @@ -2196,7 +2246,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 @@ -2212,10 +2262,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_READ(
column, isolate, use_big_ints, read_null_as_undefined, js_val, stmt, column);
return js_val;
}

Expand All @@ -2237,12 +2289,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 @@ -2255,7 +2308,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 @@ -2265,7 +2319,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 @@ -2370,7 +2424,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 @@ -2388,7 +2443,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 @@ -2434,7 +2489,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 @@ -2481,7 +2537,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 @@ -2638,6 +2695,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 @@ -2840,7 +2913,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 @@ -2880,7 +2954,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 @@ -3014,6 +3089,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 @@ -3119,6 +3196,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_ = false;
};

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