2222#include < cstring>
2323#include < limits>
2424#include < string>
25+ #include < string_view>
2526
2627namespace node {
2728namespace sqlite {
@@ -3760,6 +3761,7 @@ void Statement::Finalize() {
37603761
37613762void Statement::InvalidateColumnNameCache () {
37623763 cached_column_names_.clear ();
3764+ cached_row_template_.Reset ();
37633765 cached_column_names_reprepare_count_ = -1 ;
37643766}
37653767
@@ -3981,37 +3983,79 @@ MaybeLocal<Name> Statement::ColumnNameToName(const int column) {
39813983 .As <Name>();
39823984}
39833985
3984- // Populates `keys` with cached column names, rebuilding the cache if the
3985- // statement was re-prepared.
3986- bool Statement::GetCachedColumnNames (LocalVector<Name>* keys) {
3987- Isolate* isolate = env ()->isolate ();
3986+ // Size of the stack array RowToObject passes to the template.
3987+ static constexpr int kMaxRowTemplateColumns = 64 ;
3988+
3989+ // Templates read names as Latin-1 and reject indices and duplicates.
3990+ static bool CanUseInRowTemplate (Local<Context> context,
3991+ Local<Name> key,
3992+ std::string_view name,
3993+ const std::vector<std::string_view>& seen) {
3994+ return simdutf::validate_ascii (name.data (), name.size ()) &&
3995+ std::find (seen.begin (), seen.end (), name) == seen.end () &&
3996+ key->ToArrayIndex (context).IsEmpty ();
3997+ }
39883998
3999+ // Fills either cached_row_template_ or cached_column_names_.
4000+ bool Statement::UpdateRowCache () {
39894001 const int reprepare_count =
39904002 sqlite3_stmt_status (statement_.get (), SQLITE_STMTSTATUS_REPREPARE , false );
3991- if (reprepare_count != cached_column_names_reprepare_count_) {
3992- cached_column_names_.clear ();
3993- const int num_cols = sqlite3_column_count (statement_.get ());
3994- if (num_cols == 0 ) {
3995- cached_column_names_reprepare_count_ = reprepare_count;
3996- return true ;
3997- }
4003+ if (reprepare_count == cached_column_names_reprepare_count_) return true ;
4004+
4005+ InvalidateColumnNameCache ();
4006+ Isolate* isolate = env ()->isolate ();
4007+ Local<Context> context = env ()->context ();
4008+ const int num_cols = sqlite3_column_count (statement_.get ());
4009+ LocalVector<Name> keys (isolate);
4010+ std::vector<std::string_view> names;
4011+ keys.reserve (num_cols);
4012+ names.reserve (num_cols);
4013+ bool use_template = num_cols <= kMaxRowTemplateColumns ;
4014+ for (int i = 0 ; i < num_cols; ++i) {
4015+ Local<Name> key;
4016+ if (!ColumnNameToName (i).ToLocal (&key)) return false ;
4017+ std::string_view name = sqlite3_column_name (statement_.get (), i);
4018+ use_template =
4019+ use_template && CanUseInRowTemplate (context, key, name, names);
4020+ keys.push_back (key);
4021+ names.push_back (name);
4022+ }
4023+
4024+ if (use_template) {
4025+ cached_row_template_.Reset (
4026+ isolate,
4027+ DictionaryTemplate::New (isolate, {names.data (), names.size ()}));
4028+ } else {
39984029 cached_column_names_.reserve (num_cols);
3999- for (int i = 0 ; i < num_cols; ++i) {
4000- Local<Name> key;
4001- if (!ColumnNameToName (i).ToLocal (&key)) {
4002- InvalidateColumnNameCache ();
4003- return false ;
4004- }
4005- cached_column_names_.emplace_back (Global<Name>(isolate, key));
4030+ for (Local<Name> key : keys) {
4031+ cached_column_names_.emplace_back (isolate, key);
40064032 }
4007- cached_column_names_reprepare_count_ = reprepare_count;
40084033 }
4034+ cached_column_names_reprepare_count_ = reprepare_count;
4035+ return true ;
4036+ }
40094037
4010- keys->reserve (cached_column_names_.size ());
4011- for (const auto & name : cached_column_names_) {
4012- keys->emplace_back (name.Get (isolate));
4038+ MaybeLocal<Object> Statement::RowToObject (LocalVector<Value>* values) {
4039+ DCHECK_EQ (
4040+ sqlite3_stmt_status (statement_.get (), SQLITE_STMTSTATUS_REPREPARE , false ),
4041+ cached_column_names_reprepare_count_);
4042+ Isolate* isolate = env ()->isolate ();
4043+ if (cached_row_template_.IsEmpty ()) {
4044+ LocalVector<Name> keys (isolate);
4045+ keys.reserve (cached_column_names_.size ());
4046+ for (const auto & name : cached_column_names_) {
4047+ keys.emplace_back (name.Get (isolate));
4048+ }
4049+ DCHECK_EQ (keys.size (), values->size ());
4050+ return Object::New (
4051+ isolate, Null (isolate), keys.data (), values->data (), keys.size ());
40134052 }
4014- return true ;
4053+ DCHECK_LE (values->size (), kMaxRowTemplateColumns );
4054+ MaybeLocal<Value> template_values[kMaxRowTemplateColumns ];
4055+ std::copy (values->begin (), values->end (), template_values);
4056+ return NewDictionaryInstanceNullProto (env ()->context (),
4057+ cached_row_template_.Get (isolate),
4058+ {template_values, values->size ()});
40154059}
40164060
40174061MaybeLocal<Value> StatementExecutionHelper::ColumnToValue (Environment* env,
@@ -4056,12 +4100,14 @@ MaybeLocal<Value> StatementExecutionHelper::All(Environment* env,
40564100 int num_cols = 0 ;
40574101 LocalVector<Value> rows (isolate);
40584102 LocalVector<Value> row_values (isolate);
4059- LocalVector<Name> row_keys (isolate);
40604103
40614104 SteppingStatementGuard stepping (db, stmt);
40624105 while ((r = sqlite3_step (stmt)) == SQLITE_ROW ) {
40634106 if (num_cols == 0 ) {
40644107 num_cols = sqlite3_column_count (stmt);
4108+ if (!return_arrays && !statement->UpdateRowCache ()) {
4109+ return MaybeLocal<Value>();
4110+ }
40654111 }
40664112
40674113 if (ExtractRowValues (env, stmt, num_cols, use_big_ints, &row_values)
@@ -4074,16 +4120,10 @@ MaybeLocal<Value> StatementExecutionHelper::All(Environment* env,
40744120 Array::New (isolate, row_values.data (), row_values.size ());
40754121 rows.emplace_back (row_array);
40764122 } else {
4077- if (row_keys.size () == 0 ) {
4078- // Reuses the statement's internalized column names instead of
4079- // re-interning them on every call.
4080- if (!statement->GetCachedColumnNames (&row_keys)) {
4081- return MaybeLocal<Value>();
4082- }
4123+ Local<Object> row_obj;
4124+ if (!statement->RowToObject (&row_values).ToLocal (&row_obj)) {
4125+ return MaybeLocal<Value>();
40834126 }
4084- DCHECK_EQ (row_keys.size (), row_values.size ());
4085- Local<Object> row_obj = Object::New (
4086- isolate, Null (isolate), row_keys.data (), row_values.data (), num_cols);
40874127 rows.emplace_back (row_obj);
40884128 }
40894129 }
@@ -4220,17 +4260,9 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env,
42204260 Local<Value> result;
42214261 if (return_arrays) {
42224262 result = Array::New (isolate, row_values.data (), row_values.size ());
4223- } else {
4224- LocalVector<Name> keys (isolate);
4225- // Reuses the statement's internalized column names instead of
4226- // re-interning them on every call.
4227- if (!statement->GetCachedColumnNames (&keys)) {
4228- return MaybeLocal<Value>();
4229- }
4230-
4231- DCHECK_EQ (keys.size (), row_values.size ());
4232- result = Object::New (
4233- isolate, Null (isolate), keys.data (), row_values.data (), num_cols);
4263+ } else if (!statement->UpdateRowCache () ||
4264+ !statement->RowToObject (&row_values).ToLocal (&result)) {
4265+ return MaybeLocal<Value>();
42344266 }
42354267
42364268 RESET_AND_CHECK (isolate, db, stmt, needs_reset, MaybeLocal<Value>());
@@ -5031,7 +5063,6 @@ void StatementIterator::Next(const FunctionCallbackInfo<Value>& args) {
50315063
50325064 int num_cols = sqlite3_column_count (iter->stmt_ ->statement_ .get ());
50335065 Local<Value> row_value;
5034- LocalVector<Name> row_keys (isolate);
50355066 LocalVector<Value> row_values (isolate);
50365067
50375068 if (ExtractRowValues (env,
@@ -5045,14 +5076,9 @@ void StatementIterator::Next(const FunctionCallbackInfo<Value>& args) {
50455076
50465077 if (iter->stmt_ ->return_arrays_ ) {
50475078 row_value = Array::New (isolate, row_values.data (), row_values.size ());
5048- } else {
5049- // Use cached internalized column names to avoid repeated V8 string
5050- // creation and enable hidden class sharing across row objects.
5051- if (!iter->stmt_ ->GetCachedColumnNames (&row_keys)) return ;
5052-
5053- DCHECK_EQ (row_keys.size (), row_values.size ());
5054- row_value = Object::New (
5055- isolate, Null (isolate), row_keys.data (), row_values.data (), num_cols);
5079+ } else if (!iter->stmt_ ->UpdateRowCache () ||
5080+ !iter->stmt_ ->RowToObject (&row_values).ToLocal (&row_value)) {
5081+ return ;
50565082 }
50575083
50585084 MaybeLocal<Value> values[] = {Boolean::New (isolate, false ), row_value};
0 commit comments