@@ -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