mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
Double-quoted string (DQS) literals are not allowed by the SQL standard, which defines that text enclosed in double quotes is to be interpreted as an identifier only and never as a string literal. Nevertheless, for historical reasons, SQLite allows double-quoted string literals in some cases, which leads to inconsistent behavior and subtle bugs. This commit changes the behavior of the built-in Node.js API for SQLite such that the DQS misfeature is disabled by default. This is recommended by the developers of SQLite. Users can explicitly enable DQS for compatibility with legacy database schemas if necessary. PR-URL: https://github.com/nodejs/node/pull/55297 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Jake Yuesong Li <jake.yuesong@gmail.com>
849 lines
28 KiB
C++
849 lines
28 KiB
C++
#include "node_sqlite.h"
|
|
#include "base_object-inl.h"
|
|
#include "debug_utils-inl.h"
|
|
#include "env-inl.h"
|
|
#include "memory_tracker-inl.h"
|
|
#include "node.h"
|
|
#include "node_errors.h"
|
|
#include "node_mem-inl.h"
|
|
#include "sqlite3.h"
|
|
#include "util-inl.h"
|
|
|
|
#include <cinttypes>
|
|
|
|
namespace node {
|
|
namespace sqlite {
|
|
|
|
using v8::Array;
|
|
using v8::ArrayBuffer;
|
|
using v8::BigInt;
|
|
using v8::Boolean;
|
|
using v8::ConstructorBehavior;
|
|
using v8::Context;
|
|
using v8::DontDelete;
|
|
using v8::Exception;
|
|
using v8::FunctionCallback;
|
|
using v8::FunctionCallbackInfo;
|
|
using v8::FunctionTemplate;
|
|
using v8::Integer;
|
|
using v8::Isolate;
|
|
using v8::Local;
|
|
using v8::LocalVector;
|
|
using v8::MaybeLocal;
|
|
using v8::Name;
|
|
using v8::Null;
|
|
using v8::Number;
|
|
using v8::Object;
|
|
using v8::SideEffectType;
|
|
using v8::String;
|
|
using v8::Uint8Array;
|
|
using v8::Value;
|
|
|
|
#define CHECK_ERROR_OR_THROW(isolate, db, expr, expected, ret) \
|
|
do { \
|
|
int r_ = (expr); \
|
|
if (r_ != (expected)) { \
|
|
THROW_ERR_SQLITE_ERROR((isolate), (db)); \
|
|
return (ret); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define THROW_AND_RETURN_ON_BAD_STATE(env, condition, msg) \
|
|
do { \
|
|
if ((condition)) { \
|
|
node::THROW_ERR_INVALID_STATE((env), (msg)); \
|
|
return; \
|
|
} \
|
|
} while (0)
|
|
|
|
inline Local<Object> CreateSQLiteError(Isolate* isolate, const char* message) {
|
|
Local<String> js_msg = String::NewFromUtf8(isolate, message).ToLocalChecked();
|
|
Local<Object> e = Exception::Error(js_msg)
|
|
->ToObject(isolate->GetCurrentContext())
|
|
.ToLocalChecked();
|
|
e->Set(isolate->GetCurrentContext(),
|
|
OneByteString(isolate, "code"),
|
|
OneByteString(isolate, "ERR_SQLITE_ERROR"))
|
|
.Check();
|
|
return e;
|
|
}
|
|
|
|
inline Local<Object> CreateSQLiteError(Isolate* isolate, sqlite3* db) {
|
|
int errcode = sqlite3_extended_errcode(db);
|
|
const char* errstr = sqlite3_errstr(errcode);
|
|
const char* errmsg = sqlite3_errmsg(db);
|
|
Local<Object> e = CreateSQLiteError(isolate, errmsg);
|
|
e->Set(isolate->GetCurrentContext(),
|
|
OneByteString(isolate, "errcode"),
|
|
Integer::New(isolate, errcode))
|
|
.Check();
|
|
e->Set(isolate->GetCurrentContext(),
|
|
OneByteString(isolate, "errstr"),
|
|
String::NewFromUtf8(isolate, errstr).ToLocalChecked())
|
|
.Check();
|
|
return e;
|
|
}
|
|
|
|
inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, sqlite3* db) {
|
|
isolate->ThrowException(CreateSQLiteError(isolate, db));
|
|
}
|
|
|
|
inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, const char* message) {
|
|
isolate->ThrowException(CreateSQLiteError(isolate, message));
|
|
}
|
|
|
|
DatabaseSync::DatabaseSync(Environment* env,
|
|
Local<Object> object,
|
|
Local<String> location,
|
|
bool open,
|
|
bool enable_foreign_keys_on_open,
|
|
bool enable_dqs_on_open)
|
|
: BaseObject(env, object) {
|
|
MakeWeak();
|
|
node::Utf8Value utf8_location(env->isolate(), location);
|
|
location_ = utf8_location.ToString();
|
|
connection_ = nullptr;
|
|
enable_foreign_keys_on_open_ = enable_foreign_keys_on_open;
|
|
enable_dqs_on_open_ = enable_dqs_on_open;
|
|
|
|
if (open) {
|
|
Open();
|
|
}
|
|
}
|
|
|
|
DatabaseSync::~DatabaseSync() {
|
|
if (IsOpen()) {
|
|
FinalizeStatements();
|
|
sqlite3_close_v2(connection_);
|
|
connection_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void DatabaseSync::MemoryInfo(MemoryTracker* tracker) const {
|
|
tracker->TrackField("location", location_);
|
|
}
|
|
|
|
bool DatabaseSync::Open() {
|
|
if (IsOpen()) {
|
|
node::THROW_ERR_INVALID_STATE(env(), "database is already open");
|
|
return false;
|
|
}
|
|
|
|
// TODO(cjihrig): Support additional flags.
|
|
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
|
|
int r = sqlite3_open_v2(location_.c_str(), &connection_, flags, nullptr);
|
|
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
|
|
|
|
r = sqlite3_db_config(connection_,
|
|
SQLITE_DBCONFIG_DQS_DML,
|
|
static_cast<int>(enable_dqs_on_open_),
|
|
nullptr);
|
|
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
|
|
r = sqlite3_db_config(connection_,
|
|
SQLITE_DBCONFIG_DQS_DDL,
|
|
static_cast<int>(enable_dqs_on_open_),
|
|
nullptr);
|
|
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
|
|
|
|
int foreign_keys_enabled;
|
|
r = sqlite3_db_config(connection_,
|
|
SQLITE_DBCONFIG_ENABLE_FKEY,
|
|
static_cast<int>(enable_foreign_keys_on_open_),
|
|
&foreign_keys_enabled);
|
|
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
|
|
CHECK_EQ(foreign_keys_enabled, enable_foreign_keys_on_open_);
|
|
|
|
return true;
|
|
}
|
|
|
|
void DatabaseSync::FinalizeStatements() {
|
|
for (auto stmt : statements_) {
|
|
stmt->Finalize();
|
|
}
|
|
|
|
statements_.clear();
|
|
}
|
|
|
|
void DatabaseSync::UntrackStatement(StatementSync* statement) {
|
|
auto it = statements_.find(statement);
|
|
if (it != statements_.end()) {
|
|
statements_.erase(it);
|
|
}
|
|
}
|
|
|
|
inline bool DatabaseSync::IsOpen() {
|
|
return connection_ != nullptr;
|
|
}
|
|
|
|
inline sqlite3* DatabaseSync::Connection() {
|
|
return connection_;
|
|
}
|
|
|
|
void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
if (!args.IsConstructCall()) {
|
|
THROW_ERR_CONSTRUCT_CALL_REQUIRED(env);
|
|
return;
|
|
}
|
|
|
|
if (!args[0]->IsString()) {
|
|
node::THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
|
|
"The \"path\" argument must be a string.");
|
|
return;
|
|
}
|
|
|
|
bool open = true;
|
|
bool enable_foreign_keys = true;
|
|
bool enable_dqs = false;
|
|
|
|
if (args.Length() > 1) {
|
|
if (!args[1]->IsObject()) {
|
|
node::THROW_ERR_INVALID_ARG_TYPE(
|
|
env->isolate(), "The \"options\" argument must be an object.");
|
|
return;
|
|
}
|
|
|
|
Local<Object> options = args[1].As<Object>();
|
|
Local<String> open_string = FIXED_ONE_BYTE_STRING(env->isolate(), "open");
|
|
Local<Value> open_v;
|
|
if (!options->Get(env->context(), open_string).ToLocal(&open_v)) {
|
|
return;
|
|
}
|
|
if (!open_v->IsUndefined()) {
|
|
if (!open_v->IsBoolean()) {
|
|
node::THROW_ERR_INVALID_ARG_TYPE(
|
|
env->isolate(), "The \"options.open\" argument must be a boolean.");
|
|
return;
|
|
}
|
|
open = open_v.As<Boolean>()->Value();
|
|
}
|
|
|
|
Local<String> enable_foreign_keys_string =
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "enableForeignKeyConstraints");
|
|
Local<Value> enable_foreign_keys_v;
|
|
if (!options->Get(env->context(), enable_foreign_keys_string)
|
|
.ToLocal(&enable_foreign_keys_v)) {
|
|
return;
|
|
}
|
|
if (!enable_foreign_keys_v->IsUndefined()) {
|
|
if (!enable_foreign_keys_v->IsBoolean()) {
|
|
node::THROW_ERR_INVALID_ARG_TYPE(
|
|
env->isolate(),
|
|
"The \"options.enableForeignKeyConstraints\" argument must be a "
|
|
"boolean.");
|
|
return;
|
|
}
|
|
enable_foreign_keys = enable_foreign_keys_v.As<Boolean>()->Value();
|
|
}
|
|
|
|
Local<String> enable_dqs_string = FIXED_ONE_BYTE_STRING(
|
|
env->isolate(), "enableDoubleQuotedStringLiterals");
|
|
Local<Value> enable_dqs_v;
|
|
if (!options->Get(env->context(), enable_dqs_string)
|
|
.ToLocal(&enable_dqs_v)) {
|
|
return;
|
|
}
|
|
if (!enable_dqs_v->IsUndefined()) {
|
|
if (!enable_dqs_v->IsBoolean()) {
|
|
node::THROW_ERR_INVALID_ARG_TYPE(
|
|
env->isolate(),
|
|
"The \"options.enableDoubleQuotedStringLiterals\" argument must be "
|
|
"a boolean.");
|
|
return;
|
|
}
|
|
enable_dqs = enable_dqs_v.As<Boolean>()->Value();
|
|
}
|
|
}
|
|
|
|
new DatabaseSync(env,
|
|
args.This(),
|
|
args[0].As<String>(),
|
|
open,
|
|
enable_foreign_keys,
|
|
enable_dqs);
|
|
}
|
|
|
|
void DatabaseSync::Open(const FunctionCallbackInfo<Value>& args) {
|
|
DatabaseSync* db;
|
|
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
|
|
db->Open();
|
|
}
|
|
|
|
void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
|
|
DatabaseSync* db;
|
|
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
|
|
Environment* env = Environment::GetCurrent(args);
|
|
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
|
|
db->FinalizeStatements();
|
|
int r = sqlite3_close_v2(db->connection_);
|
|
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
|
|
db->connection_ = nullptr;
|
|
}
|
|
|
|
void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
|
|
DatabaseSync* db;
|
|
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
|
|
Environment* env = Environment::GetCurrent(args);
|
|
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
|
|
|
|
if (!args[0]->IsString()) {
|
|
node::THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
|
|
"The \"sql\" argument must be a string.");
|
|
return;
|
|
}
|
|
|
|
node::Utf8Value sql(env->isolate(), args[0].As<String>());
|
|
sqlite3_stmt* s = nullptr;
|
|
int r = sqlite3_prepare_v2(db->connection_, *sql, -1, &s, 0);
|
|
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
|
|
BaseObjectPtr<StatementSync> stmt = StatementSync::Create(env, db, s);
|
|
db->statements_.insert(stmt.get());
|
|
args.GetReturnValue().Set(stmt->object());
|
|
}
|
|
|
|
void DatabaseSync::Exec(const FunctionCallbackInfo<Value>& args) {
|
|
DatabaseSync* db;
|
|
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
|
|
Environment* env = Environment::GetCurrent(args);
|
|
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
|
|
|
|
if (!args[0]->IsString()) {
|
|
node::THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
|
|
"The \"sql\" argument must be a string.");
|
|
return;
|
|
}
|
|
|
|
node::Utf8Value sql(env->isolate(), args[0].As<String>());
|
|
int r = sqlite3_exec(db->connection_, *sql, nullptr, nullptr, nullptr);
|
|
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
|
|
}
|
|
|
|
StatementSync::StatementSync(Environment* env,
|
|
Local<Object> object,
|
|
DatabaseSync* db,
|
|
sqlite3_stmt* stmt)
|
|
: BaseObject(env, object) {
|
|
MakeWeak();
|
|
db_ = db;
|
|
statement_ = stmt;
|
|
// In the future, some of these options could be set at the database
|
|
// connection level and inherited by statements to reduce boilerplate.
|
|
use_big_ints_ = false;
|
|
allow_bare_named_params_ = true;
|
|
bare_named_params_ = std::nullopt;
|
|
}
|
|
|
|
StatementSync::~StatementSync() {
|
|
if (!IsFinalized()) {
|
|
db_->UntrackStatement(this);
|
|
Finalize();
|
|
}
|
|
}
|
|
|
|
void StatementSync::Finalize() {
|
|
sqlite3_finalize(statement_);
|
|
statement_ = nullptr;
|
|
}
|
|
|
|
inline bool StatementSync::IsFinalized() {
|
|
return statement_ == nullptr;
|
|
}
|
|
|
|
bool StatementSync::BindParams(const FunctionCallbackInfo<Value>& args) {
|
|
int r = sqlite3_clear_bindings(statement_);
|
|
CHECK_ERROR_OR_THROW(
|
|
env()->isolate(), db_->Connection(), r, SQLITE_OK, false);
|
|
|
|
int anon_idx = 1;
|
|
int anon_start = 0;
|
|
|
|
if (args[0]->IsObject() && !args[0]->IsUint8Array()) {
|
|
Local<Object> obj = args[0].As<Object>();
|
|
Local<Context> context = obj->GetIsolate()->GetCurrentContext();
|
|
Local<Array> keys;
|
|
if (!obj->GetOwnPropertyNames(context).ToLocal(&keys)) {
|
|
return false;
|
|
}
|
|
|
|
if (allow_bare_named_params_ && !bare_named_params_.has_value()) {
|
|
bare_named_params_.emplace();
|
|
int param_count = sqlite3_bind_parameter_count(statement_);
|
|
// Parameter indexing starts at one.
|
|
for (int i = 1; i <= param_count; ++i) {
|
|
const char* name = sqlite3_bind_parameter_name(statement_, i);
|
|
if (name == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
auto bare_name = std::string(name + 1);
|
|
auto full_name = std::string(name);
|
|
auto insertion = bare_named_params_->insert({bare_name, full_name});
|
|
if (insertion.second == false) {
|
|
auto existing_full_name = (*insertion.first).second;
|
|
if (full_name != existing_full_name) {
|
|
node::THROW_ERR_INVALID_STATE(
|
|
env(),
|
|
"Cannot create bare named parameter '%s' because of "
|
|
"conflicting names '%s' and '%s'.",
|
|
bare_name,
|
|
existing_full_name,
|
|
full_name);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t len = keys->Length();
|
|
for (uint32_t j = 0; j < len; j++) {
|
|
Local<Value> key;
|
|
if (!keys->Get(context, j).ToLocal(&key)) {
|
|
return false;
|
|
}
|
|
|
|
node::Utf8Value utf8_key(env()->isolate(), key);
|
|
int r = sqlite3_bind_parameter_index(statement_, *utf8_key);
|
|
if (r == 0) {
|
|
if (allow_bare_named_params_) {
|
|
auto lookup = bare_named_params_->find(std::string(*utf8_key));
|
|
if (lookup != bare_named_params_->end()) {
|
|
r = sqlite3_bind_parameter_index(statement_,
|
|
lookup->second.c_str());
|
|
}
|
|
}
|
|
|
|
if (r == 0) {
|
|
node::THROW_ERR_INVALID_STATE(
|
|
env(), "Unknown named parameter '%s'", *utf8_key);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Local<Value> value;
|
|
if (!obj->Get(context, key).ToLocal(&value)) {
|
|
return false;
|
|
}
|
|
|
|
if (!BindValue(value, r)) {
|
|
return false;
|
|
}
|
|
}
|
|
anon_start++;
|
|
}
|
|
|
|
for (int i = anon_start; i < args.Length(); ++i) {
|
|
while (sqlite3_bind_parameter_name(statement_, anon_idx) != nullptr) {
|
|
anon_idx++;
|
|
}
|
|
|
|
if (!BindValue(args[i], anon_idx)) {
|
|
return false;
|
|
}
|
|
|
|
anon_idx++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool StatementSync::BindValue(const Local<Value>& value, const int index) {
|
|
// SQLite only supports a subset of JavaScript types. Some JS types such as
|
|
// functions don't make sense to support. Other JS types such as booleans and
|
|
// Dates could be supported by converting them to numbers. However, there
|
|
// would not be a good way to read the values back from SQLite with the
|
|
// original type.
|
|
int r;
|
|
if (value->IsNumber()) {
|
|
double val = value.As<Number>()->Value();
|
|
r = sqlite3_bind_double(statement_, index, val);
|
|
} else if (value->IsString()) {
|
|
node::Utf8Value val(env()->isolate(), value.As<String>());
|
|
r = sqlite3_bind_text(
|
|
statement_, index, *val, val.length(), SQLITE_TRANSIENT);
|
|
} else if (value->IsNull()) {
|
|
r = sqlite3_bind_null(statement_, index);
|
|
} else if (value->IsUint8Array()) {
|
|
ArrayBufferViewContents<uint8_t> buf(value);
|
|
r = sqlite3_bind_blob(
|
|
statement_, index, buf.data(), buf.length(), SQLITE_TRANSIENT);
|
|
} else if (value->IsBigInt()) {
|
|
bool lossless;
|
|
int64_t as_int = value.As<BigInt>()->Int64Value(&lossless);
|
|
if (!lossless) {
|
|
node::THROW_ERR_INVALID_ARG_VALUE(env(),
|
|
"BigInt value is too large to bind.");
|
|
return false;
|
|
}
|
|
r = sqlite3_bind_int64(statement_, index, as_int);
|
|
} else {
|
|
node::THROW_ERR_INVALID_ARG_TYPE(
|
|
env()->isolate(),
|
|
"Provided value cannot be bound to SQLite parameter %d.",
|
|
index);
|
|
return false;
|
|
}
|
|
|
|
CHECK_ERROR_OR_THROW(
|
|
env()->isolate(), db_->Connection(), r, SQLITE_OK, false);
|
|
return true;
|
|
}
|
|
|
|
MaybeLocal<Value> StatementSync::ColumnToValue(const int column) {
|
|
switch (sqlite3_column_type(statement_, column)) {
|
|
case SQLITE_INTEGER: {
|
|
sqlite3_int64 value = sqlite3_column_int64(statement_, column);
|
|
if (use_big_ints_) {
|
|
return BigInt::New(env()->isolate(), value);
|
|
} else if (std::abs(value) <= kMaxSafeJsInteger) {
|
|
return Number::New(env()->isolate(), value);
|
|
} else {
|
|
THROW_ERR_OUT_OF_RANGE(env()->isolate(),
|
|
"The value of column %d is too large to be "
|
|
"represented as a JavaScript number: %" PRId64,
|
|
column,
|
|
value);
|
|
return MaybeLocal<Value>();
|
|
}
|
|
}
|
|
case SQLITE_FLOAT:
|
|
return Number::New(env()->isolate(),
|
|
sqlite3_column_double(statement_, column));
|
|
case SQLITE_TEXT: {
|
|
const char* value = reinterpret_cast<const char*>(
|
|
sqlite3_column_text(statement_, column));
|
|
return String::NewFromUtf8(env()->isolate(), value).As<Value>();
|
|
}
|
|
case SQLITE_NULL:
|
|
return Null(env()->isolate());
|
|
case SQLITE_BLOB: {
|
|
size_t size =
|
|
static_cast<size_t>(sqlite3_column_bytes(statement_, column));
|
|
auto data = reinterpret_cast<const uint8_t*>(
|
|
sqlite3_column_blob(statement_, column));
|
|
auto store = ArrayBuffer::NewBackingStore(env()->isolate(), size);
|
|
memcpy(store->Data(), data, size);
|
|
auto ab = ArrayBuffer::New(env()->isolate(), std::move(store));
|
|
return Uint8Array::New(ab, 0, size);
|
|
}
|
|
default:
|
|
UNREACHABLE("Bad SQLite column type");
|
|
}
|
|
}
|
|
|
|
MaybeLocal<Name> StatementSync::ColumnNameToName(const int column) {
|
|
const char* col_name = sqlite3_column_name(statement_, column);
|
|
if (col_name == nullptr) {
|
|
node::THROW_ERR_INVALID_STATE(
|
|
env(), "Cannot get name of column %d", column);
|
|
return MaybeLocal<Name>();
|
|
}
|
|
|
|
return String::NewFromUtf8(env()->isolate(), col_name).As<Name>();
|
|
}
|
|
|
|
void StatementSync::MemoryInfo(MemoryTracker* tracker) const {}
|
|
|
|
void StatementSync::All(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");
|
|
Isolate* isolate = env->isolate();
|
|
int r = sqlite3_reset(stmt->statement_);
|
|
CHECK_ERROR_OR_THROW(isolate, stmt->db_->Connection(), r, SQLITE_OK, void());
|
|
|
|
if (!stmt->BindParams(args)) {
|
|
return;
|
|
}
|
|
|
|
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); });
|
|
int num_cols = sqlite3_column_count(stmt->statement_);
|
|
LocalVector<Value> rows(isolate);
|
|
while ((r = sqlite3_step(stmt->statement_)) == SQLITE_ROW) {
|
|
LocalVector<Name> row_keys(isolate);
|
|
row_keys.reserve(num_cols);
|
|
LocalVector<Value> row_values(isolate);
|
|
row_values.reserve(num_cols);
|
|
|
|
for (int i = 0; i < num_cols; ++i) {
|
|
Local<Name> key;
|
|
if (!stmt->ColumnNameToName(i).ToLocal(&key)) return;
|
|
Local<Value> val;
|
|
if (!stmt->ColumnToValue(i).ToLocal(&val)) return;
|
|
row_keys.emplace_back(key);
|
|
row_values.emplace_back(val);
|
|
}
|
|
|
|
Local<Object> row = Object::New(
|
|
isolate, Null(isolate), row_keys.data(), row_values.data(), num_cols);
|
|
rows.emplace_back(row);
|
|
}
|
|
|
|
CHECK_ERROR_OR_THROW(
|
|
isolate, stmt->db_->Connection(), r, SQLITE_DONE, void());
|
|
args.GetReturnValue().Set(Array::New(isolate, rows.data(), rows.size()));
|
|
}
|
|
|
|
void StatementSync::Get(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");
|
|
Isolate* isolate = env->isolate();
|
|
int r = sqlite3_reset(stmt->statement_);
|
|
CHECK_ERROR_OR_THROW(isolate, stmt->db_->Connection(), r, SQLITE_OK, void());
|
|
|
|
if (!stmt->BindParams(args)) {
|
|
return;
|
|
}
|
|
|
|
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); });
|
|
r = sqlite3_step(stmt->statement_);
|
|
if (r == SQLITE_DONE) return;
|
|
if (r != SQLITE_ROW) {
|
|
THROW_ERR_SQLITE_ERROR(isolate, stmt->db_->Connection());
|
|
return;
|
|
}
|
|
|
|
int num_cols = sqlite3_column_count(stmt->statement_);
|
|
if (num_cols == 0) {
|
|
return;
|
|
}
|
|
|
|
LocalVector<Name> keys(isolate);
|
|
keys.reserve(num_cols);
|
|
LocalVector<Value> values(isolate);
|
|
values.reserve(num_cols);
|
|
|
|
for (int i = 0; i < num_cols; ++i) {
|
|
Local<Name> key;
|
|
if (!stmt->ColumnNameToName(i).ToLocal(&key)) return;
|
|
Local<Value> val;
|
|
if (!stmt->ColumnToValue(i).ToLocal(&val)) return;
|
|
keys.emplace_back(key);
|
|
values.emplace_back(val);
|
|
}
|
|
|
|
Local<Object> result =
|
|
Object::New(isolate, Null(isolate), keys.data(), values.data(), num_cols);
|
|
|
|
args.GetReturnValue().Set(result);
|
|
}
|
|
|
|
void StatementSync::Run(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");
|
|
int r = sqlite3_reset(stmt->statement_);
|
|
CHECK_ERROR_OR_THROW(
|
|
env->isolate(), stmt->db_->Connection(), r, SQLITE_OK, void());
|
|
|
|
if (!stmt->BindParams(args)) {
|
|
return;
|
|
}
|
|
|
|
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); });
|
|
r = sqlite3_step(stmt->statement_);
|
|
if (r != SQLITE_ROW && r != SQLITE_DONE) {
|
|
THROW_ERR_SQLITE_ERROR(env->isolate(), stmt->db_->Connection());
|
|
return;
|
|
}
|
|
|
|
Local<Object> result = Object::New(env->isolate());
|
|
Local<String> last_insert_rowid_string =
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "lastInsertRowid");
|
|
Local<String> changes_string =
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "changes");
|
|
sqlite3_int64 last_insert_rowid =
|
|
sqlite3_last_insert_rowid(stmt->db_->Connection());
|
|
sqlite3_int64 changes = sqlite3_changes64(stmt->db_->Connection());
|
|
Local<Value> last_insert_rowid_val;
|
|
Local<Value> changes_val;
|
|
|
|
if (stmt->use_big_ints_) {
|
|
last_insert_rowid_val = BigInt::New(env->isolate(), last_insert_rowid);
|
|
changes_val = BigInt::New(env->isolate(), changes);
|
|
} else {
|
|
last_insert_rowid_val = Number::New(env->isolate(), last_insert_rowid);
|
|
changes_val = Number::New(env->isolate(), changes);
|
|
}
|
|
|
|
if (result
|
|
->Set(env->context(), last_insert_rowid_string, last_insert_rowid_val)
|
|
.IsNothing() ||
|
|
result->Set(env->context(), changes_string, changes_val).IsNothing()) {
|
|
return;
|
|
}
|
|
|
|
args.GetReturnValue().Set(result);
|
|
}
|
|
|
|
void StatementSync::SourceSQLGetter(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");
|
|
Local<String> sql;
|
|
if (!String::NewFromUtf8(env->isolate(), sqlite3_sql(stmt->statement_))
|
|
.ToLocal(&sql)) {
|
|
return;
|
|
}
|
|
args.GetReturnValue().Set(sql);
|
|
}
|
|
|
|
void StatementSync::ExpandedSQLGetter(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");
|
|
|
|
// sqlite3_expanded_sql may return nullptr without producing an error code.
|
|
char* expanded = sqlite3_expanded_sql(stmt->statement_);
|
|
if (expanded == nullptr) {
|
|
return THROW_ERR_SQLITE_ERROR(
|
|
env->isolate(), "Expanded SQL text would exceed configured limits");
|
|
}
|
|
auto maybe_expanded = String::NewFromUtf8(env->isolate(), expanded);
|
|
sqlite3_free(expanded);
|
|
Local<String> result;
|
|
if (!maybe_expanded.ToLocal(&result)) {
|
|
return;
|
|
}
|
|
args.GetReturnValue().Set(result);
|
|
}
|
|
|
|
void StatementSync::SetAllowBareNamedParameters(
|
|
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()) {
|
|
node::THROW_ERR_INVALID_ARG_TYPE(
|
|
env->isolate(),
|
|
"The \"allowBareNamedParameters\" argument must be a boolean.");
|
|
return;
|
|
}
|
|
|
|
stmt->allow_bare_named_params_ = args[0]->IsTrue();
|
|
}
|
|
|
|
void StatementSync::SetReadBigInts(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()) {
|
|
node::THROW_ERR_INVALID_ARG_TYPE(
|
|
env->isolate(), "The \"readBigInts\" argument must be a boolean.");
|
|
return;
|
|
}
|
|
|
|
stmt->use_big_ints_ = args[0]->IsTrue();
|
|
}
|
|
|
|
void IllegalConstructor(const FunctionCallbackInfo<Value>& args) {
|
|
node::THROW_ERR_ILLEGAL_CONSTRUCTOR(Environment::GetCurrent(args));
|
|
}
|
|
|
|
static inline void SetSideEffectFreeGetter(
|
|
Isolate* isolate,
|
|
Local<FunctionTemplate> class_template,
|
|
Local<String> name,
|
|
FunctionCallback fn) {
|
|
Local<FunctionTemplate> getter =
|
|
FunctionTemplate::New(isolate,
|
|
fn,
|
|
Local<Value>(),
|
|
v8::Signature::New(isolate, class_template),
|
|
/* length */ 0,
|
|
ConstructorBehavior::kThrow,
|
|
SideEffectType::kHasNoSideEffect);
|
|
class_template->InstanceTemplate()->SetAccessorProperty(
|
|
name, getter, Local<FunctionTemplate>(), DontDelete);
|
|
}
|
|
|
|
Local<FunctionTemplate> StatementSync::GetConstructorTemplate(
|
|
Environment* env) {
|
|
Local<FunctionTemplate> tmpl =
|
|
env->sqlite_statement_sync_constructor_template();
|
|
if (tmpl.IsEmpty()) {
|
|
Isolate* isolate = env->isolate();
|
|
tmpl = NewFunctionTemplate(isolate, IllegalConstructor);
|
|
tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "StatementSync"));
|
|
tmpl->InstanceTemplate()->SetInternalFieldCount(
|
|
StatementSync::kInternalFieldCount);
|
|
SetProtoMethod(isolate, tmpl, "all", StatementSync::All);
|
|
SetProtoMethod(isolate, tmpl, "get", StatementSync::Get);
|
|
SetProtoMethod(isolate, tmpl, "run", StatementSync::Run);
|
|
SetSideEffectFreeGetter(isolate,
|
|
tmpl,
|
|
FIXED_ONE_BYTE_STRING(isolate, "sourceSQL"),
|
|
StatementSync::SourceSQLGetter);
|
|
SetSideEffectFreeGetter(isolate,
|
|
tmpl,
|
|
FIXED_ONE_BYTE_STRING(isolate, "expandedSQL"),
|
|
StatementSync::ExpandedSQLGetter);
|
|
SetProtoMethod(isolate,
|
|
tmpl,
|
|
"setAllowBareNamedParameters",
|
|
StatementSync::SetAllowBareNamedParameters);
|
|
SetProtoMethod(
|
|
isolate, tmpl, "setReadBigInts", StatementSync::SetReadBigInts);
|
|
env->set_sqlite_statement_sync_constructor_template(tmpl);
|
|
}
|
|
return tmpl;
|
|
}
|
|
|
|
BaseObjectPtr<StatementSync> StatementSync::Create(Environment* env,
|
|
DatabaseSync* db,
|
|
sqlite3_stmt* stmt) {
|
|
Local<Object> obj;
|
|
if (!GetConstructorTemplate(env)
|
|
->InstanceTemplate()
|
|
->NewInstance(env->context())
|
|
.ToLocal(&obj)) {
|
|
return BaseObjectPtr<StatementSync>();
|
|
}
|
|
|
|
return MakeBaseObject<StatementSync>(env, obj, db, stmt);
|
|
}
|
|
|
|
static void Initialize(Local<Object> target,
|
|
Local<Value> unused,
|
|
Local<Context> context,
|
|
void* priv) {
|
|
Environment* env = Environment::GetCurrent(context);
|
|
Isolate* isolate = env->isolate();
|
|
Local<FunctionTemplate> db_tmpl =
|
|
NewFunctionTemplate(isolate, DatabaseSync::New);
|
|
db_tmpl->InstanceTemplate()->SetInternalFieldCount(
|
|
DatabaseSync::kInternalFieldCount);
|
|
|
|
SetProtoMethod(isolate, db_tmpl, "open", DatabaseSync::Open);
|
|
SetProtoMethod(isolate, db_tmpl, "close", DatabaseSync::Close);
|
|
SetProtoMethod(isolate, db_tmpl, "prepare", DatabaseSync::Prepare);
|
|
SetProtoMethod(isolate, db_tmpl, "exec", DatabaseSync::Exec);
|
|
SetConstructorFunction(context, target, "DatabaseSync", db_tmpl);
|
|
SetConstructorFunction(context,
|
|
target,
|
|
"StatementSync",
|
|
StatementSync::GetConstructorTemplate(env));
|
|
}
|
|
|
|
} // namespace sqlite
|
|
} // namespace node
|
|
|
|
NODE_BINDING_CONTEXT_AWARE_INTERNAL(sqlite, node::sqlite::Initialize)
|