@@ -3759,6 +3759,7 @@ void Statement::Finalize() {
37593759}
37603760
37613761void Statement::InvalidateColumnNameCache () {
3762+ cached_row_template_.Reset ();
37623763 cached_column_names_.clear ();
37633764 cached_column_names_reprepare_count_ = -1 ;
37643765}
@@ -3981,37 +3982,86 @@ MaybeLocal<Name> Statement::ColumnNameToName(const int column) {
39813982 .As <Name>();
39823983}
39833984
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 ();
3985+ // Mirrors V8's String::AsArrayIndex(): canonical decimal below 2^32 - 1.
3986+ static bool IsArrayIndex (std::string_view name) {
3987+ uint32_t index;
3988+ const char * end = name.data () + name.size ();
3989+ auto [ptr, ec] = std::from_chars (name.data (), end, index);
3990+ return ec == std::errc () && ptr == end &&
3991+ (name.size () == 1 || name[0 ] != ' 0' ) &&
3992+ index != std::numeric_limits<uint32_t >::max ();
3993+ }
3994+
3995+ // DictionaryTemplate CHECKs that no key is an array index, interns keys as
3996+ // Latin-1 rather than UTF-8, and assumes keys are unique.
3997+ static bool CanUseRowTemplate (const std::vector<std::string_view>& names) {
3998+ std::unordered_set<std::string_view> seen;
3999+ for (std::string_view name : names) {
4000+ if (!simdutf::validate_ascii (name.data (), name.size ()) ||
4001+ IsArrayIndex (name) || !seen.insert (name).second ) {
4002+ return false ;
4003+ }
4004+ }
4005+ return true ;
4006+ }
39884007
4008+ // Rebuilds the row shape if the statement was re-prepared.
4009+ bool Statement::UpdateColumnNameCache () {
39894010 const int reprepare_count =
39904011 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- }
4012+ if (reprepare_count == cached_column_names_reprepare_count_) return true ;
4013+
4014+ Isolate* isolate = env ()->isolate ();
4015+ InvalidateColumnNameCache ();
4016+ const int num_cols = sqlite3_column_count (statement_.get ());
4017+ std::vector<std::string_view> names;
4018+ names.reserve (num_cols);
4019+ for (int i = 0 ; i < num_cols; ++i) {
4020+ const char * name = sqlite3_column_name (statement_.get (), i);
4021+ // ColumnNameToName() below reports the error.
4022+ if (name == nullptr ) break ;
4023+ names.emplace_back (name);
4024+ }
4025+
4026+ if (names.size () == static_cast <size_t >(num_cols) &&
4027+ CanUseRowTemplate (names)) {
4028+ cached_row_template_.Reset (
4029+ isolate,
4030+ DictionaryTemplate::New (isolate, {names.data (), names.size ()}));
4031+ } else {
39984032 cached_column_names_.reserve (num_cols);
39994033 for (int i = 0 ; i < num_cols; ++i) {
40004034 Local<Name> key;
40014035 if (!ColumnNameToName (i).ToLocal (&key)) {
40024036 InvalidateColumnNameCache ();
40034037 return false ;
40044038 }
4005- cached_column_names_.emplace_back (Global<Name>( isolate, key) );
4039+ cached_column_names_.emplace_back (isolate, key);
40064040 }
4007- cached_column_names_reprepare_count_ = reprepare_count;
40084041 }
4042+ cached_column_names_reprepare_count_ = reprepare_count;
4043+ return true ;
4044+ }
40094045
4010- keys->reserve (cached_column_names_.size ());
4046+ MaybeLocal<Object> Statement::NewRowObject (LocalVector<Value>* values) {
4047+ Isolate* isolate = env ()->isolate ();
4048+ if (!cached_row_template_.IsEmpty ()) {
4049+ // NewInstance() may replace entries, so it needs a MaybeLocal copy.
4050+ MaybeStackBuffer<MaybeLocal<Value>, 16 > maybe_values (values->size ());
4051+ for (size_t i = 0 ; i < values->size (); ++i) maybe_values[i] = (*values)[i];
4052+ return NewDictionaryInstanceNullProto (env ()->context (),
4053+ cached_row_template_.Get (isolate),
4054+ {maybe_values.out (), values->size ()});
4055+ }
4056+
4057+ DCHECK_EQ (cached_column_names_.size (), values->size ());
4058+ LocalVector<Name> keys (isolate);
4059+ keys.reserve (cached_column_names_.size ());
40114060 for (const auto & name : cached_column_names_) {
4012- keys-> emplace_back (name.Get (isolate));
4061+ keys. emplace_back (name.Get (isolate));
40134062 }
4014- return true ;
4063+ return Object::New (
4064+ isolate, Null (isolate), keys.data (), values->data (), values->size ());
40154065}
40164066
40174067MaybeLocal<Value> StatementExecutionHelper::ColumnToValue (Environment* env,
@@ -4056,12 +4106,15 @@ MaybeLocal<Value> StatementExecutionHelper::All(Environment* env,
40564106 int num_cols = 0 ;
40574107 LocalVector<Value> rows (isolate);
40584108 LocalVector<Value> row_values (isolate);
4059- LocalVector<Name> row_keys (isolate);
40604109
40614110 SteppingStatementGuard stepping (db, stmt);
40624111 while ((r = sqlite3_step (stmt)) == SQLITE_ROW ) {
40634112 if (num_cols == 0 ) {
40644113 num_cols = sqlite3_column_count (stmt);
4114+ // A running statement is only re-prepared before its first row.
4115+ if (!return_arrays && !statement->UpdateColumnNameCache ()) {
4116+ return MaybeLocal<Value>();
4117+ }
40654118 }
40664119
40674120 if (ExtractRowValues (env, stmt, num_cols, use_big_ints, &row_values)
@@ -4074,16 +4127,10 @@ MaybeLocal<Value> StatementExecutionHelper::All(Environment* env,
40744127 Array::New (isolate, row_values.data (), row_values.size ());
40754128 rows.emplace_back (row_array);
40764129 } 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- }
4130+ Local<Object> row_obj;
4131+ if (!statement->NewRowObject (&row_values).ToLocal (&row_obj)) {
4132+ return MaybeLocal<Value>();
40834133 }
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);
40874134 rows.emplace_back (row_obj);
40884135 }
40894136 }
@@ -4221,16 +4268,10 @@ MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env,
42214268 if (return_arrays) {
42224269 result = Array::New (isolate, row_values.data (), row_values.size ());
42234270 } 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)) {
4271+ if (!statement->UpdateColumnNameCache () ||
4272+ !statement->NewRowObject (&row_values).ToLocal (&result)) {
42284273 return MaybeLocal<Value>();
42294274 }
4230-
4231- DCHECK_EQ (keys.size (), row_values.size ());
4232- result = Object::New (
4233- isolate, Null (isolate), keys.data (), row_values.data (), num_cols);
42344275 }
42354276
42364277 RESET_AND_CHECK (isolate, db, stmt, needs_reset, MaybeLocal<Value>());
@@ -5031,7 +5072,6 @@ void StatementIterator::Next(const FunctionCallbackInfo<Value>& args) {
50315072
50325073 int num_cols = sqlite3_column_count (iter->stmt_ ->statement_ .get ());
50335074 Local<Value> row_value;
5034- LocalVector<Name> row_keys (isolate);
50355075 LocalVector<Value> row_values (isolate);
50365076
50375077 if (ExtractRowValues (env,
@@ -5046,13 +5086,10 @@ void StatementIterator::Next(const FunctionCallbackInfo<Value>& args) {
50465086 if (iter->stmt_ ->return_arrays_ ) {
50475087 row_value = Array::New (isolate, row_values.data (), row_values.size ());
50485088 } 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);
5089+ if (!iter->stmt_ ->UpdateColumnNameCache () ||
5090+ !iter->stmt_ ->NewRowObject (&row_values).ToLocal (&row_value)) {
5091+ return ;
5092+ }
50565093 }
50575094
50585095 MaybeLocal<Value> values[] = {Boolean::New (isolate, false ), row_value};
0 commit comments