diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 98612f39695897..f4d88f039c3490 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -8,6 +8,7 @@ #include "node_errors.h" #include "node_mem-inl.h" #include "node_url.h" +#include "simdutf.h" #include "sqlite3.h" #include "threadpoolwork-inl.h" #include "util-inl.h" @@ -63,6 +64,20 @@ using v8::TryCatch; using v8::Uint8Array; using v8::Value; +inline MaybeLocal Utf8StringMaybeOneByte(Isolate* isolate, + std::string_view input) { + const int len = static_cast(input.size()); + if (simdutf::validate_ascii(input.data(), input.size())) { + return String::NewFromOneByte( + isolate, + reinterpret_cast(input.data()), + NewStringType::kNormal, + len); + } + return String::NewFromUtf8( + isolate, input.data(), NewStringType::kNormal, len); +} + #define CHECK_ERROR_OR_THROW(isolate, db, expr, expected, ret) \ do { \ int r_ = (expr); \ @@ -105,7 +120,10 @@ using v8::Value; case SQLITE_TEXT: { \ const char* v = \ reinterpret_cast(sqlite3_##from##_text(__VA_ARGS__)); \ - (result) = String::NewFromUtf8((isolate), v).As(); \ + const int v_len = sqlite3_##from##_bytes(__VA_ARGS__); \ + (result) = \ + Utf8StringMaybeOneByte((isolate), std::string_view(v, v_len)) \ + .As(); \ break; \ } \ case SQLITE_NULL: { \ @@ -2415,6 +2433,11 @@ StatementSync::~StatementSync() { void StatementSync::Finalize() { sqlite3_finalize(statement_); statement_ = nullptr; + InvalidateColumnNameCache(); +} + +void StatementSync::InvalidateColumnNameCache() { + cached_column_names_.clear(); } inline bool StatementSync::IsFinalized() { @@ -2598,7 +2621,42 @@ MaybeLocal StatementSync::ColumnNameToName(const int column) { return MaybeLocal(); } - return String::NewFromUtf8(env()->isolate(), col_name).As(); + return String::NewFromUtf8( + env()->isolate(), col_name, NewStringType::kInternalized) + .As(); +} + +// Populates `keys` with cached column names, rebuilding the cache if the +// statement was re-prepared. +bool StatementSync::GetCachedColumnNames(LocalVector* keys) { + Isolate* isolate = env()->isolate(); + + const int reprepare_count = + sqlite3_stmt_status(statement_, SQLITE_STMTSTATUS_REPREPARE, false); + if (reprepare_count != cached_column_names_reprepare_count_) { + cached_column_names_.clear(); + const int num_cols = sqlite3_column_count(statement_); + if (num_cols == 0) { + cached_column_names_reprepare_count_ = reprepare_count; + return true; + } + cached_column_names_.reserve(num_cols); + for (int i = 0; i < num_cols; ++i) { + Local key; + if (!ColumnNameToName(i).ToLocal(&key)) { + InvalidateColumnNameCache(); + return false; + } + cached_column_names_.emplace_back(Global(isolate, key)); + } + cached_column_names_reprepare_count_ = reprepare_count; + } + + keys->reserve(cached_column_names_.size()); + for (const auto& name : cached_column_names_) { + keys->emplace_back(name.Get(isolate)); + } + return true; } MaybeLocal StatementExecutionHelper::ColumnToValue(Environment* env, @@ -2620,7 +2678,9 @@ MaybeLocal StatementExecutionHelper::ColumnNameToName(Environment* env, return MaybeLocal(); } - return String::NewFromUtf8(env->isolate(), col_name).As(); + return String::NewFromUtf8( + env->isolate(), col_name, NewStringType::kInternalized) + .As(); } void StatementSync::MemoryInfo(MemoryTracker* tracker) const {} @@ -3530,12 +3590,9 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo& args) { if (iter->stmt_->return_arrays_) { row_value = Array::New(isolate, row_values.data(), row_values.size()); } else { - row_keys.reserve(num_cols); - for (int i = 0; i < num_cols; ++i) { - Local key; - if (!iter->stmt_->ColumnNameToName(i).ToLocal(&key)) return; - row_keys.emplace_back(key); - } + // Use cached internalized column names to avoid repeated V8 string + // creation and enable hidden class sharing across row objects. + if (!iter->stmt_->GetCachedColumnNames(&row_keys)) return; DCHECK_EQ(row_keys.size(), row_values.size()); row_value = Object::New( diff --git a/src/node_sqlite.h b/src/node_sqlite.h index 3ee79cc10ec562..1ff5804e704231 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace node { namespace sqlite { @@ -277,6 +278,7 @@ class StatementSync : public BaseObject { static void SetReturnArrays(const v8::FunctionCallbackInfo& args); v8::MaybeLocal ColumnToValue(const int column); v8::MaybeLocal ColumnNameToName(const int column); + bool GetCachedColumnNames(v8::LocalVector* keys); void Finalize(); bool IsFinalized(); @@ -294,6 +296,9 @@ class StatementSync : public BaseObject { uint64_t reset_generation_ = 0; std::optional> bare_named_params_; inline int ResetStatement(); + std::vector> cached_column_names_; + int cached_column_names_reprepare_count_ = -1; + void InvalidateColumnNameCache(); bool BindParams(const v8::FunctionCallbackInfo& args); bool BindValue(const v8::Local& value, const int index);