use super::super::super::{params, table_exists, Catalog, RelationIdentity, Result};
struct Candidate {
relation: Option<RelationIdentity>,
name: String,
table_name: String,
columns_json: String,
}
pub(super) fn migrate(tx: &rusqlite::Connection) -> Result<()> {
if !table_exists(tx, "_catalog_indexes")?
|| !table_exists(tx, "_ivf_indexes")?
|| !table_exists(tx, "_hnsw_indexes")?
{
return Ok(());
}
let shape = Catalog::table_columns(tx, "_catalog_indexes")?.unwrap_or_default();
let candidates = if shape.contains_key("schema_name") {
let mut statement = tx.prepare(
"SELECT schema_name, relation_name, table_schema_name,
table_relation_name, columns
FROM _catalog_indexes
WHERE lower(index_type) = 'hnsw'
ORDER BY schema_name, relation_name",
)?;
let rows = statement.query_map([], |row| {
let schema = row.get::<_, String>(0)?;
let name = row.get::<_, String>(1)?;
let table_schema = row.get::<_, String>(2)?;
let table_name = row.get::<_, String>(3)?;
Ok(Candidate {
relation: Some(RelationIdentity::new(schema, &name)),
name,
table_name: RelationIdentity::new(table_schema, table_name).qualified_name(),
columns_json: row.get(4)?,
})
})?;
rows.collect::<rusqlite::Result<Vec<_>>>()?
} else {
let mut statement = tx.prepare(
"SELECT name, table_name, columns
FROM _catalog_indexes
WHERE lower(index_type) = 'hnsw'
ORDER BY name",
)?;
let rows = statement.query_map([], |row| {
Ok(Candidate {
relation: None,
name: row.get(0)?,
table_name: row.get(1)?,
columns_json: row.get(2)?,
})
})?;
rows.collect::<rusqlite::Result<Vec<_>>>()?
};
let mut legacy_aliases = Vec::new();
for candidate in candidates {
let columns: Vec<String> = serde_json::from_str(&candidate.columns_json)?;
if columns.is_empty() {
continue;
}
let mut all_ivf = true;
let mut any_hnsw = false;
for field in columns {
let has_ivf = tx.query_row(
"SELECT EXISTS(
SELECT 1 FROM _ivf_indexes
WHERE table_name = ?1 AND field = ?2
)",
params![candidate.table_name, field],
|row| row.get::<_, bool>(0),
)?;
let has_hnsw = tx.query_row(
"SELECT EXISTS(
SELECT 1 FROM _hnsw_indexes
WHERE table_name = ?1 AND field = ?2
)",
params![candidate.table_name, field],
|row| row.get::<_, bool>(0),
)?;
all_ivf &= has_ivf;
any_hnsw |= has_hnsw;
}
if all_ivf && !any_hnsw {
legacy_aliases.push(candidate);
}
}
for candidate in legacy_aliases {
if let Some(relation) = candidate.relation {
tx.execute(
"UPDATE _catalog_indexes
SET index_type = 'ivf'
WHERE schema_name = ?1 AND relation_name = ?2
AND lower(index_type) = 'hnsw'",
params![relation.schema, relation.name],
)?;
} else {
tx.execute(
"UPDATE _catalog_indexes
SET index_type = 'ivf'
WHERE name = ?1 AND lower(index_type) = 'hnsw'",
params![candidate.name],
)?;
}
}
Ok(())
}