Skip to content

Commit e1fbfcb

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 2422ed8 commit e1fbfcb

File tree

2 files changed

+59
-9
lines changed

2 files changed

+59
-9
lines changed

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: { \
@@ -2415,6 +2430,7 @@ StatementSync::~StatementSync() {
24152430
void StatementSync::Finalize() {
24162431
sqlite3_finalize(statement_);
24172432
statement_ = nullptr;
2433+
cached_column_names_.clear();
24182434
}
24192435

24202436
inline bool StatementSync::IsFinalized() {
@@ -2598,7 +2614,40 @@ MaybeLocal<Name> StatementSync::ColumnNameToName(const int column) {
25982614
return MaybeLocal<Name>();
25992615
}
26002616

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

26042653
MaybeLocal<Value> StatementExecutionHelper::ColumnToValue(Environment* env,
@@ -2620,7 +2669,9 @@ MaybeLocal<Name> StatementExecutionHelper::ColumnNameToName(Environment* env,
26202669
return MaybeLocal<Name>();
26212670
}
26222671

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

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

35403586
DCHECK_EQ(row_keys.size(), row_values.size());
35413587
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)