Skip to content

Commit ef8f8f8

Browse files
sqlite: use OneByte for ASCII text and internalize col names
Use simdutf to detect ASCII text values and create them via NewFromOneByte for compact one-byte representation. Internalize column name strings with kInternalized so V8 shares hidden classes across row objects. Cache column names on StatementSync for iterate(), invalidated via SQLITE_STMTSTATUS_REPREPARE on schema changes. Refs: nodejs/performance#181 PR-URL: #61954 Refs: nodejs/performance#181 Reviewed-By: Robert Nagy <ronagy@icloud.com>
1 parent 726b220 commit ef8f8f8

File tree

2 files changed

+71
-9
lines changed

2 files changed

+71
-9
lines changed

src/node_sqlite.cc

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "node_errors.h"
99
#include "node_mem-inl.h"
1010
#include "node_url.h"
11+
#include "simdutf.h"
1112
#include "sqlite3.h"
1213
#include "threadpoolwork-inl.h"
1314
#include "util-inl.h"
@@ -64,6 +65,20 @@ using v8::TryCatch;
6465
using v8::Uint8Array;
6566
using v8::Value;
6667

68+
inline MaybeLocal<String> Utf8StringMaybeOneByte(Isolate* isolate,
69+
std::string_view input) {
70+
const int len = static_cast<int>(input.size());
71+
if (simdutf::validate_ascii(input.data(), input.size())) {
72+
return String::NewFromOneByte(
73+
isolate,
74+
reinterpret_cast<const uint8_t*>(input.data()),
75+
NewStringType::kNormal,
76+
len);
77+
}
78+
return String::NewFromUtf8(
79+
isolate, input.data(), NewStringType::kNormal, len);
80+
}
81+
6782
#define CHECK_ERROR_OR_THROW(isolate, db, expr, expected, ret) \
6883
do { \
6984
int r_ = (expr); \
@@ -106,7 +121,10 @@ using v8::Value;
106121
case SQLITE_TEXT: { \
107122
const char* v = \
108123
reinterpret_cast<const char*>(sqlite3_##from##_text(__VA_ARGS__)); \
109-
(result) = String::NewFromUtf8((isolate), v).As<Value>(); \
124+
const int v_len = sqlite3_##from##_bytes(__VA_ARGS__); \
125+
(result) = \
126+
Utf8StringMaybeOneByte((isolate), std::string_view(v, v_len)) \
127+
.As<Value>(); \
110128
break; \
111129
} \
112130
case SQLITE_NULL: { \
@@ -2547,6 +2565,11 @@ StatementSync::~StatementSync() {
25472565
void StatementSync::Finalize() {
25482566
sqlite3_finalize(statement_);
25492567
statement_ = nullptr;
2568+
InvalidateColumnNameCache();
2569+
}
2570+
2571+
void StatementSync::InvalidateColumnNameCache() {
2572+
cached_column_names_.clear();
25502573
}
25512574

25522575
inline bool StatementSync::IsFinalized() {
@@ -2730,7 +2753,42 @@ MaybeLocal<Name> StatementSync::ColumnNameToName(const int column) {
27302753
return MaybeLocal<Name>();
27312754
}
27322755

2733-
return String::NewFromUtf8(env()->isolate(), col_name).As<Name>();
2756+
return String::NewFromUtf8(
2757+
env()->isolate(), col_name, NewStringType::kInternalized)
2758+
.As<Name>();
2759+
}
2760+
2761+
// Populates `keys` with cached column names, rebuilding the cache if the
2762+
// statement was re-prepared.
2763+
bool StatementSync::GetCachedColumnNames(LocalVector<Name>* keys) {
2764+
Isolate* isolate = env()->isolate();
2765+
2766+
const int reprepare_count =
2767+
sqlite3_stmt_status(statement_, SQLITE_STMTSTATUS_REPREPARE, false);
2768+
if (reprepare_count != cached_column_names_reprepare_count_) {
2769+
cached_column_names_.clear();
2770+
const int num_cols = sqlite3_column_count(statement_);
2771+
if (num_cols == 0) {
2772+
cached_column_names_reprepare_count_ = reprepare_count;
2773+
return true;
2774+
}
2775+
cached_column_names_.reserve(num_cols);
2776+
for (int i = 0; i < num_cols; ++i) {
2777+
Local<Name> key;
2778+
if (!ColumnNameToName(i).ToLocal(&key)) {
2779+
InvalidateColumnNameCache();
2780+
return false;
2781+
}
2782+
cached_column_names_.emplace_back(Global<Name>(isolate, key));
2783+
}
2784+
cached_column_names_reprepare_count_ = reprepare_count;
2785+
}
2786+
2787+
keys->reserve(cached_column_names_.size());
2788+
for (const auto& name : cached_column_names_) {
2789+
keys->emplace_back(name.Get(isolate));
2790+
}
2791+
return true;
27342792
}
27352793

27362794
MaybeLocal<Value> StatementExecutionHelper::ColumnToValue(Environment* env,
@@ -2752,7 +2810,9 @@ MaybeLocal<Name> StatementExecutionHelper::ColumnNameToName(Environment* env,
27522810
return MaybeLocal<Name>();
27532811
}
27542812

2755-
return String::NewFromUtf8(env->isolate(), col_name).As<Name>();
2813+
return String::NewFromUtf8(
2814+
env->isolate(), col_name, NewStringType::kInternalized)
2815+
.As<Name>();
27562816
}
27572817

27582818
void StatementSync::MemoryInfo(MemoryTracker* tracker) const {}
@@ -3662,12 +3722,9 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) {
36623722
if (iter->stmt_->return_arrays_) {
36633723
row_value = Array::New(isolate, row_values.data(), row_values.size());
36643724
} else {
3665-
row_keys.reserve(num_cols);
3666-
for (int i = 0; i < num_cols; ++i) {
3667-
Local<Name> key;
3668-
if (!iter->stmt_->ColumnNameToName(i).ToLocal(&key)) return;
3669-
row_keys.emplace_back(key);
3670-
}
3725+
// Use cached internalized column names to avoid repeated V8 string
3726+
// creation and enable hidden class sharing across row objects.
3727+
if (!iter->stmt_->GetCachedColumnNames(&row_keys)) return;
36713728

36723729
DCHECK_EQ(row_keys.size(), row_values.size());
36733730
row_value = Object::New(

src/node_sqlite.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <optional>
1616
#include <string_view>
1717
#include <unordered_set>
18+
#include <vector>
1819

1920
namespace node {
2021
namespace sqlite {
@@ -279,6 +280,7 @@ class StatementSync : public BaseObject {
279280
static void SetReturnArrays(const v8::FunctionCallbackInfo<v8::Value>& args);
280281
v8::MaybeLocal<v8::Value> ColumnToValue(const int column);
281282
v8::MaybeLocal<v8::Name> ColumnNameToName(const int column);
283+
bool GetCachedColumnNames(v8::LocalVector<v8::Name>* keys);
282284
void Finalize();
283285
bool IsFinalized();
284286

@@ -296,6 +298,9 @@ class StatementSync : public BaseObject {
296298
uint64_t reset_generation_ = 0;
297299
std::optional<std::map<std::string, std::string>> bare_named_params_;
298300
inline int ResetStatement();
301+
std::vector<v8::Global<v8::Name>> cached_column_names_;
302+
int cached_column_names_reprepare_count_ = -1;
303+
void InvalidateColumnNameCache();
299304
bool BindParams(const v8::FunctionCallbackInfo<v8::Value>& args);
300305
bool BindValue(const v8::Local<v8::Value>& value, const int index);
301306

0 commit comments

Comments
 (0)