Skip to content

Commit 4fcb1ed

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 da5efc4 commit 4fcb1ed

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"
@@ -55,6 +56,19 @@ using v8::TryCatch;
5556
using v8::Uint8Array;
5657
using v8::Value;
5758

59+
inline MaybeLocal<String> Utf8StringMaybeOneByte(Isolate* isolate,
60+
const char* data,
61+
size_t length) {
62+
int len = static_cast<int>(length);
63+
if (simdutf::validate_ascii(data, length)) {
64+
return String::NewFromOneByte(isolate,
65+
reinterpret_cast<const uint8_t*>(data),
66+
NewStringType::kNormal,
67+
len);
68+
}
69+
return String::NewFromUtf8(isolate, data, NewStringType::kNormal, len);
70+
}
71+
5872
#define CHECK_ERROR_OR_THROW(isolate, db, expr, expected, ret) \
5973
do { \
6074
int r_ = (expr); \
@@ -97,7 +111,8 @@ using v8::Value;
97111
case SQLITE_TEXT: { \
98112
const char* v = \
99113
reinterpret_cast<const char*>(sqlite3_##from##_text(__VA_ARGS__)); \
100-
(result) = String::NewFromUtf8((isolate), v).As<Value>(); \
114+
int v_len = sqlite3_##from##_bytes(__VA_ARGS__); \
115+
(result) = Utf8StringMaybeOneByte((isolate), v, v_len).As<Value>(); \
101116
break; \
102117
} \
103118
case SQLITE_NULL: { \
@@ -2147,6 +2162,7 @@ StatementSync::~StatementSync() {
21472162
void StatementSync::Finalize() {
21482163
sqlite3_finalize(statement_);
21492164
statement_ = nullptr;
2165+
cached_column_names_.clear();
21502166
}
21512167

21522168
inline bool StatementSync::IsFinalized() {
@@ -2325,7 +2341,40 @@ MaybeLocal<Name> StatementSync::ColumnNameToName(const int column) {
23252341
return MaybeLocal<Name>();
23262342
}
23272343

2328-
return String::NewFromUtf8(env()->isolate(), col_name).As<Name>();
2344+
return String::NewFromUtf8(
2345+
env()->isolate(), col_name, NewStringType::kInternalized)
2346+
.As<Name>();
2347+
}
2348+
2349+
bool StatementSync::GetCachedColumnNames(LocalVector<Name>* keys) {
2350+
Isolate* isolate = env()->isolate();
2351+
2352+
int reprepare_count =
2353+
sqlite3_stmt_status(statement_, SQLITE_STMTSTATUS_REPREPARE, 0);
2354+
if (reprepare_count != cached_column_names_reprepare_count_) {
2355+
cached_column_names_.clear();
2356+
int num_cols = sqlite3_column_count(statement_);
2357+
if (num_cols == 0) {
2358+
cached_column_names_reprepare_count_ = reprepare_count;
2359+
return true;
2360+
}
2361+
cached_column_names_.reserve(num_cols);
2362+
for (int i = 0; i < num_cols; ++i) {
2363+
Local<Name> key;
2364+
if (!ColumnNameToName(i).ToLocal(&key)) {
2365+
cached_column_names_.clear();
2366+
return false;
2367+
}
2368+
cached_column_names_.emplace_back(Global<Name>(isolate, key));
2369+
}
2370+
cached_column_names_reprepare_count_ = reprepare_count;
2371+
}
2372+
2373+
keys->reserve(cached_column_names_.size());
2374+
for (const auto& name : cached_column_names_) {
2375+
keys->emplace_back(name.Get(isolate));
2376+
}
2377+
return true;
23292378
}
23302379

23312380
MaybeLocal<Value> StatementExecutionHelper::ColumnToValue(Environment* env,
@@ -2347,7 +2396,9 @@ MaybeLocal<Name> StatementExecutionHelper::ColumnNameToName(Environment* env,
23472396
return MaybeLocal<Name>();
23482397
}
23492398

2350-
return String::NewFromUtf8(env->isolate(), col_name).As<Name>();
2399+
return String::NewFromUtf8(
2400+
env->isolate(), col_name, NewStringType::kInternalized)
2401+
.As<Name>();
23512402
}
23522403

23532404
void StatementSync::MemoryInfo(MemoryTracker* tracker) const {}
@@ -3251,12 +3302,7 @@ void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) {
32513302
if (iter->stmt_->return_arrays_) {
32523303
row_value = Array::New(isolate, row_values.data(), row_values.size());
32533304
} else {
3254-
row_keys.reserve(num_cols);
3255-
for (int i = 0; i < num_cols; ++i) {
3256-
Local<Name> key;
3257-
if (!iter->stmt_->ColumnNameToName(i).ToLocal(&key)) return;
3258-
row_keys.emplace_back(key);
3259-
}
3305+
if (!iter->stmt_->GetCachedColumnNames(&row_keys)) return;
32603306

32613307
DCHECK_EQ(row_keys.size(), row_values.size());
32623308
row_value = Object::New(

src/node_sqlite.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <list>
1313
#include <map>
1414
#include <unordered_set>
15+
#include <vector>
1516

1617
namespace node {
1718
namespace sqlite {
@@ -228,6 +229,7 @@ class StatementSync : public BaseObject {
228229
static void SetReturnArrays(const v8::FunctionCallbackInfo<v8::Value>& args);
229230
v8::MaybeLocal<v8::Value> ColumnToValue(const int column);
230231
v8::MaybeLocal<v8::Name> ColumnNameToName(const int column);
232+
bool GetCachedColumnNames(v8::LocalVector<v8::Name>* keys);
231233
void Finalize();
232234
bool IsFinalized();
233235

@@ -243,6 +245,8 @@ class StatementSync : public BaseObject {
243245
bool allow_bare_named_params_;
244246
bool allow_unknown_named_params_;
245247
std::optional<std::map<std::string, std::string>> bare_named_params_;
248+
std::vector<v8::Global<v8::Name>> cached_column_names_;
249+
int cached_column_names_reprepare_count_ = -1;
246250
bool BindParams(const v8::FunctionCallbackInfo<v8::Value>& args);
247251
bool BindValue(const v8::Local<v8::Value>& value, const int index);
248252

0 commit comments

Comments
 (0)