sqlite: expose prepared statement statistics · nodejs/node@72c37b1 · GitHub
Skip to content

Commit 72c37b1

Browse files
geeksilva97aduh95
authored andcommitted
sqlite: expose prepared statement statistics
Signed-off-by: geeksilva97 <edigleyssonsilva@gmail.com> PR-URL: #64541 Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent e3fda69 commit 72c37b1

4 files changed

Lines changed: 353 additions & 6 deletions

File tree

doc/api/sqlite.md

Lines changed: 51 additions & 0 deletions

src/node_sqlite.cc

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,11 @@ Local<DictionaryTemplate> getLazyIterTemplate(Environment* env) {
177177
}
178178
} // namespace
179179

180-
// Helper function to find limit info from JS property name
181-
static constexpr const LimitInfo* GetLimitInfoFromName(std::string_view name) {
182-
for (const auto& info : kLimitMapping) {
180+
// Helper function to look up a mapping entry by its JS-facing name
181+
template <typename T, size_t N>
182+
static constexpr const T* FindByJsName(const std::array<T, N>& mapping,
183+
std::string_view name) {
184+
for (const auto& info : mapping) {
183185
if (name == info.js_name) {
184186
return &info;
185187
}
@@ -793,7 +795,8 @@ Intercepted DatabaseSyncLimits::LimitsGetter(
793795
Isolate* isolate = env->isolate();
794796

795797
Utf8Value prop_name(isolate, property);
796-
const LimitInfo* limit_info = GetLimitInfoFromName(prop_name.ToStringView());
798+
const LimitInfo* limit_info =
799+
FindByJsName(kLimitMapping, prop_name.ToStringView());
797800

798801
if (limit_info == nullptr) {
799802
return Intercepted::kNo; // Unknown property, let default handling occur
@@ -825,7 +828,8 @@ Intercepted DatabaseSyncLimits::LimitsSetter(
825828
Isolate* isolate = env->isolate();
826829

827830
Utf8Value prop_name(isolate, property);
828-
const LimitInfo* limit_info = GetLimitInfoFromName(prop_name.ToStringView());
831+
const LimitInfo* limit_info =
832+
FindByJsName(kLimitMapping, prop_name.ToStringView());
829833

830834
if (limit_info == nullptr) {
831835
return Intercepted::kNo;
@@ -873,7 +877,8 @@ Intercepted DatabaseSyncLimits::LimitsQuery(
873877

874878
Isolate* isolate = info.GetIsolate();
875879
Utf8Value prop_name(isolate, property);
876-
const LimitInfo* limit_info = GetLimitInfoFromName(prop_name.ToStringView());
880+
const LimitInfo* limit_info =
881+
FindByJsName(kLimitMapping, prop_name.ToStringView());
877882

878883
if (!limit_info) {
879884
return Intercepted::kNo;
@@ -2694,6 +2699,7 @@ void StatementSync::Finalize() {
26942699

26952700
void StatementSync::InvalidateColumnNameCache() {
26962701
cached_column_names_.clear();
2702+
cached_column_names_reprepare_count_ = -1;
26972703
}
26982704

26992705
inline bool StatementSync::IsFinalized() {
@@ -3350,6 +3356,60 @@ void StatementSync::ExpandedSQLGetter(const FunctionCallbackInfo<Value>& args) {
33503356
args.GetReturnValue().Set(result);
33513357
}
33523358

3359+
void StatementSync::Stat(const FunctionCallbackInfo<Value>& args) {
3360+
StatementSync* stmt;
3361+
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
3362+
Environment* env = Environment::GetCurrent(args);
3363+
THROW_AND_RETURN_ON_BAD_STATE(
3364+
env, stmt->IsFinalized(), "statement has been finalized");
3365+
Isolate* isolate = env->isolate();
3366+
3367+
if (!args[0]->IsString()) {
3368+
THROW_ERR_INVALID_ARG_TYPE(isolate,
3369+
"The \"counter\" argument must be a string.");
3370+
return;
3371+
}
3372+
3373+
Utf8Value counter(isolate, args[0].As<String>());
3374+
const StatusInfo* status_info =
3375+
FindByJsName(kStatusMapping, counter.ToStringView());
3376+
if (status_info == nullptr) {
3377+
THROW_ERR_INVALID_ARG_VALUE(
3378+
isolate, "The \"counter\" argument is not a valid statistic name.");
3379+
return;
3380+
}
3381+
3382+
// The reset flag is always false; the counter is read without being cleared.
3383+
int value = sqlite3_stmt_status(
3384+
stmt->statement_.get(), status_info->sqlite_status_id, false);
3385+
args.GetReturnValue().Set(Integer::New(isolate, value));
3386+
}
3387+
3388+
void StatementSync::ResetStats(const FunctionCallbackInfo<Value>& args) {
3389+
StatementSync* stmt;
3390+
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
3391+
Environment* env = Environment::GetCurrent(args);
3392+
THROW_AND_RETURN_ON_BAD_STATE(
3393+
env, stmt->IsFinalized(), "statement has been finalized");
3394+
3395+
// sqlite3_stmt_status() resets a single counter per call, so every exposed
3396+
// counter is visited. The returned value is the pre-reset one and is unused.
3397+
// SQLITE_STMTSTATUS_MEMUSED is skipped: it reports current memory usage
3398+
// rather than an accumulated counter, and SQLite ignores the reset flag for
3399+
// it.
3400+
for (const auto& info : kStatusMapping) {
3401+
if (info.sqlite_status_id == SQLITE_STMTSTATUS_MEMUSED) {
3402+
continue;
3403+
}
3404+
sqlite3_stmt_status(stmt->statement_.get(), info.sqlite_status_id, true);
3405+
}
3406+
3407+
// The column name cache is keyed on SQLITE_STMTSTATUS_REPREPARE, which was
3408+
// just zeroed. Without invalidating, a later re-prepare can make the counter
3409+
// match the cached generation again and the stale names would be reused.
3410+
stmt->InvalidateColumnNameCache();
3411+
}
3412+
33533413
void StatementSync::SetAllowBareNamedParameters(
33543414
const FunctionCallbackInfo<Value>& args) {
33553415
StatementSync* stmt;
@@ -3775,6 +3835,8 @@ Local<FunctionTemplate> StatementSync::GetConstructorTemplate(
37753835
tmpl,
37763836
FIXED_ONE_BYTE_STRING(isolate, "expandedSQL"),
37773837
StatementSync::ExpandedSQLGetter);
3838+
SetProtoMethodNoSideEffect(isolate, tmpl, "stat", StatementSync::Stat);
3839+
SetProtoMethod(isolate, tmpl, "resetStats", StatementSync::ResetStats);
37783840
SetProtoMethod(isolate,
37793841
tmpl,
37803842
"setAllowBareNamedParameters",

src/node_sqlite.h

Lines changed: 28 additions & 0 deletions

0 commit comments

Comments
 (0)