uqa_sql/schema/indexes/
removal.rs1use crate::{
9 ast::{ColumnType, ForeignKey},
10 catalog::resolution::RelationResolution,
11 SQLError,
12};
13use std::collections::BTreeSet;
14use uqa_core::RelationIdentity;
15
16pub fn resolve_drop_index_name(
17 resolution: RelationResolution,
18 requested: &str,
19 if_exists: bool,
20 notice: &mut dyn FnMut(&str),
21) -> Result<Option<String>, SQLError> {
22 match resolution {
23 RelationResolution::Found(canonical, "index") => Ok(Some(canonical)),
24 RelationResolution::Found(_, _) => Err(SQLError::Routine {
25 sqlstate: "42809".into(),
26 message: format!("\"{requested}\" is not an index"),
27 }),
28 RelationResolution::MissingSchema(schema) if if_exists => {
29 notice(&format!("schema \"{schema}\" does not exist, skipping"));
30 Ok(None)
31 }
32 RelationResolution::MissingSchema(schema) => Err(SQLError::Routine {
33 sqlstate: "3F000".into(),
34 message: format!("schema \"{schema}\" does not exist"),
35 }),
36 RelationResolution::MissingRelation if if_exists => {
37 let local = uqa_core::RelationIdentity::parse_reference(requested)
38 .map_err(SQLError::Internal)?
39 .1;
40 notice(&format!("index \"{local}\" does not exist, skipping"));
41 Ok(None)
42 }
43 RelationResolution::MissingRelation => {
44 let local = uqa_core::RelationIdentity::parse_reference(requested)
45 .map_err(SQLError::Internal)?
46 .1;
47 Err(SQLError::Routine {
48 sqlstate: "42704".into(),
49 message: format!("index \"{local}\" does not exist"),
50 })
51 }
52 }
53}
54pub fn ensure_index_not_constraint_owned(
55 relation: &RelationIdentity,
56 table: &str,
57 constraint_owned: bool,
58) -> Result<(), SQLError> {
59 if constraint_owned {
60 return Err(SQLError::Routine {
61 sqlstate: "2BP01".into(),
62 message: format!(
63 "cannot drop index {} because constraint {} on table {} requires it",
64 relation.name, relation.name, table
65 ),
66 });
67 }
68 Ok(())
69}
70pub fn catalog_index_columns(
71 relation: &RelationIdentity,
72 columns_json: &str,
73 action: &str,
74) -> Result<Vec<String>, SQLError> {
75 serde_json::from_str(columns_json).map_err(|e| {
76 SQLError::Internal(format!(
77 "{action} `{}`: invalid index column metadata: {e}",
78 relation.qualified_name()
79 ))
80 })
81}
82
83pub fn collect_index_dependents(
84 index_name: &str,
85 index_id: [u8; 16],
86 referrers: Vec<(String, ForeignKey)>,
87 cascade: bool,
88 dependents: &mut BTreeSet<(String, String)>,
89) -> Result<(), SQLError> {
90 for (table, foreign_key) in referrers {
91 if foreign_key.referenced_index != Some(index_id) {
92 continue;
93 }
94 let name = foreign_key
95 .name
96 .ok_or_else(|| SQLError::Internal("unnamed foreign-key dependency".into()))?;
97 if !cascade {
98 return Err(SQLError::Routine {
99 sqlstate: "2BP01".into(),
100 message: format!("cannot drop index {index_name} because constraint {name} on table {table} depends on it"),
101 });
102 }
103 dependents.insert((table, name));
104 }
105 Ok(())
106}
107pub struct IndexRemovalCandidate<'a> {
109 pub relation: &'a RelationIdentity,
110 pub table: &'a str,
111 pub method: &'a str,
112 pub columns_json: &'a str,
113}
114pub fn gin_field_is_referenced<'a>(
115 relation: &RelationIdentity,
116 table: &str,
117 field: &str,
118 candidates: impl IntoIterator<Item = IndexRemovalCandidate<'a>>,
119) -> Result<bool, SQLError> {
120 for candidate in candidates {
121 if candidate.relation == relation
122 || candidate.table != table
123 || !candidate.method.eq_ignore_ascii_case("gin")
124 {
125 continue;
126 }
127 if catalog_index_columns(candidate.relation, candidate.columns_json, "DROP INDEX")?
128 .iter()
129 .any(|candidate_field| candidate_field == field)
130 {
131 return Ok(true);
132 }
133 }
134 Ok(false)
135}
136pub fn vector_index_dimensions(
137 relation: &RelationIdentity,
138 table: &str,
139 column: &str,
140 column_type: Option<ColumnType>,
141) -> Result<u32, SQLError> {
142 match column_type {
143 Some(ColumnType::Vector(dim) | ColumnType::Tensor(dim)) => Ok(dim),
144 Some(other) => Err(SQLError::Unsupported(format!(
145 "DROP INDEX `{}`: vector-index column `{}`.`{column}` is no longer VECTOR or TENSOR, got {other:?}",
146 relation.qualified_name(), table
147 ))),
148 None => Err(SQLError::Unsupported(format!(
149 "DROP INDEX `{}`: column `{}`.`{column}` does not exist",
150 relation.qualified_name(), table
151 ))),
152 }
153}