Skip to content

Commit 73b2324

Browse files
committed
scanstatus return binding as int for parent and selectid
1 parent e6e25f2 commit 73b2324

File tree

2 files changed

+136
-3
lines changed

2 files changed

+136
-3
lines changed

src/objects/statement.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,12 +416,22 @@ NODE_METHOD(Statement::JS_scanStatusV2) {
416416
}
417417
return;
418418
}
419+
case SQLITE_SCANSTAT_SELECTID:
420+
case SQLITE_SCANSTAT_PARENTID: {
421+
// Integer opcodes (int)
422+
int pOut;
423+
rc = sqlite3_stmt_scanstatus_v2(stmt->handle, idx, opcode, flags, (void*)&pOut);
424+
if (rc == SQLITE_OK) {
425+
info.GetReturnValue().Set(v8::Number::New(isolate, (double)pOut));
426+
} else {
427+
info.GetReturnValue().Set(v8::Undefined(isolate));
428+
}
429+
return;
430+
}
419431
case SQLITE_SCANSTAT_NLOOP:
420432
case SQLITE_SCANSTAT_NVISIT:
421-
case SQLITE_SCANSTAT_SELECTID:
422-
case SQLITE_SCANSTAT_PARENTID:
423433
case SQLITE_SCANSTAT_NCYCLE: {
424-
// Integer opcodes
434+
// Integer opcodes (int64)
425435
sqlite3_int64 pOut;
426436
rc = sqlite3_stmt_scanstatus_v2(stmt->handle, idx, opcode, flags, (void*)&pOut);
427437
if (rc == SQLITE_OK) {

test/26.statement.scanstatus.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,127 @@ describe('Statement#scanStatusV2()', function () {
9797
expect(Database.SQLITE_SCANSTAT_NCYCLE).to.equal(7);
9898
expect(Database.SQLITE_SCANSTAT_COMPLEX).to.equal(0x0001);
9999
});
100+
101+
it('should return reasonable SELECTID and PARENTID values', function () {
102+
const stmt = this.db.prepare('SELECT * FROM entries WHERE value > ?');
103+
stmt.all(1);
104+
105+
// Get SELECTID and PARENTID for the first loop (using flag 1 for COMPLEX)
106+
const selectId = stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_SELECTID, 1);
107+
const parentId = stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_PARENTID, 1);
108+
109+
// Verify selectId is a number (not undefined)
110+
expect(selectId).to.be.a('number');
111+
112+
// SELECTID should be a small positive integer (usually 1-10 for simple queries)
113+
// It corresponds to the ID shown in EXPLAIN QUERY PLAN
114+
expect(selectId).to.be.at.least(0);
115+
expect(selectId).to.be.at.most(1000); // Reasonable upper bound
116+
expect(Number.isInteger(selectId)).to.be.true;
117+
118+
// PARENTID can be 0 (no parent) or a positive integer
119+
if (parentId !== null && parentId !== undefined) {
120+
expect(parentId).to.be.a('number');
121+
expect(parentId).to.be.at.least(0);
122+
expect(parentId).to.be.at.most(1000); // Reasonable upper bound
123+
expect(Number.isInteger(parentId)).to.be.true;
124+
}
125+
});
126+
127+
it('should verify SELECTID and PARENTID match EXPLAIN QUERY PLAN', function () {
128+
const stmt = this.db.prepare('SELECT * FROM entries WHERE value > ?');
129+
stmt.all(1);
130+
131+
// Get SELECTID using flag 1 for COMPLEX
132+
const selectId = stmt.scanStatusV2(0, Database.SQLITE_SCANSTAT_SELECTID, 1);
133+
134+
// Get EXPLAIN QUERY PLAN for the same query
135+
const explainStmt = this.db.prepare('EXPLAIN QUERY PLAN SELECT * FROM entries WHERE value > ?');
136+
const explainRows = explainStmt.all(1);
137+
138+
// The selectId should correspond to one of the IDs in the EXPLAIN QUERY PLAN output
139+
const explainIds = explainRows.map(row => row.id);
140+
141+
// selectId should be in the range of IDs from EXPLAIN QUERY PLAN
142+
if (explainIds.length > 0) {
143+
const minId = Math.min(...explainIds);
144+
const maxId = Math.max(...explainIds);
145+
expect(selectId).to.be.at.least(minId);
146+
expect(selectId).to.be.at.most(maxId);
147+
}
148+
});
149+
150+
it('should handle complex queries with joins', function () {
151+
// Create a more complex schema
152+
this.db.prepare('CREATE TABLE orders (id INTEGER PRIMARY KEY, entry_id INTEGER, amount INTEGER)').run();
153+
this.db.prepare('INSERT INTO orders (entry_id, amount) VALUES (1, 100), (2, 200), (3, 300)').run();
154+
155+
const stmt = this.db.prepare('SELECT e.name, o.amount FROM entries e JOIN orders o ON e.id = o.entry_id WHERE o.amount > ?');
156+
stmt.all(150);
157+
158+
// Check that we can get scan status for multiple loops
159+
let loopCount = 0;
160+
for (let i = 0; i < 10; i++) {
161+
const selectId = stmt.scanStatusV2(i, Database.SQLITE_SCANSTAT_SELECTID, 1);
162+
if (selectId === undefined) break;
163+
164+
loopCount++;
165+
expect(selectId).to.be.a('number');
166+
expect(selectId).to.be.at.least(0);
167+
expect(selectId).to.be.at.most(1000);
168+
expect(Number.isInteger(selectId)).to.be.true;
169+
170+
const parentId = stmt.scanStatusV2(i, Database.SQLITE_SCANSTAT_PARENTID, 1);
171+
if (parentId !== null && parentId !== undefined) {
172+
expect(parentId).to.be.a('number');
173+
expect(parentId).to.be.at.least(0);
174+
expect(parentId).to.be.at.most(1000);
175+
expect(Number.isInteger(parentId)).to.be.true;
176+
}
177+
}
178+
179+
// For a join query, we should have at least 2 loops
180+
expect(loopCount).to.be.at.least(2);
181+
});
182+
183+
it('should verify parent/child loop relationship with ORDER BY', function () {
184+
// ORDER BY on non-indexed field creates a parent/child loop relationship
185+
const stmt = this.db.prepare('SELECT * FROM entries ORDER BY name');
186+
stmt.all();
187+
188+
// Collect all loops
189+
const loops = [];
190+
for (let i = 0; i < 10; i++) {
191+
const selectId = stmt.scanStatusV2(i, Database.SQLITE_SCANSTAT_SELECTID, 1);
192+
if (selectId === undefined) break;
193+
194+
const parentId = stmt.scanStatusV2(i, Database.SQLITE_SCANSTAT_PARENTID, 1);
195+
loops.push({ index: i, selectId, parentId });
196+
197+
// Verify values are reasonable
198+
expect(selectId).to.be.a('number');
199+
expect(selectId).to.be.at.least(0);
200+
expect(selectId).to.be.at.most(1000);
201+
expect(Number.isInteger(selectId)).to.be.true;
202+
203+
if (parentId !== null && parentId !== undefined) {
204+
expect(parentId).to.be.a('number');
205+
expect(parentId).to.be.at.least(0);
206+
expect(parentId).to.be.at.most(1000);
207+
expect(Number.isInteger(parentId)).to.be.true;
208+
}
209+
}
210+
211+
// Should have at least 2 loops for ORDER BY
212+
expect(loops.length).to.be.at.least(2);
213+
214+
// Verify parent/child relationship: find a loop that references another as parent
215+
const childLoop = loops.find(l => l.parentId > 0);
216+
if (childLoop) {
217+
const parentLoop = loops.find(l => l.selectId === childLoop.parentId);
218+
expect(parentLoop).to.not.be.undefined;
219+
// Parent loop should have been processed before child loop
220+
expect(parentLoop.index).to.be.lessThan(childLoop.index);
221+
}
222+
});
100223
});

0 commit comments

Comments
 (0)