Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions deps/sqlcipher/sqlcipher.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
'SQLITE_EXTRA_SHUTDOWN=sqlcipher_extra_shutdown',
],
'conditions': [
# Link with extension
['OS == "win"', {
'defines': [
'WIN32'
Expand All @@ -96,6 +97,18 @@
]
},
}],

# Profiling
["\"<!(node -p \"require('../../package.json').version\")\".endswith(\"-profiling\")", {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could maybe add a matrix to the .github/workflows/publish.yaml#publish job so that each new tag creates two separate versions x.x.x and x.x.x-profiling

'defines': [
'SQLITE_ENABLE_STMT_SCANSTATUS'
],
'direct_dependent_settings': {
'defines': [
'SQLITE_ENABLE_STMT_SCANSTATUS'
],
},
}],
],
'configurations': {
'Debug': {
Expand Down
29 changes: 29 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const addon = bindings<{
cache: Array<SqliteValue<Options>> | undefined,
isGet: boolean,
): Array<SqliteValue<Options>>;
statementScanStats(stmt: NativeStatement): Array<ScanStats>;
statementClose(stmt: NativeStatement): void;

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

/**
* Report collected performance statics for the statement.
*
* @returns A list of objects describing the performance of the query.
*
* @see {@link https://www.sqlite.org/profile.html}
*/
public scanStats(): Array<ScanStats> {
if (this.#native === undefined) {
throw new Error('Statement closed');
}
return addon.statementScanStats(this.#native);
}

/**
* Close the statement and release the used memory.
*/
Expand Down Expand Up @@ -302,6 +317,20 @@ export type PragmaResult<Options extends PragmaOptions> = Options extends {
? RowType<{ pluck: true }> | undefined
: Array<RowType<object>>;

/**
* An entry of result array of `stmt.scanStats()` method.
*
* Value of `-1` indicates that the field is not available for a given entry.
*/
export type ScanStats = Readonly<{
id: number;
parent: number;
cycles: number;
loops: number;
rows: number;
explain: string | null;
}>;

/** @internal */
type TransactionStatement = Statement<{ persistent: true; pluck: true }>;

Expand Down
104 changes: 104 additions & 0 deletions src/addon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ Napi::Object Statement::Init(Napi::Env env, Napi::Object exports) {
exports["statementClose"] = Napi::Function::New(env, &Statement::Close);
exports["statementRun"] = Napi::Function::New(env, &Statement::Run);
exports["statementStep"] = Napi::Function::New(env, &Statement::Step);
exports["statementScanStats"] =
Napi::Function::New(env, &Statement::ScanStats);
return exports;
}

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

// Only enabled on `-profiling` npm package versions

#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
Napi::Value Statement::ScanStats(const Napi::CallbackInfo& info) {
auto env = info.Env();

auto stmt = FromExternal(info[0]);
if (stmt == nullptr) {
return Napi::Value();
}

sqlite3_int64 total_cycles = 0;
int r = sqlite3_stmt_scanstatus_v2(stmt->handle_, -1, SQLITE_SCANSTAT_NCYCLE,
SQLITE_SCANSTAT_COMPLEX, &total_cycles);

if (r != SQLITE_OK) {
return stmt->db_->ThrowSqliteError(env, r);
}

auto results = Napi::Array::New(env, 1);

auto root = Napi::Object::New(env);

root["id"] = 0;
root["parent"] = -1;
root["cycles"] = total_cycles;
root["loops"] = -1;
root["rows"] = -1;
root["explain"] = env.Null();
results[static_cast<uint32_t>(0)] = root;

for (int idx = 0; r == SQLITE_OK; idx++) {
int id = 0;
int parent = 0;
sqlite3_int64 cycles = 0;
sqlite3_int64 loops = 0;
sqlite3_int64 rows = 0;
const char* explain = nullptr;

r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_SELECTID,
SQLITE_SCANSTAT_COMPLEX, &id);
if (r != SQLITE_OK) {
break;
}
r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_PARENTID,
SQLITE_SCANSTAT_COMPLEX, &parent);
if (r != SQLITE_OK) {
break;
}
r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_NCYCLE,
SQLITE_SCANSTAT_COMPLEX, &cycles);
if (r != SQLITE_OK) {
break;
}
r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_NLOOP,
SQLITE_SCANSTAT_COMPLEX, &loops);
if (r != SQLITE_OK) {
break;
}
r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_NVISIT,
SQLITE_SCANSTAT_COMPLEX, &rows);
if (r != SQLITE_OK) {
break;
}
r = sqlite3_stmt_scanstatus_v2(stmt->handle_, idx, SQLITE_SCANSTAT_EXPLAIN,
SQLITE_SCANSTAT_COMPLEX, &explain);
if (r != SQLITE_OK) {
break;
}

auto result = Napi::Object::New(env);

result["id"] = id;
result["parent"] = parent;
result["cycles"] = cycles;
result["loops"] = loops;
result["rows"] = rows;
if (explain == nullptr) {
result["explain"] = env.Null();
} else {
result["explain"] = explain;
}

results[static_cast<uint32_t>(idx + 1)] = result;
}

// SQLITE_ERROR is returned when `idx` is out of range
if (r != SQLITE_ERROR) {
return stmt->db_->ThrowSqliteError(env, r);
}

return results;
}
#else // !SQLITE_ENABLE_STMT_SCANSTATUS
Napi::Value Statement::ScanStats(const Napi::CallbackInfo& info) {
auto env = info.Env();

NAPI_THROW(Napi::Error::New(env, "Not available in production builds"),
Napi::Value());
}
#endif // !SQLITE_ENABLE_STMT_SCANSTATUS

bool Statement::BindParams(Napi::Env env, Napi::Value params) {
int key_count = sqlite3_bind_parameter_count(handle_);

Expand Down
1 change: 1 addition & 0 deletions src/addon.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ class Statement {
static Napi::Value Close(const Napi::CallbackInfo& info);
static Napi::Value Run(const Napi::CallbackInfo& info);
static Napi::Value Step(const Napi::CallbackInfo& info);
static Napi::Value ScanStats(const Napi::CallbackInfo& info);

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

Expand Down
Loading