Skip to content

Commit 9e85ee2

Browse files
feat: stmt.scanStats()
1 parent d1646af commit 9e85ee2

File tree

4 files changed

+147
-0
lines changed

4 files changed

+147
-0
lines changed

deps/sqlcipher/sqlcipher.gyp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
'SQLITE_EXTRA_SHUTDOWN=sqlcipher_extra_shutdown',
7474
],
7575
'conditions': [
76+
# Link with extension
7677
['OS == "win"', {
7778
'defines': [
7879
'WIN32'
@@ -96,6 +97,18 @@
9697
]
9798
},
9899
}],
100+
101+
# Profiling
102+
["\"<!(node -p \"require('../../package.json').version\")\".endswith(\"-profiling\")", {
103+
'defines': [
104+
'SQLITE_ENABLE_STMT_SCANSTATUS'
105+
],
106+
'direct_dependent_settings': {
107+
'defines': [
108+
'SQLITE_ENABLE_STMT_SCANSTATUS'
109+
],
110+
},
111+
}],
99112
],
100113
'configurations': {
101114
'Debug': {

lib/index.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const addon = bindings<{
3939
cache: Array<SqliteValue<Options>> | undefined,
4040
isGet: boolean,
4141
): Array<SqliteValue<Options>>;
42+
statementScanStats(stmt: NativeStatement): Array<ScanStats>;
4243
statementClose(stmt: NativeStatement): void;
4344

4445
databaseOpen(path: string): NativeDatabase;
@@ -225,6 +226,20 @@ class Statement<Options extends StatementOptions = object> {
225226
return result as unknown as Array<Row>;
226227
}
227228

229+
/**
230+
* Report collected performance statics for the statement.
231+
*
232+
* @returns A list of objects describing the performance of the query.
233+
*
234+
* @see {@link https://www.sqlite.org/profile.html}
235+
*/
236+
public scanStats(): Array<ScanStats> {
237+
if (this.#native === undefined) {
238+
throw new Error('Statement closed');
239+
}
240+
return addon.statementScanStats(this.#native);
241+
}
242+
228243
/**
229244
* Close the statement and release the used memory.
230245
*/
@@ -302,6 +317,20 @@ export type PragmaResult<Options extends PragmaOptions> = Options extends {
302317
? RowType<{ pluck: true }> | undefined
303318
: Array<RowType<object>>;
304319

320+
/**
321+
* An entry of result array of `stmt.scanStats()` method.
322+
*
323+
* Value of `-1` indicates that the field is not available for a given entry.
324+
*/
325+
export type ScanStats = Readonly<{
326+
id: number;
327+
parent: number;
328+
cycles: number;
329+
loops: number;
330+
rows: number;
331+
explain: string | null;
332+
}>;
333+
305334
/** @internal */
306335
type TransactionStatement = Statement<{ persistent: true; pluck: true }>;
307336

src/addon.cc

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ Napi::Object Statement::Init(Napi::Env env, Napi::Object exports) {
321321
exports["statementClose"] = Napi::Function::New(env, &Statement::Close);
322322
exports["statementRun"] = Napi::Function::New(env, &Statement::Run);
323323
exports["statementStep"] = Napi::Function::New(env, &Statement::Step);
324+
exports["statementScanStats"] =
325+
Napi::Function::New(env, &Statement::ScanStats);
324326
return exports;
325327
}
326328

@@ -562,6 +564,108 @@ Napi::Value Statement::Step(const Napi::CallbackInfo& info) {
562564
return result;
563565
}
564566

567+
// Only enabled on `-profiling` npm package versions
568+
569+
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
570+
Napi::Value Statement::ScanStats(const Napi::CallbackInfo& info) {
571+
auto env = info.Env();
572+
573+
auto stmt = FromExternal(info[0]);
574+
if (stmt == nullptr) {
575+
return Napi::Value();
576+
}
577+
578+
sqlite3_int64 total_cycles = 0;
579+
int r = sqlite3_stmt_scanstatus_v2(stmt->handle_, -1, SQLITE_SCANSTAT_NCYCLE,
580+
SQLITE_SCANSTAT_COMPLEX, &total_cycles);
581+
582+
if (r != SQLITE_OK) {
583+
return stmt->db_->ThrowSqliteError(env, r);
584+
}
585+
586+
auto results = Napi::Array::New(env, 1);
587+
588+
auto root = Napi::Object::New(env);
589+
590+
root["id"] = 0;
591+
root["parent"] = -1;
592+
root["cycles"] = total_cycles;
593+
root["loops"] = -1;
594+
root["rows"] = -1;
595+
root["explain"] = env.Null();
596+
results[static_cast<uint32_t>(0)] = root;
597+
598+
for (int idx = 0; r == SQLITE_OK; idx++) {
599+
int id = 0;
600+
int parent = 0;
601+
sqlite3_int64 cycles = 0;
602+
sqlite3_int64 loops = 0;
603+
sqlite3_int64 rows = 0;
604+
const char* explain = nullptr;
605+
606+
r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_SELECTID,
607+
SQLITE_SCANSTAT_COMPLEX, &id);
608+
if (r != SQLITE_OK) {
609+
break;
610+
}
611+
r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_PARENTID,
612+
SQLITE_SCANSTAT_COMPLEX, &parent);
613+
if (r != SQLITE_OK) {
614+
break;
615+
}
616+
r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_NCYCLE,
617+
SQLITE_SCANSTAT_COMPLEX, &cycles);
618+
if (r != SQLITE_OK) {
619+
break;
620+
}
621+
r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_NLOOP,
622+
SQLITE_SCANSTAT_COMPLEX, &loops);
623+
if (r != SQLITE_OK) {
624+
break;
625+
}
626+
r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_NVISIT,
627+
SQLITE_SCANSTAT_COMPLEX, &rows);
628+
if (r != SQLITE_OK) {
629+
break;
630+
}
631+
r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_EXPLAIN,
632+
SQLITE_SCANSTAT_COMPLEX, &explain);
633+
if (r != SQLITE_OK) {
634+
break;
635+
}
636+
637+
auto result = Napi::Object::New(env);
638+
639+
result["id"] = id;
640+
result["parent"] = parent;
641+
result["cycles"] = cycles;
642+
result["loops"] = loops;
643+
result["rows"] = rows;
644+
if (explain == nullptr) {
645+
result["explain"] = env.Null();
646+
} else {
647+
result["explain"] = explain;
648+
}
649+
650+
results[static_cast<uint32_t>(idx + 1)] = result;
651+
}
652+
653+
// SQLITE_ERROR is returned when `idx` is out of range
654+
if (r != SQLITE_ERROR) {
655+
return stmt->db_->ThrowSqliteError(env, r);
656+
}
657+
658+
return results;
659+
}
660+
#else // !SQLITE_ENABLE_STMT_SCANSTATUS
661+
Napi::Value Statement::ScanStats(const Napi::CallbackInfo& info) {
662+
auto env = info.Env();
663+
664+
NAPI_THROW(Napi::Error::New(env, "Not available in production builds"),
665+
Napi::Value());
666+
}
667+
#endif // !SQLITE_ENABLE_STMT_SCANSTATUS
668+
565669
bool Statement::BindParams(Napi::Env env, Napi::Value params) {
566670
int key_count = sqlite3_bind_parameter_count(handle_);
567671

src/addon.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ class Statement {
131131
static Napi::Value Close(const Napi::CallbackInfo& info);
132132
static Napi::Value Run(const Napi::CallbackInfo& info);
133133
static Napi::Value Step(const Napi::CallbackInfo& info);
134+
static Napi::Value ScanStats(const Napi::CallbackInfo& info);
134135

135136
bool BindParams(Napi::Env env, Napi::Value params);
136137

0 commit comments

Comments
 (0)