@@ -177,7 +177,7 @@ class InnerDatabaseFile : public SQLiteVFS::File {
177177 std::vector<char > decodebuf; // otherwise decode into this buffer (background fetch)
178178
179179 SQLite::Statement cursor; // outer db cursor
180- std::unique_ptr<SQLite::Statement> btree_interior_cursor;
180+ std::unique_ptr<SQLite::Statement> btree_interior_cursor, btree_interior_pageno_cursor ;
181181 sqlite3_int64 cursor_pageno = 0 ; // if >0 then cursor is currently on this page
182182 unsigned long long last_op = 0 ; // "time" of the cursor's last use
183183 bool was_sequential = false ;
@@ -199,12 +199,16 @@ class InnerDatabaseFile : public SQLiteVFS::File {
199199 PutState (State::NEW);
200200 // prepare to read from btree interior index in web mode only. it can be
201201 // counterproductive locally due to its big index keys => low fan-out
202- if (that. web_ && !that.btree_interior_index_ .empty ()) {
202+ if (!that.btree_interior_index_ .empty ()) {
203203 btree_interior_cursor.reset (new SQLite::Statement (
204204 *(that.outer_db_ ), " SELECT pageno, data, meta1, meta2 FROM " +
205205 that.inner_db_pages_table_ + " INDEXED BY " +
206206 that.btree_interior_index_ +
207207 " WHERE btree_interior AND pageno = ?" ));
208+ btree_interior_pageno_cursor.reset (new SQLite::Statement (
209+ *(that.outer_db_ ), " SELECT 1 FROM " + that.inner_db_pages_table_ +
210+ " INDEXED BY " + that.btree_interior_index_ +
211+ " _pageno WHERE btree_interior AND pageno = ?" ));
208212 }
209213 }
210214
@@ -251,10 +255,23 @@ class InnerDatabaseFile : public SQLiteVFS::File {
251255 non_sequential++;
252256#endif
253257
254- // first try the covering index of interior btree pages
255- if (btree_interior_cursor &&
256- (btree_interior_cursor->reset (), btree_interior_cursor->bind (1 , pageno),
257- btree_interior_cursor->executeStep ())) {
258+ // First try the index of interior btree pages. The covering index is somewhat
259+ // costly to search due to its large btree keys (=> low fan-out). Therefore, we
260+ // first search the pageno-only index so that we can skip the costlier lookup
261+ // in the common case that it'd fail anyway (like a bloom filter, but exact).
262+ if (btree_interior_pageno_cursor &&
263+ (btree_interior_pageno_cursor->reset (),
264+ btree_interior_pageno_cursor->bind (1 , pageno),
265+ btree_interior_pageno_cursor->executeStep ())) {
266+ assert (btree_interior_cursor);
267+ btree_interior_cursor->reset ();
268+ btree_interior_cursor->bind (1 , pageno);
269+ if (!btree_interior_cursor->executeStep ()) {
270+ // impossible
271+ throw SQLite::Exception (" inconsistent btree interior index, page " +
272+ std::to_string (pageno),
273+ SQLITE_CORRUPT);
274+ }
258275 btree_interior = true ;
259276#ifndef NDEBUG
260277 interior_hits++;
@@ -1093,7 +1110,7 @@ class InnerDatabaseFile : public SQLiteVFS::File {
10931110 if (outer_db_
10941111 ->execAndGet (
10951112 " SELECT count(1) FROM sqlite_master WHERE type = 'index' AND name = '" +
1096- btree_interior_index_ + " '" )
1113+ btree_interior_index_ + " _pageno '" )
10971114 .getInt () != 1 ) {
10981115 btree_interior_index_.clear ();
10991116 }
@@ -1160,6 +1177,15 @@ class VFS : public SQLiteVFS::Wrapper {
11601177 " pages_btree_interior ON " + inner_db_tablename_prefix_ +
11611178 " pages (pageno,data,meta1,meta2) WHERE btree_interior" )
11621179 .executeStep ();
1180+ // redundant second index with only the page numbers. the covering index is somewhat
1181+ // costly to search due to its large keys => low fan-out. therefore, we first search
1182+ // the pageno-only index so that we can skip the costlier lookup in the common case
1183+ // that it'd fail anyway (like a bloom filter, but exact).
1184+ SQLite::Statement (db, " CREATE INDEX " + inner_db_tablename_prefix_ +
1185+ " pages_btree_interior_pageno ON " +
1186+ inner_db_tablename_prefix_ +
1187+ " pages (pageno) WHERE btree_interior" )
1188+ .executeStep ();
11631189 }
11641190 }
11651191
0 commit comments