2222#include < cstring>
2323#include < limits>
2424#include < string>
25+ #include < string_view>
2526
2627namespace node {
2728namespace sqlite {
@@ -3812,6 +3813,7 @@ void Statement::Finalize() {
38123813
38133814void Statement::InvalidateColumnNameCache () {
38143815 cached_column_names_.clear ();
3816+ cached_row_template_.Reset ();
38153817 cached_column_names_reprepare_count_ = -1 ;
38163818}
38173819
@@ -4033,37 +4035,79 @@ MaybeLocal<Name> Statement::ColumnNameToName(const int column) {
40334035 .As <Name>();
40344036}
40354037
4036- // Populates `keys` with cached column names, rebuilding the cache if the
4037- // statement was re-prepared.
4038- bool Statement::GetCachedColumnNames (LocalVector<Name>* keys) {
4039- Isolate* isolate = env ()->isolate ();
4038+ // Size of the stack array RowToObject passes to the template.
4039+ static constexpr int kMaxRowTemplateColumns = 64 ;
4040+
4041+ // Templates read names as Latin-1 and reject indices and duplicates.
4042+ static bool CanUseInRowTemplate (Local<Context> context,
4043+ Local<Name> key,
4044+ std::string_view name,
4045+ const std::vector<std::string_view>& seen) {
4046+ return simdutf::validate_ascii (name.data (), name.size ()) &&
4047+ std::find (seen.begin (), seen.end (), name) == seen.end () &&
4048+ key->ToArrayIndex (context).IsEmpty ();
4049+ }
40404050
4051+ // Fills either cached_row_template_ or cached_column_names_.
4052+ bool Statement::UpdateRowCache () {
40414053 const int reprepare_count =
40424054 sqlite3_stmt_status (statement_.get (), SQLITE_STMTSTATUS_REPREPARE , false );
4043- if (reprepare_count != cached_column_names_reprepare_count_) {
4044- cached_column_names_.clear ();
4045- const int num_cols = sqlite3_column_count (statement_.get ());
4046- if (num_cols == 0 ) {
4047- cached_column_names_reprepare_count_ = reprepare_count;
4048- return true ;
4049- }
4055+ if (reprepare_count == cached_column_names_reprepare_count_) return true ;
4056+
4057+ InvalidateColumnNameCache ();
4058+ Isolate* isolate = env ()->isolate ();
4059+ Local<Context> context = env ()->context ();
4060+ const int num_cols = sqlite3_column_count (statement_.get ());
4061+ LocalVector<Name> keys (isolate);
4062+ std::vector<std::string_view> names;
4063+ keys.reserve (num_cols);
4064+ names.reserve (num_cols);
4065+ bool use_template = num_cols <= kMaxRowTemplateColumns ;
4066+ for (int i = 0 ; i < num_cols; ++i) {
4067+ Local<Name> key;
4068+ if (!ColumnNameToName (i).ToLocal (&key)) return false ;
4069+ std::string_view name = sqlite3_column_name (statement_.get (), i);
4070+ use_template =
4071+ use_template && CanUseInRowTemplate (context, key, name, names);
4072+ keys.push_back (key);
4073+ names.push_back (name);
4074+ }
4075+
4076+ if (use_template) {
4077+ cached_row_template_.Reset (
4078+ isolate,
4079+ DictionaryTemplate::New (isolate, {names.data (), names.size ()}));
4080+ } else {
40504081 cached_column_names_.reserve (num_cols);
4051- for (int i = 0 ; i < num_cols; ++i) {
4052- Local<Name> key;
4053- if (!ColumnNameToName (i).ToLocal (&key)) {
4054- InvalidateColumnNameCache ();
4055- return false ;
4056- }
4057- cached_column_names_.emplace_back (Global<Name>(isolate, key));
4082+ for (Local<Name> key : keys) {
4083+ cached_column_names_.emplace_back (isolate, key);
40584084 }
4059- cached_column_names_reprepare_count_ = reprepare_count;
40604085 }
4086+ cached_column_names_reprepare_count_ = reprepare_count;
4087+ return true ;
4088+ }
40614089
4062- keys->reserve (cached_column_names_.size ());
4063- for (const auto & name : cached_column_names_) {
4064- keys->emplace_back (name.Get (isolate));
4090+ MaybeLocal<Object> Statement::RowToObject (LocalVector<Value>* values) {
4091+ DCHECK_EQ (
4092+ sqlite3_stmt_status (statement_.get (), SQLITE_STMTSTATUS_REPREPARE , false ),
4093+ cached_column_names_reprepare_count_);
4094+ Isolate* isolate = env ()->isolate ();
4095+ if (cached_row_template_.IsEmpty ()) {
4096+ LocalVector<Name> keys (isolate);
4097+ keys.reserve (cached_column_names_.size ());
4098+ for (const auto & name : cached_column_names_) {
4099+ keys.emplace_back (name.Get (isolate));
4100+ }
4101+ DCHECK_EQ (keys.size (), values->size ());
4102+ return Object::New (
4103+ isolate, Null (isolate), keys.data (), values->data (), keys.size ());
40654104 }
4066- return true ;
4105+ DCHECK_LE (values->size (), kMaxRowTemplateColumns );
4106+ MaybeLocal<Value> template_values[kMaxRowTemplateColumns ];
4107+ std::copy (values->begin (), values->end (), template_values);
4108+ return NewDictionaryInstanceNullProto (env ()->context (),
4109+ cached_row_template_.Get (isolate),
4110+ {template_values, values->size ()});
40674111}
40684112
40694113MaybeLocal<Value> StatementExecutionHelper::ColumnToValue (Environment* env,
@@ -4108,12 +4152,14 @@ MaybeLocal<Value> StatementExecutionHelper::All(Environment* env,
41084152 int num_cols = 0 ;
41094153 LocalVector<Value> rows (isolate);
41104154 LocalVector<Value> row_values (isolate);
4111- LocalVector<Name> row_keys (isolate);
41124155
41134156 SteppingStatementGuard stepping (db, stmt);
41144157 while ((r = sqlite3_step (stmt)) == SQLITE_ROW ) {
41154158 if (num_cols == 0 ) {
41164159 num_cols = sqlite3_column_count (stmt);
4160+ if (!return_arrays && !statement->UpdateRowCache ()) {
4161+ return MaybeLocal<Value>();
4162+ }
41174163 }
41184164
41194165 if (ExtractRowValues (env, stmt, num_cols, use_big_ints, &row_values)
@@ -4126,16 +4172,10 @@ MaybeLocal<Value> StatementExecutionHelper::All(Environment* env,
41264172 Array::New (isolate, row_values.data (), row_values.size ());
41274173 rows.emplace_back (row_array);
41284174 } else {
4129- if (row_keys.size () == 0 ) {
4130- // Reuses the statement's internalized column names instead of
4131- // re-interning them on every call.
4132- if (!statement->GetCachedColumnNames (&row_keys)) {
4133- return MaybeLocal<Value>();
4134- }
4175+ Local<Object> row_obj;
4176+ if (!statement->RowToObject (&row_values).ToLocal (&row_obj)) {
4177+ return MaybeLocal<Value>();
41354178 }
4136- DCHECK_EQ (row_keys.size (), row_values.size ());
4137- Local<Object> row_obj = Object::New (
4138- isolate, Null (isolate), row_keys.data (), row_values.data (), num_cols);
41394179 rows.emplace_back (row_obj);
41404180 }
41414181 }
@@ -4272,17 +4312,9 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env,
42724312 Local<Value> result;
42734313 if (return_arrays) {
42744314 result = Array::New (isolate, row_values.data (), row_values.size ());
4275- } else {
4276- LocalVector<Name> keys (isolate);
4277- // Reuses the statement's internalized column names instead of
4278- // re-interning them on every call.
4279- if (!statement->GetCachedColumnNames (&keys)) {
4280- return MaybeLocal<Value>();
4281- }
4282-
4283- DCHECK_EQ (keys.size (), row_values.size ());
4284- result = Object::New (
4285- isolate, Null (isolate), keys.data (), row_values.data (), num_cols);
4315+ } else if (!statement->UpdateRowCache () ||
4316+ !statement->RowToObject (&row_values).ToLocal (&result)) {
4317+ return MaybeLocal<Value>();
42864318 }
42874319
42884320 RESET_AND_CHECK (isolate, db, stmt, needs_reset, MaybeLocal<Value>());
@@ -5083,7 +5115,6 @@ void StatementIterator::Next(const FunctionCallbackInfo<Value>& args) {
50835115
50845116 int num_cols = sqlite3_column_count (iter->stmt_ ->statement_ .get ());
50855117 Local<Value> row_value;
5086- LocalVector<Name> row_keys (isolate);
50875118 LocalVector<Value> row_values (isolate);
50885119
50895120 if (ExtractRowValues (env,
@@ -5097,14 +5128,9 @@ void StatementIterator::Next(const FunctionCallbackInfo<Value>& args) {
50975128
50985129 if (iter->stmt_ ->return_arrays_ ) {
50995130 row_value = Array::New (isolate, row_values.data (), row_values.size ());
5100- } else {
5101- // Use cached internalized column names to avoid repeated V8 string
5102- // creation and enable hidden class sharing across row objects.
5103- if (!iter->stmt_ ->GetCachedColumnNames (&row_keys)) return ;
5104-
5105- DCHECK_EQ (row_keys.size (), row_values.size ());
5106- row_value = Object::New (
5107- isolate, Null (isolate), row_keys.data (), row_values.data (), num_cols);
5131+ } else if (!iter->stmt_ ->UpdateRowCache () ||
5132+ !iter->stmt_ ->RowToObject (&row_values).ToLocal (&row_value)) {
5133+ return ;
51085134 }
51095135
51105136 MaybeLocal<Value> values[] = {Boolean::New (isolate, false ), row_value};
0 commit comments