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 referrers: Vec<(String, ForeignKey)>,
86 cascade: bool,
87 dependents: &mut BTreeSet<(String, String)>,
88) -> Result<(), SQLError> {
89 for (table, foreign_key) in referrers {
90 if foreign_key.referenced_key.as_deref() != Some(index_name) {
91 continue;
92 }
93 let name = foreign_key
94 .name
95 .ok_or_else(|| SQLError::Internal("unnamed foreign-key dependency".into()))?;
96 if !cascade {
97 return Err(SQLError::Routine {
98 sqlstate: "2BP01".into(),
99 message: format!("cannot drop index {index_name} because constraint {name} on table {table} depends on it"),
100 });
101 }
102 dependents.insert((table, name));
103 }
104 Ok(())
105}
106pub struct IndexRemovalCandidate<'a> {
108 pub relation: &'a RelationIdentity,
109 pub table: &'a str,
110 pub method: &'a str,
111 pub columns_json: &'a str,
112}
113pub fn gin_field_is_referenced<'a>(
114 relation: &RelationIdentity,
115 table: &str,
116 field: &str,
117 candidates: impl IntoIterator<Item = IndexRemovalCandidate<'a>>,
118) -> Result<bool, SQLError> {
119 for candidate in candidates {
120 if candidate.relation == relation
121 || candidate.table != table
122 || !candidate.method.eq_ignore_ascii_case("gin")
123 {
124 continue;
125 }
126 if catalog_index_columns(candidate.relation, candidate.columns_json, "DROP INDEX")?
127 .iter()
128 .any(|candidate_field| candidate_field == field)
129 {
130 return Ok(true);
131 }
132 }
133 Ok(false)
134}
135pub fn vector_index_dimensions(
136 relation: &RelationIdentity,
137 table: &str,
138 column: &str,
139 column_type: Option<ColumnType>,
140) -> Result<u32, SQLError> {
141 match column_type {
142 Some(ColumnType::Vector(dim) | ColumnType::Tensor(dim)) => Ok(dim),
143 Some(other) => Err(SQLError::Unsupported(format!(
144 "DROP INDEX `{}`: vector-index column `{}`.`{column}` is no longer VECTOR or TENSOR, got {other:?}",
145 relation.qualified_name(), table
146 ))),
147 None => Err(SQLError::Unsupported(format!(
148 "DROP INDEX `{}`: column `{}`.`{column}` does not exist",
149 relation.qualified_name(), table
150 ))),
151 }
152}