blob: cb7855a2ad170752fe25f87d4f808b4ae56f6495 [file] [log] [blame] [edit]
#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::Context;
using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
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<Value> CreateSQLiteError(Isolate* isolate, sqlite3* db) {
int errcode = sqlite3_extended_errcode(db);
const char* errstr = sqlite3_errstr(errcode);
const char* errmsg = sqlite3_errmsg(db);
Local<String> js_msg = String::NewFromUtf8(isolate, errmsg).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();
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));
}
DatabaseSync::DatabaseSync(Environment* env,
Local<Object> object,
Local<String> location,
bool open)
: BaseObject(env, object) {
MakeWeak();
node::Utf8Value utf8_location(env->isolate(), location);
location_ = utf8_location.ToString();
connection_ = nullptr;
if (open) {
Open();
}
}
DatabaseSync::~DatabaseSync() {
sqlite3_close_v2(connection_);
connection_ = nullptr;
}
void DatabaseSync::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("location", location_);
}
bool DatabaseSync::Open() {
if (connection_ != nullptr) {
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);
return true;
}
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;
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();
}
}
new DatabaseSync(env, args.This(), args[0].As<String>(), open);
}
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->connection_ == nullptr, "database is not open");
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->connection_ == nullptr, "database is not open");
if (!args[0]->IsString()) {
node::THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
"The \"sql\" argument must be a string.");
return;
}
auto sql = node::Utf8Value(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->connection_, s);
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->connection_ == nullptr, "database is not open");
if (!args[0]->IsString()) {
node::THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
"The \"sql\" argument must be a string.");
return;
}
auto sql = node::Utf8Value(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,
sqlite3* 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() {
sqlite3_finalize(statement_);
statement_ = nullptr;
}
bool StatementSync::BindParams(const FunctionCallbackInfo<Value>& args) {
int r = sqlite3_clear_bindings(statement_);
CHECK_ERROR_OR_THROW(env()->isolate(), db_, 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;
}
auto utf8_key = node::Utf8Value(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()) {
auto val = node::Utf8Value(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_, r, SQLITE_OK, false);
return true;
}
Local<Value> StatementSync::ColumnToValue(const int column) {
switch (sqlite3_column_type(statement_, column)) {
case SQLITE_INTEGER:
if (use_big_ints_) {
return BigInt::New(env()->isolate(),
sqlite3_column_int64(statement_, column));
}
// Fall through.
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));
Local<Value> val;
if (!String::NewFromUtf8(env()->isolate(), value).ToLocal(&val)) {
return Local<Value>();
}
return val;
}
case SQLITE_NULL:
return v8::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");
}
}
Local<Value> StatementSync::ColumnNameToValue(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 Local<Value>();
}
Local<String> key;
if (!String::NewFromUtf8(env()->isolate(), col_name).ToLocal(&key)) {
return Local<Value>();
}
return key;
}
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);
int r = sqlite3_reset(stmt->statement_);
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_, r, SQLITE_OK, void());
if (!stmt->BindParams(args)) {
return;
}
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); });
int num_cols = sqlite3_column_count(stmt->statement_);
std::vector<Local<Value>> rows;
while ((r = sqlite3_step(stmt->statement_)) == SQLITE_ROW) {
Local<Object> row = Object::New(env->isolate());
for (int i = 0; i < num_cols; ++i) {
Local<Value> key = stmt->ColumnNameToValue(i);
Local<Value> val = stmt->ColumnToValue(i);
if (row->Set(env->context(), key, val).IsNothing()) {
return;
}
}
rows.emplace_back(row);
}
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_, r, SQLITE_DONE, void());
args.GetReturnValue().Set(
Array::New(env->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);
int r = sqlite3_reset(stmt->statement_);
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_, 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_);
return;
}
int num_cols = sqlite3_column_count(stmt->statement_);
if (num_cols == 0) {
return;
}
Local<Object> result = Object::New(env->isolate());
for (int i = 0; i < num_cols; ++i) {
Local<Value> key = stmt->ColumnNameToValue(i);
Local<Value> val = stmt->ColumnToValue(i);
if (result->Set(env->context(), key, val).IsNothing()) {
return;
}
}
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);
int r = sqlite3_reset(stmt->statement_);
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_, 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_);
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_);
sqlite3_int64 changes = sqlite3_changes64(stmt->db_);
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::SourceSQL(const FunctionCallbackInfo<Value>& args) {
StatementSync* stmt;
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
Environment* env = Environment::GetCurrent(args);
Local<String> sql;
if (!String::NewFromUtf8(env->isolate(), sqlite3_sql(stmt->statement_))
.ToLocal(&sql)) {
return;
}
args.GetReturnValue().Set(sql);
}
void StatementSync::ExpandedSQL(const FunctionCallbackInfo<Value>& args) {
StatementSync* stmt;
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
Environment* env = Environment::GetCurrent(args);
char* expanded = sqlite3_expanded_sql(stmt->statement_);
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);
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);
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));
}
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(env->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);
SetProtoMethod(isolate, tmpl, "sourceSQL", StatementSync::SourceSQL);
SetProtoMethod(isolate, tmpl, "expandedSQL", StatementSync::ExpandedSQL);
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,
sqlite3* 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)