@@ -104,6 +104,64 @@ fn open_conn(db_path: &str) -> DFResult<Connection> {
104104 Ok ( conn)
105105}
106106
107+ /// Ensure the key column has an index. If the table was created with
108+ /// `INTEGER PRIMARY KEY` the rowid alias already serves as the index and
109+ /// this is a no-op. For tables created without a PK (pre-fix builds) we
110+ /// create a secondary index so point lookups use the B-tree instead of a
111+ /// full table scan.
112+ fn ensure_key_index ( conn : & Connection , table_name : & str , key_col : & str ) -> DFResult < ( ) > {
113+ // Check if the key column is the INTEGER PRIMARY KEY (rowid alias).
114+ // In that case SQLite already uses the rowid B-tree — no extra index needed.
115+ let is_pk: bool = conn
116+ . query_row (
117+ & format ! ( "SELECT pk FROM pragma_table_info({tn}) WHERE name = ?1" ,
118+ tn = quote_ident( table_name) ) ,
119+ rusqlite:: params![ key_col] ,
120+ |row| row. get :: < _ , i64 > ( 0 ) ,
121+ )
122+ . map ( |pk| pk > 0 )
123+ . unwrap_or ( false ) ;
124+
125+ if is_pk {
126+ return Ok ( ( ) ) ;
127+ }
128+
129+ // Check if any index already covers the key column.
130+ let has_index: bool = conn
131+ . query_row (
132+ "SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND tbl_name=?1 AND sql LIKE ?2" ,
133+ rusqlite:: params![ table_name, format!( "%{}%" , quote_ident( key_col) ) ] ,
134+ |row| row. get :: < _ , i64 > ( 0 ) ,
135+ )
136+ . map ( |n| n > 0 )
137+ . unwrap_or ( false ) ;
138+
139+ if has_index {
140+ return Ok ( ( ) ) ;
141+ }
142+
143+ tracing:: warn!(
144+ "SQLite table '{}': key column '{}' has no index — creating one (one-time migration)." ,
145+ table_name, key_col,
146+ ) ;
147+ conn. execute (
148+ & format ! (
149+ "CREATE INDEX {idx} ON {tn}({col})" ,
150+ idx = quote_ident( & format!( "idx_{table_name}_{key_col}" ) ) ,
151+ tn = quote_ident( table_name) ,
152+ col = quote_ident( key_col) ,
153+ ) ,
154+ [ ] ,
155+ )
156+ . map_err ( |e| DataFusionError :: Execution ( format ! ( "failed to create key index: {e}" ) ) ) ?;
157+
158+ tracing:: info!(
159+ "Created index on '{}'.'{}'" ,
160+ table_name, key_col,
161+ ) ;
162+ Ok ( ( ) )
163+ }
164+
107165impl SqliteLookupProvider {
108166 /// Open the existing SQLite database at `db_path`, or build it from
109167 /// parquet files on first run. Opens a pool of `pool_size` read
@@ -152,6 +210,10 @@ impl SqliteLookupProvider {
152210 table_name,
153211 n
154212 ) ;
213+ // Ensure the key column is indexed. Tables built before the
214+ // INTEGER PRIMARY KEY fix may lack any index on the key column,
215+ // turning every point lookup into a full table scan.
216+ ensure_key_index ( & conn, table_name, & key_col) ?;
155217 } else {
156218 tracing:: info!(
157219 "First run: building SQLite table '{}' (one-time)." ,
0 commit comments