Skip to content

Commit 3326b60

Browse files
authored
Merge pull request #9 from rocicorp/scanstatus
Enable scanstatus
2 parents 3eb132a + 2845da5 commit 3326b60

File tree

9 files changed

+271
-4
lines changed

9 files changed

+271
-4
lines changed

deps/defines.gypi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
'SQLITE_ENABLE_MATH_FUNCTIONS',
2828
'SQLITE_ENABLE_RTREE',
2929
'SQLITE_ENABLE_STAT4',
30+
'SQLITE_ENABLE_STMT_SCANSTATUS',
3031
'SQLITE_ENABLE_UPDATE_DELETE_LIMIT',
3132
'SQLITE_LIKE_DOESNT_MATCH_BLOBS',
3233
'SQLITE_OMIT_DEPRECATED',

lib/database.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,16 @@ Database.prototype.defaultSafeIntegers = wrappers.defaultSafeIntegers;
8686
Database.prototype.unsafeMode = wrappers.unsafeMode;
8787
Database.prototype[util.inspect] = require('./methods/inspect');
8888

89+
// Export SQLITE_SCANSTAT_* constants from native addon
90+
const nativeAddon = DEFAULT_ADDON || require('bindings')('better_sqlite3.node');
91+
Database.SQLITE_SCANSTAT_NLOOP = nativeAddon.SQLITE_SCANSTAT_NLOOP;
92+
Database.SQLITE_SCANSTAT_NVISIT = nativeAddon.SQLITE_SCANSTAT_NVISIT;
93+
Database.SQLITE_SCANSTAT_EST = nativeAddon.SQLITE_SCANSTAT_EST;
94+
Database.SQLITE_SCANSTAT_NAME = nativeAddon.SQLITE_SCANSTAT_NAME;
95+
Database.SQLITE_SCANSTAT_EXPLAIN = nativeAddon.SQLITE_SCANSTAT_EXPLAIN;
96+
Database.SQLITE_SCANSTAT_SELECTID = nativeAddon.SQLITE_SCANSTAT_SELECTID;
97+
Database.SQLITE_SCANSTAT_PARENTID = nativeAddon.SQLITE_SCANSTAT_PARENTID;
98+
Database.SQLITE_SCANSTAT_NCYCLE = nativeAddon.SQLITE_SCANSTAT_NCYCLE;
99+
Database.SQLITE_SCANSTAT_COMPLEX = nativeAddon.SQLITE_SCANSTAT_COMPLEX;
100+
89101
module.exports = Database;

lib/index.d.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ type VariableArgFunction = (...params: any[]) => unknown;
55
type ArgumentTypes<F extends VariableArgFunction> = F extends (...args: infer A) => unknown ? A : never;
66
type ElementOf<T> = T extends Array<infer E> ? E : T;
77

8+
declare const enum ScanStatOpcode {
9+
SQLITE_SCANSTAT_NLOOP = 0,
10+
SQLITE_SCANSTAT_NVISIT = 1,
11+
SQLITE_SCANSTAT_EST = 2,
12+
SQLITE_SCANSTAT_NAME = 3,
13+
SQLITE_SCANSTAT_EXPLAIN = 4,
14+
SQLITE_SCANSTAT_SELECTID = 5,
15+
SQLITE_SCANSTAT_PARENTID = 6,
16+
SQLITE_SCANSTAT_NCYCLE = 7,
17+
}
18+
819
declare namespace BetterSqlite3 {
920
interface Statement<BindParameters extends unknown[], Result = unknown> {
1021
database: Database;
@@ -23,6 +34,15 @@ declare namespace BetterSqlite3 {
2334
bind(...params: BindParameters): this;
2435
columns(): ColumnDefinition[];
2536
safeIntegers(toggleState?: boolean): this;
37+
scanStatusV2(idx: number, opcode: ScanStatOpcode.SQLITE_SCANSTAT_NAME, resetFlag: number): string | undefined;
38+
scanStatusV2(idx: number, opcode: ScanStatOpcode.SQLITE_SCANSTAT_EXPLAIN, resetFlag: number): string | undefined;
39+
scanStatusV2(idx: number, opcode: ScanStatOpcode.SQLITE_SCANSTAT_NLOOP, resetFlag: number): number | undefined;
40+
scanStatusV2(idx: number, opcode: ScanStatOpcode.SQLITE_SCANSTAT_NVISIT, resetFlag: number): number | undefined;
41+
scanStatusV2(idx: number, opcode: ScanStatOpcode.SQLITE_SCANSTAT_EST, resetFlag: number): number | undefined;
42+
scanStatusV2(idx: number, opcode: ScanStatOpcode.SQLITE_SCANSTAT_SELECTID, resetFlag: number): number | undefined;
43+
scanStatusV2(idx: number, opcode: ScanStatOpcode.SQLITE_SCANSTAT_PARENTID, resetFlag: number): number | undefined;
44+
scanStatusV2(idx: number, opcode: ScanStatOpcode.SQLITE_SCANSTAT_NCYCLE, resetFlag: number): number | undefined;
45+
scanStatusV2(idx: number, opcode: ScanStatOpcode, resetFlag: number): number | string | undefined;
2646
}
2747

2848
interface ColumnDefinition {
@@ -89,6 +109,16 @@ declare namespace BetterSqlite3 {
89109
prototype: Database;
90110

91111
SqliteError: typeof SqliteError;
112+
113+
// scanstatus constants
114+
SQLITE_SCANSTAT_NLOOP: ScanStatOpcode.SQLITE_SCANSTAT_NLOOP;
115+
SQLITE_SCANSTAT_NVISIT: ScanStatOpcode.SQLITE_SCANSTAT_NVISIT;
116+
SQLITE_SCANSTAT_EST: ScanStatOpcode.SQLITE_SCANSTAT_EST;
117+
SQLITE_SCANSTAT_NAME: ScanStatOpcode.SQLITE_SCANSTAT_NAME;
118+
SQLITE_SCANSTAT_EXPLAIN: ScanStatOpcode.SQLITE_SCANSTAT_EXPLAIN;
119+
SQLITE_SCANSTAT_SELECTID: ScanStatOpcode.SQLITE_SCANSTAT_SELECTID;
120+
SQLITE_SCANSTAT_PARENTID: ScanStatOpcode.SQLITE_SCANSTAT_PARENTID;
121+
SQLITE_SCANSTAT_NCYCLE: ScanStatOpcode.SQLITE_SCANSTAT_NCYCLE;
92122
}
93123
}
94124

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rocicorp/zero-sqlite3",
3-
"version": "1.0.8",
3+
"version": "1.0.9",
44
"description": "better-sqlite3 on bedrock",
55
"homepage": "https://github.com/rocicorp/zero-sqlite3",
66
"author": "Rocicorp",

src/better_sqlite3.cpp

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,17 @@ NODE_MODULE_INIT(/* exports, context */) {
7373
exports->Set(context, InternalizedFromLatin1(isolate, "Backup"), Backup::Init(isolate, data)).FromJust();
7474
exports->Set(context, InternalizedFromLatin1(isolate, "setErrorConstructor"), v8::FunctionTemplate::New(isolate, Addon::JS_setErrorConstructor, data)->GetFunction(context).ToLocalChecked()).FromJust();
7575

76+
// Export SQLITE_SCANSTAT_* constants
77+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_NLOOP"), v8::Int32::New(isolate, SQLITE_SCANSTAT_NLOOP)).FromJust();
78+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_NVISIT"), v8::Int32::New(isolate, SQLITE_SCANSTAT_NVISIT)).FromJust();
79+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_EST"), v8::Int32::New(isolate, SQLITE_SCANSTAT_EST)).FromJust();
80+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_NAME"), v8::Int32::New(isolate, SQLITE_SCANSTAT_NAME)).FromJust();
81+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_EXPLAIN"), v8::Int32::New(isolate, SQLITE_SCANSTAT_EXPLAIN)).FromJust();
82+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_SELECTID"), v8::Int32::New(isolate, SQLITE_SCANSTAT_SELECTID)).FromJust();
83+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_PARENTID"), v8::Int32::New(isolate, SQLITE_SCANSTAT_PARENTID)).FromJust();
84+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_NCYCLE"), v8::Int32::New(isolate, SQLITE_SCANSTAT_NCYCLE)).FromJust();
85+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_COMPLEX"), v8::Int32::New(isolate, SQLITE_SCANSTAT_COMPLEX)).FromJust();
86+
7687
// Store addon instance data.
7788
addon->Statement.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "Statement")).ToLocalChecked().As<v8::Function>());
7889
addon->StatementIterator.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "StatementIterator")).ToLocalChecked().As<v8::Function>());
@@ -773,6 +784,7 @@ v8::Local <v8 :: Function> Statement::Init (v8::Isolate * isolate, v8::Local <v8
773784
SetPrototypeMethod(isolate, data, t, "raw", JS_raw);
774785
SetPrototypeMethod(isolate, data, t, "safeIntegers", JS_safeIntegers);
775786
SetPrototypeMethod(isolate, data, t, "columns", JS_columns);
787+
SetPrototypeMethod(isolate, data, t, "scanStatusV2", JS_scanStatusV2);
776788
SetPrototypeGetter(isolate, data, t, "busy", JS_busy);
777789
return t->GetFunction( isolate -> GetCurrentContext ( ) ).ToLocalChecked();
778790
}
@@ -1083,9 +1095,59 @@ void Statement::JS_columns (v8::FunctionCallbackInfo <v8 :: Value> const & info)
10831095

10841096
info.GetReturnValue().Set(columns);
10851097
}
1086-
#line 321 "./src/objects/statement.lzz"
1098+
#line 322 "./src/objects/statement.lzz"
1099+
void Statement::JS_scanStatusV2 (v8::FunctionCallbackInfo <v8 :: Value> const & info)
1100+
#line 322 "./src/objects/statement.lzz"
1101+
{
1102+
Statement* stmt = node :: ObjectWrap :: Unwrap <Statement>(info.This());
1103+
if (!stmt->db->GetState()->open) return ThrowTypeError("The database connection is not open");
1104+
if (!stmt->alive) return ThrowTypeError("The statement has been finalized and can no longer be used");
1105+
1106+
int idx; if (info.Length() < (0 + 1) || !info[0]->IsInt32()) return ThrowTypeError("Expected " "first" " argument to be " "a 32-bit signed integer"); idx = (info[0].As<v8::Int32>()->Value());;
1107+
int iScanStatusOp; if (info.Length() < (1 + 1) || !info[1]->IsInt32()) return ThrowTypeError("Expected " "second" " argument to be " "a 32-bit signed integer"); iScanStatusOp = (info[1].As<v8::Int32>()->Value());;
1108+
int flags; if (info.Length() < (2 + 1) || !info[2]->IsInt32()) return ThrowTypeError("Expected " "third" " argument to be " "a 32-bit signed integer"); flags = (info[2].As<v8::Int32>()->Value());;
1109+
1110+
v8::Isolate* isolate = info.GetIsolate();;
1111+
1112+
// Based on iScanStatusOp, we know what type of output to expect
1113+
int rc;
1114+
if (iScanStatusOp == 0 || iScanStatusOp == 1 || iScanStatusOp == 5 || iScanStatusOp == 6 || iScanStatusOp == 7) {
1115+
// NLOOP, NVISIT, SELECTID, PARENTID, NCYCLE - return sqlite3_int64
1116+
sqlite3_int64 iOut;
1117+
rc = sqlite3_stmt_scanstatus_v2(stmt->handle, idx, iScanStatusOp, flags, (void*)&iOut);
1118+
if (rc == 0) {
1119+
info.GetReturnValue().Set(stmt->safe_ints
1120+
? v8::BigInt::New(isolate, iOut).As<v8::Value>()
1121+
: v8::Number::New(isolate, (double)iOut).As<v8::Value>());
1122+
return;
1123+
}
1124+
} else if (iScanStatusOp == 2) {
1125+
// EST - return double
1126+
double dOut;
1127+
rc = sqlite3_stmt_scanstatus_v2(stmt->handle, idx, iScanStatusOp, flags, (void*)&dOut);
1128+
if (rc == 0) {
1129+
info.GetReturnValue().Set(v8::Number::New(isolate, dOut));
1130+
return;
1131+
}
1132+
} else if (iScanStatusOp == 3 || iScanStatusOp == 4) {
1133+
// NAME, EXPLAIN - return const char*
1134+
const char* zOut;
1135+
rc = sqlite3_stmt_scanstatus_v2(stmt->handle, idx, iScanStatusOp, flags, (void*)&zOut);
1136+
if (rc == 0 && zOut != NULL) {
1137+
info.GetReturnValue().Set(StringFromUtf8(isolate, zOut, -1));
1138+
return;
1139+
} else if (rc == 0) {
1140+
info.GetReturnValue().Set(v8::Null(isolate));
1141+
return;
1142+
}
1143+
}
1144+
1145+
// Error or invalid operation - return undefined
1146+
info.GetReturnValue().Set(v8::Undefined(isolate));
1147+
}
1148+
#line 370 "./src/objects/statement.lzz"
10871149
void Statement::JS_busy (v8::Local <v8 :: Name> _, v8::PropertyCallbackInfo <v8 :: Value> const & info)
1088-
#line 321 "./src/objects/statement.lzz"
1150+
#line 370 "./src/objects/statement.lzz"
10891151
{
10901152
Statement* stmt = node :: ObjectWrap :: Unwrap <Statement>(info.This());
10911153
info.GetReturnValue().Set(stmt->alive && stmt->locked);

src/better_sqlite3.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,9 @@ class Statement : public node::ObjectWrap
362362
static void JS_safeIntegers (v8::FunctionCallbackInfo <v8 :: Value> const & info);
363363
#line 278 "./src/objects/statement.lzz"
364364
static void JS_columns (v8::FunctionCallbackInfo <v8 :: Value> const & info);
365-
#line 321 "./src/objects/statement.lzz"
365+
#line 322 "./src/objects/statement.lzz"
366+
static void JS_scanStatusV2 (v8::FunctionCallbackInfo <v8 :: Value> const & info);
367+
#line 370 "./src/objects/statement.lzz"
366368
static void JS_busy (v8::Local <v8 :: Name> _, v8::PropertyCallbackInfo <v8 :: Value> const & info);
367369
#line 326 "./src/objects/statement.lzz"
368370
Database * const db;

src/better_sqlite3.lzz

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ NODE_MODULE_INIT(/* exports, context */) {
8080
exports->Set(context, InternalizedFromLatin1(isolate, "Backup"), Backup::Init(isolate, data)).FromJust();
8181
exports->Set(context, InternalizedFromLatin1(isolate, "setErrorConstructor"), v8::FunctionTemplate::New(isolate, Addon::JS_setErrorConstructor, data)->GetFunction(context).ToLocalChecked()).FromJust();
8282

83+
// Export SQLITE_SCANSTAT_* constants
84+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_NLOOP"), v8::Int32::New(isolate, SQLITE_SCANSTAT_NLOOP)).FromJust();
85+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_NVISIT"), v8::Int32::New(isolate, SQLITE_SCANSTAT_NVISIT)).FromJust();
86+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_EST"), v8::Int32::New(isolate, SQLITE_SCANSTAT_EST)).FromJust();
87+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_NAME"), v8::Int32::New(isolate, SQLITE_SCANSTAT_NAME)).FromJust();
88+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_EXPLAIN"), v8::Int32::New(isolate, SQLITE_SCANSTAT_EXPLAIN)).FromJust();
89+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_SELECTID"), v8::Int32::New(isolate, SQLITE_SCANSTAT_SELECTID)).FromJust();
90+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_PARENTID"), v8::Int32::New(isolate, SQLITE_SCANSTAT_PARENTID)).FromJust();
91+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_NCYCLE"), v8::Int32::New(isolate, SQLITE_SCANSTAT_NCYCLE)).FromJust();
92+
exports->Set(context, InternalizedFromLatin1(isolate, "SQLITE_SCANSTAT_COMPLEX"), v8::Int32::New(isolate, SQLITE_SCANSTAT_COMPLEX)).FromJust();
93+
8394
// Store addon instance data.
8495
addon->Statement.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "Statement")).ToLocalChecked().As<v8::Function>());
8596
addon->StatementIterator.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "StatementIterator")).ToLocalChecked().As<v8::Function>());

src/objects/statement.lzz

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public:
1313
SetPrototypeMethod(isolate, data, t, "raw", JS_raw);
1414
SetPrototypeMethod(isolate, data, t, "safeIntegers", JS_safeIntegers);
1515
SetPrototypeMethod(isolate, data, t, "columns", JS_columns);
16+
SetPrototypeMethod(isolate, data, t, "scanStatusV2", JS_scanStatusV2);
1617
SetPrototypeGetter(isolate, data, t, "busy", JS_busy);
1718
return t->GetFunction(OnlyContext).ToLocalChecked();
1819
}
@@ -318,6 +319,54 @@ private:
318319
info.GetReturnValue().Set(columns);
319320
}
320321

322+
NODE_METHOD(JS_scanStatusV2) {
323+
Statement* stmt = Unwrap<Statement>(info.This());
324+
REQUIRE_DATABASE_OPEN(stmt->db->GetState());
325+
if (!stmt->alive) return ThrowTypeError("The statement has been finalized and can no longer be used");
326+
327+
REQUIRE_ARGUMENT_INT32(first, int idx);
328+
REQUIRE_ARGUMENT_INT32(second, int iScanStatusOp);
329+
REQUIRE_ARGUMENT_INT32(third, int flags);
330+
331+
UseIsolate;
332+
333+
// Based on iScanStatusOp, we know what type of output to expect
334+
int rc;
335+
if (iScanStatusOp == 0 || iScanStatusOp == 1 || iScanStatusOp == 5 || iScanStatusOp == 6 || iScanStatusOp == 7) {
336+
// NLOOP, NVISIT, SELECTID, PARENTID, NCYCLE - return sqlite3_int64
337+
sqlite3_int64 iOut;
338+
rc = sqlite3_stmt_scanstatus_v2(stmt->handle, idx, iScanStatusOp, flags, (void*)&iOut);
339+
if (rc == 0) {
340+
info.GetReturnValue().Set(stmt->safe_ints
341+
? v8::BigInt::New(isolate, iOut).As<v8::Value>()
342+
: v8::Number::New(isolate, (double)iOut).As<v8::Value>());
343+
return;
344+
}
345+
} else if (iScanStatusOp == 2) {
346+
// EST - return double
347+
double dOut;
348+
rc = sqlite3_stmt_scanstatus_v2(stmt->handle, idx, iScanStatusOp, flags, (void*)&dOut);
349+
if (rc == 0) {
350+
info.GetReturnValue().Set(v8::Number::New(isolate, dOut));
351+
return;
352+
}
353+
} else if (iScanStatusOp == 3 || iScanStatusOp == 4) {
354+
// NAME, EXPLAIN - return const char*
355+
const char* zOut;
356+
rc = sqlite3_stmt_scanstatus_v2(stmt->handle, idx, iScanStatusOp, flags, (void*)&zOut);
357+
if (rc == 0 && zOut != NULL) {
358+
info.GetReturnValue().Set(StringFromUtf8(isolate, zOut, -1));
359+
return;
360+
} else if (rc == 0) {
361+
info.GetReturnValue().Set(v8::Null(isolate));
362+
return;
363+
}
364+
}
365+
366+
// Error or invalid operation - return undefined
367+
info.GetReturnValue().Set(v8::Undefined(isolate));
368+
}
369+
321370
NODE_GETTER(JS_busy) {
322371
Statement* stmt = Unwrap<Statement>(info.This());
323372
info.GetReturnValue().Set(stmt->alive && stmt->locked);

test/26.statement.scanstatus.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
'use strict';
2+
const Database = require('../.');
3+
4+
describe('Statement#scanStatusV2()', function () {
5+
beforeEach(function () {
6+
this.db = new Database(util.next());
7+
this.db.prepare('CREATE TABLE entries (id INTEGER PRIMARY KEY, name TEXT, value INTEGER)').run();
8+
this.db.prepare('INSERT INTO entries (name, value) VALUES (?, ?)').run('foo', 1);
9+
this.db.prepare('INSERT INTO entries (name, value) VALUES (?, ?)').run('bar', 2);
10+
this.db.prepare('INSERT INTO entries (name, value) VALUES (?, ?)').run('baz', 3);
11+
});
12+
afterEach(function () {
13+
this.db.close();
14+
});
15+
16+
it('should return scan status information for a query', function () {
17+
const stmt = this.db.prepare('SELECT * FROM entries WHERE value > ?');
18+
19+
// Execute the statement to populate scan status
20+
const rows = stmt.all(1);
21+
expect(rows).to.have.lengthOf(2);
22+
23+
// Get scan status for the first loop (idx=0)
24+
const explain = stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_EXPLAIN, 0);
25+
expect(explain).to.be.a('string');
26+
27+
const nLoop = stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_NLOOP, 0);
28+
expect(nLoop).to.be.a('number');
29+
30+
const nVisit = stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_NVISIT, 0);
31+
expect(nVisit).to.be.a('number');
32+
});
33+
34+
it('should return undefined for invalid index', function () {
35+
const stmt = this.db.prepare('SELECT * FROM entries');
36+
stmt.all();
37+
38+
// Try to get scan status for a non-existent loop
39+
const result = stmt.scanStatusV2(999, Database.SQLITE_SCANSTAT_EXPLAIN, 0);
40+
expect(result).to.be.undefined;
41+
});
42+
43+
it('should work with SQLITE_SCANSTAT_COMPLEX flag', function () {
44+
const stmt = this.db.prepare('SELECT * FROM entries WHERE value > ?');
45+
stmt.all(1);
46+
47+
// Use COMPLEX flag to get more detailed information
48+
const explain = stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_EXPLAIN, Database.SQLITE_SCANSTAT_COMPLEX);
49+
expect(explain).to.be.a('string');
50+
51+
const selectId = stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_SELECTID, Database.SQLITE_SCANSTAT_COMPLEX);
52+
expect(selectId).to.be.a('number');
53+
});
54+
55+
it('should respect safeIntegers setting', function () {
56+
const stmt = this.db.prepare('SELECT * FROM entries').safeIntegers(true);
57+
stmt.all();
58+
59+
const nLoop = stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_NLOOP, 0);
60+
expect(nLoop).to.be.a('bigint');
61+
});
62+
63+
it('should return EST as a double', function () {
64+
const stmt = this.db.prepare('SELECT * FROM entries WHERE value > ?');
65+
stmt.all(1);
66+
67+
const est = stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_EST, 0);
68+
expect(est).to.be.a('number');
69+
expect(est).to.be.greaterThan(0);
70+
});
71+
72+
it('should return NAME for table/index name', function () {
73+
const stmt = this.db.prepare('SELECT * FROM entries');
74+
stmt.all();
75+
76+
const name = stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_NAME, 0);
77+
// Name could be null for some query plans or a string for table/index name
78+
expect(name === null || typeof name === 'string').to.be.true;
79+
});
80+
81+
it('should throw when database is closed', function () {
82+
const stmt = this.db.prepare('SELECT * FROM entries');
83+
this.db.close();
84+
85+
expect(() => stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_EXPLAIN, 0))
86+
.to.throw(TypeError);
87+
});
88+
89+
it('should verify constants are exported', function () {
90+
expect(Database.SQLITE_SCANSTAT_NLOOP).to.equal(0);
91+
expect(Database.SQLITE_SCANSTAT_NVISIT).to.equal(1);
92+
expect(Database.SQLITE_SCANSTAT_EST).to.equal(2);
93+
expect(Database.SQLITE_SCANSTAT_NAME).to.equal(3);
94+
expect(Database.SQLITE_SCANSTAT_EXPLAIN).to.equal(4);
95+
expect(Database.SQLITE_SCANSTAT_SELECTID).to.equal(5);
96+
expect(Database.SQLITE_SCANSTAT_PARENTID).to.equal(6);
97+
expect(Database.SQLITE_SCANSTAT_NCYCLE).to.equal(7);
98+
expect(Database.SQLITE_SCANSTAT_COMPLEX).to.equal(0x0001);
99+
});
100+
});

0 commit comments

Comments
 (0)