Skip to content

Commit 82de30f

Browse files
committed
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
1 parent 511a57a commit 82de30f

2 files changed

Lines changed: 59 additions & 9 deletions

File tree

src/node_sqlite.cc

Lines changed: 55 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"
@@ -63,6 +64,19 @@ using v8::TryCatch;
6364
using v8::Uint8Array;
6465
using v8::Value;
6566

67+
inline MaybeLocal<String> Utf8StringMaybeOneByte(Isolate* isolate,
68+
const char* data,
69+
size_t length) {
70+
int len = static_cast<int>(length);
71+
if (simdutf::validate_ascii(data, length)) {
72+
return String::NewFromOneByte(isolate,
73+
reinterpret_cast<const uint8_t*>(data),
74+
NewStringType::kNormal,
75+
len);
76+
}
77+
return String::NewFromUtf8(isolate, data, NewStringType::kNormal, len);
78+
}
79+
6680
#define CHECK_ERROR_OR_THROW(isolate, db, expr, expected, ret) \
6781
do { \
6882
int r_ = (expr); \
@@ -105,7 +119,8 @@ using v8::Value;
105119
case SQLITE_TEXT: { \
106120
const char* v = \
107121
reinterpret_cast<const char*>(sqlite3_##from##_text(__VA_ARGS__)); \
108-
(result) = String::NewFromUtf8((isolate), v).As<Value>(); \
122+
int v_len = sqlite3_##from##_bytes(__VA_ARGS__); \
123+
(result) = Utf8StringMaybeOneByte((isolate), v, v_len).As<Value>(); \
109124
break; \
110125
} \
111126
case SQLITE_NULL: { \
@@ -2416,6 +2431,7 @@ StatementSync::~StatementSync() {
24162431
void StatementSync::Finalize() {
24172432
sqlite3_finalize(statement_);
24182433
statement_ = nullptr;
2434+
cached_column_names_.clear();
24192435
}
24202436

24212437
inline bool StatementSync::IsFinalized() {
@@ -2599,7 +2615,40 @@ MaybeLocal<Name> StatementSync::ColumnNameToName(const int column) {
25992615
return MaybeLocal<Name>();
26002616
}
26012617

2602-
return String::NewFromUtf8(env()->isolate(), col_name).As<Name>();
2618+
return String::NewFromUtf8(
2619+
env()->isolate(), col_name, NewStringType::kInternalized)
2620+
.As<Name>();
2621+
}
2622+
2623+
bool StatementSync::GetCachedColumnNames(LocalVector<Name>* keys) {
2624+
Isolate* isolate = env()->isolate();
2625+
2626+
int reprepare_count =
2627+
sqlite3_stmt_status(statement_, SQLITE_STMTSTATUS_REPREPARE, 0);
2628+
if (reprepare_count != cached_column_names_reprepare_count_) {
2629+
cached_column_names_.clear();
2630+
int num_cols = sqlite3_column_count(statement_);
2631+
if (num_cols == 0) {
2632+
cached_column_names_reprepare_count_ = reprepare_count;
2633+
return true;
2634+
}
2635+
cached_column_names_.reserve(num_cols);
2636+
for (int i = 0; i < num_cols; ++i) {
2637+
Local<Name> key;
2638+
if (!ColumnNameToName(i).ToLocal(&key)) {
2639+
cached_column_names_.clear();
2640+
return false;
2641+
}
2642+
cached_column_names_.emplace_back(Global<Name>(isolate, key));
2643+
}
2644+
cached_column_names_reprepare_count_ = reprepare_count;
2645+
}
2646+
2647+
keys->reserve(cached_column_names_.size());
2648+
for (const auto& name : cached_column_names_) {
2649+
keys->emplace_back(name.Get(isolate));
2650+
}
2651+
return true;
26032652
}
26042653

26052654
MaybeLocal<Value> StatementExecutionHelper::ColumnToValue(Environment* env,
@@ -2621,7 +2670,9 @@ MaybeLocal<Name> StatementExecutionHelper::ColumnNameToName(Environment* env,
26212670
return MaybeLocal<Name>();
26222671
}
26232672

2624-
return String::NewFromUtf8(env->isolate(), col_name).As<Name>();
2673+
return String::NewFromUtf8(
2674+
env->isolate(), col_name, NewStringType::kInternalized)
2675+
.As<Name>();
26252676
}
26262677

26272678
void StatementSync::MemoryInfo(MemoryTracker* tracker) const {}
@@ -3531,12 +3582,7 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) {
35313582
if (iter->stmt_->return_arrays_) {
35323583
row_value = Array::New(isolate, row_values.data(), row_values.size());
35333584
} else {
3534-
row_keys.reserve(num_cols);
3535-
for (int i = 0; i < num_cols; ++i) {
3536-
Local<Name> key;
3537-
if (!iter->stmt_->ColumnNameToName(i).ToLocal(&key)) return;
3538-
row_keys.emplace_back(key);
3539-
}
3585+
if (!iter->stmt_->GetCachedColumnNames(&row_keys)) return;
35403586

35413587
DCHECK_EQ(row_keys.size(), row_values.size());
35423588
row_value = Object::New(

src/node_sqlite.h

Lines changed: 4 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 {
@@ -277,6 +278,7 @@ class StatementSync : public BaseObject {
277278
static void SetReturnArrays(const v8::FunctionCallbackInfo<v8::Value>& args);
278279
v8::MaybeLocal<v8::Value> ColumnToValue(const int column);
279280
v8::MaybeLocal<v8::Name> ColumnNameToName(const int column);
281+
bool GetCachedColumnNames(v8::LocalVector<v8::Name>* keys);
280282
void Finalize();
281283
bool IsFinalized();
282284

@@ -294,6 +296,8 @@ class StatementSync : public BaseObject {
294296
uint64_t reset_generation_ = 0;
295297
std::optional<std::map<std::string, std::string>> bare_named_params_;
296298
inline int ResetStatement();
299+
std::vector<v8::Global<v8::Name>> cached_column_names_;
300+
int cached_column_names_reprepare_count_ = -1;
297301
bool BindParams(const v8::FunctionCallbackInfo<v8::Value>& args);
298302
bool BindValue(const v8::Local<v8::Value>& value, const int index);
299303

0 commit comments

Comments
 (0)