Skip to content

Commit 4cfb4cc

Browse files
committed
fix(sqlite-provider): auto-create index on key column if missing
On startup, ensure_key_index() checks whether the key column has a PRIMARY KEY or secondary index. If neither exists, it creates one. This prevents accidental full table scans if a table is ever built or altered without a proper key index.
1 parent c754605 commit 4cfb4cc

1 file changed

Lines changed: 62 additions & 0 deletions

File tree

src/sqlite_provider.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
107165
impl 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

Comments
 (0)