@@ -166,42 +166,36 @@ function seedCurrentDatabase(dbPath: string): void {
166166}
167167
168168function corruptLastTagLeaf ( dbPath : string ) : void {
169+ // Zero exactly one LEAF page of the `tags` b-tree, so most rows survive and `.recover`
170+ // has something to salvage. Two approaches are unavailable here: `dbstat` is a
171+ // compile-time SQLite option (SQLITE_ENABLE_DBSTAT_VTAB) that some bun builds omit, and
172+ // scanning for row text picks a different page depending on how the build packs cells —
173+ // both make the fixture depend on the toolchain rather than on the code under test.
174+ // Instead walk the documented on-disk format: sqlite_master gives the table's root page,
175+ // and the page header's rightmost-child pointer descends to the last leaf.
169176 const db = new Database ( dbPath , { readonly : true } ) ;
170177 const { page_size : pageSize } = db . prepare ( "PRAGMA page_size" ) . get ( ) as { page_size : number } ;
171- // `dbstat` is a compile-time SQLite option (SQLITE_ENABLE_DBSTAT_VTAB), present in some
172- // bun builds and absent in others, so picking a page through it makes this fixture pass
173- // or fail on the toolchain rather than on the code under test. Scan the file instead.
174- // Every `tags` row stores the literal 'message' in its type column and nothing bulky,
175- // so a tags leaf packs dozens of them into one page while other pages that mention the
176- // word (the schema page's start_message/end_message columns) hold only a few. Taking
177- // the page with the MOST occurrences therefore lands on a real tags leaf without a
178- // magic threshold; skip page 1, which is the schema header.
179- const { page_count : pageCount } = db . prepare ( "PRAGMA page_count" ) . get ( ) as {
180- page_count : number ;
181- } ;
178+ const { rootpage } = db
179+ . prepare ( "SELECT rootpage FROM sqlite_master WHERE type = 'table' AND name = 'tags'" )
180+ . get ( ) as { rootpage : number } ;
182181 db . close ( ) ;
183182
184- const rowMarker = Buffer . from ( "message" ) ;
185183 const fd = openSync ( dbPath , "r+" ) ;
186184 try {
187185 const buffer = Buffer . alloc ( pageSize ) ;
188- let target : number | undefined ;
189- let best = 0 ;
190- for ( let pageno = 2 ; pageno <= pageCount ; pageno ++ ) {
186+ let pageno = rootpage ;
187+ // Page 1 carries the 100-byte database header before its b-tree header.
188+ for ( let depth = 0 ; depth < 32 ; depth ++ ) {
191189 readSync ( fd , buffer , 0 , pageSize , ( pageno - 1 ) * pageSize ) ;
192- let count = 0 ;
193- let at = buffer . indexOf ( rowMarker ) ;
194- while ( at !== - 1 ) {
195- count += 1 ;
196- at = buffer . indexOf ( rowMarker , at + rowMarker . length ) ;
197- }
198- if ( count > best ) {
199- best = count ;
200- target = pageno ;
201- }
190+ const headerAt = pageno === 1 ? 100 : 0 ;
191+ const pageType = buffer [ headerAt ] ;
192+ if ( pageType === 0x0d ) break ; // leaf table page
193+ if ( pageType !== 0x05 ) throw new Error ( `unexpected page type 0x${ pageType . toString ( 16 ) } ` ) ;
194+ // Interior table page: the rightmost child pointer lives at header offset 8.
195+ pageno = buffer . readUInt32BE ( headerAt + 8 ) ;
202196 }
203- if ( ! target ) throw new Error ( "no tags leaf page found to corrupt " ) ;
204- writeSync ( fd , Buffer . alloc ( pageSize ) , 0 , pageSize , ( target - 1 ) * pageSize ) ;
197+ if ( buffer [ pageno === 1 ? 100 : 0 ] !== 0x0d ) throw new Error ( "no tags leaf page found" ) ;
198+ writeSync ( fd , Buffer . alloc ( pageSize ) , 0 , pageSize , ( pageno - 1 ) * pageSize ) ;
205199 } finally {
206200 closeSync ( fd ) ;
207201 }
0 commit comments