#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 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::LocalVector; using v8::MaybeLocal; using v8::Name; using v8::Null; 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 CreateSQLiteError(Isolate* isolate, const char* message) { Local js_msg = String::NewFromUtf8(isolate, message).ToLocalChecked(); Local 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 CreateSQLiteError(Isolate* isolate, sqlite3* db) { int errcode = sqlite3_extended_errcode(db); const char* errstr = sqlite3_errstr(errcode); const char* errmsg = sqlite3_errmsg(db); Local 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, Local 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() { 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); 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& 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 options = args[1].As(); Local open_string = FIXED_ONE_BYTE_STRING(env->isolate(), "open"); Local 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()->Value(); } } new DatabaseSync(env, args.This(), args[0].As(), open); } void DatabaseSync::Open(const FunctionCallbackInfo& args) { DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); db->Open(); } void DatabaseSync::Close(const FunctionCallbackInfo& 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& 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()); 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 stmt = StatementSync::Create(env, db, s); db->statements_.insert(stmt.get()); args.GetReturnValue().Set(stmt->object()); } void DatabaseSync::Exec(const FunctionCallbackInfo& 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()); 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, 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& 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 obj = args[0].As(); Local context = obj->GetIsolate()->GetCurrentContext(); Local 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 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; 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, 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()->Value(); r = sqlite3_bind_double(statement_, index, val); } else if (value->IsString()) { node::Utf8Value val(env()->isolate(), value.As()); 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 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()->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 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(); } } case SQLITE_FLOAT: return Number::New(env()->isolate(), sqlite3_column_double(statement_, column)); case SQLITE_TEXT: { const char* value = reinterpret_cast( sqlite3_column_text(statement_, column)); return String::NewFromUtf8(env()->isolate(), value).As(); } case SQLITE_NULL: return Null(env()->isolate()); case SQLITE_BLOB: { size_t size = static_cast(sqlite3_column_bytes(statement_, column)); auto data = reinterpret_cast( 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 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(); } return String::NewFromUtf8(env()->isolate(), col_name).As(); } void StatementSync::MemoryInfo(MemoryTracker* tracker) const {} void StatementSync::All(const FunctionCallbackInfo& 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 rows(isolate); while ((r = sqlite3_step(stmt->statement_)) == SQLITE_ROW) { LocalVector row_keys(isolate); row_keys.reserve(num_cols); LocalVector row_values(isolate); row_values.reserve(num_cols); for (int i = 0; i < num_cols; ++i) { Local key; if (!stmt->ColumnNameToName(i).ToLocal(&key)) return; Local val; if (!stmt->ColumnToValue(i).ToLocal(&val)) return; row_keys.emplace_back(key); row_values.emplace_back(val); } Local 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& 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 keys(isolate); keys.reserve(num_cols); LocalVector values(isolate); values.reserve(num_cols); for (int i = 0; i < num_cols; ++i) { Local key; if (!stmt->ColumnNameToName(i).ToLocal(&key)) return; Local val; if (!stmt->ColumnToValue(i).ToLocal(&val)) return; keys.emplace_back(key); values.emplace_back(val); } Local result = Object::New(isolate, Null(isolate), keys.data(), values.data(), num_cols); args.GetReturnValue().Set(result); } void StatementSync::Run(const FunctionCallbackInfo& 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 result = Object::New(env->isolate()); Local last_insert_rowid_string = FIXED_ONE_BYTE_STRING(env->isolate(), "lastInsertRowid"); Local 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 last_insert_rowid_val; Local 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& 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 sql; if (!String::NewFromUtf8(env->isolate(), sqlite3_sql(stmt->statement_)) .ToLocal(&sql)) { return; } args.GetReturnValue().Set(sql); } void StatementSync::ExpandedSQL(const FunctionCallbackInfo& 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 result; if (!maybe_expanded.ToLocal(&result)) { return; } args.GetReturnValue().Set(result); } void StatementSync::SetAllowBareNamedParameters( const FunctionCallbackInfo& 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& 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& args) { node::THROW_ERR_ILLEGAL_CONSTRUCTOR(Environment::GetCurrent(args)); } Local StatementSync::GetConstructorTemplate( Environment* env) { Local 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); 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::Create(Environment* env, DatabaseSync* db, sqlite3_stmt* stmt) { Local obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return BaseObjectPtr(); } return MakeBaseObject(env, obj, db, stmt); } static void Initialize(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); Local 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)