Skip to main content

uqa_sql/schema/indexes/
removal.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! DROP INDEX names, constraint dependencies and stored field references.
8use crate::{ast::ColumnType, catalog::resolution::RelationResolution, SQLError};
9use uqa_core::RelationIdentity;
10
11pub fn resolve_drop_index_name(
12    resolution: RelationResolution,
13    requested: &str,
14    if_exists: bool,
15    notice: &mut dyn FnMut(&str),
16) -> Result<Option<String>, SQLError> {
17    match resolution {
18        RelationResolution::Found(canonical, "index") => Ok(Some(canonical)),
19        RelationResolution::Found(_, found) => {
20            let local = uqa_core::RelationIdentity::parse_reference(requested)
21                .map_err(SQLError::Internal)?
22                .1;
23            Err(crate::schema::removal::wrong_drop_kind_error(
24                &local, "index", found,
25            ))
26        }
27        RelationResolution::MissingSchema(schema) if if_exists => {
28            notice(&format!("schema \"{schema}\" does not exist, skipping"));
29            Ok(None)
30        }
31        RelationResolution::MissingSchema(schema) => Err(SQLError::Routine {
32            sqlstate: "3F000".into(),
33            message: format!("schema \"{schema}\" does not exist"),
34        }),
35        RelationResolution::MissingRelation if if_exists => {
36            let local = uqa_core::RelationIdentity::parse_reference(requested)
37                .map_err(SQLError::Internal)?
38                .1;
39            notice(&format!("index \"{local}\" does not exist, skipping"));
40            Ok(None)
41        }
42        RelationResolution::MissingRelation => {
43            let local = uqa_core::RelationIdentity::parse_reference(requested)
44                .map_err(SQLError::Internal)?
45                .1;
46            Err(SQLError::Routine {
47                sqlstate: "42704".into(),
48                message: format!("index \"{local}\" does not exist"),
49            })
50        }
51    }
52}
53pub fn catalog_index_columns(
54    relation: &RelationIdentity,
55    columns_json: &str,
56    action: &str,
57) -> Result<Vec<String>, SQLError> {
58    serde_json::from_str(columns_json).map_err(|e| {
59        SQLError::Internal(format!(
60            "{action} `{}`: invalid index column metadata: {e}",
61            relation.qualified_name()
62        ))
63    })
64}
65
66/// Borrowed catalog metadata used to detect remaining references to a physical text field.
67pub struct IndexRemovalCandidate<'a> {
68    pub relation: &'a RelationIdentity,
69    pub table: &'a str,
70    pub method: &'a str,
71    pub columns_json: &'a str,
72}
73pub fn gin_field_is_referenced<'a>(
74    relation: &RelationIdentity,
75    table: &str,
76    field: &str,
77    candidates: impl IntoIterator<Item = IndexRemovalCandidate<'a>>,
78) -> Result<bool, SQLError> {
79    for candidate in candidates {
80        if candidate.relation == relation
81            || candidate.table != table
82            || !candidate.method.eq_ignore_ascii_case("gin")
83        {
84            continue;
85        }
86        if catalog_index_columns(candidate.relation, candidate.columns_json, "DROP INDEX")?
87            .iter()
88            .any(|candidate_field| candidate_field == field)
89        {
90            return Ok(true);
91        }
92    }
93    Ok(false)
94}
95pub fn vector_index_dimensions(
96    relation: &RelationIdentity,
97    table: &str,
98    column: &str,
99    column_type: Option<ColumnType>,
100) -> Result<u32, SQLError> {
101    match column_type {
102        Some(ColumnType::Vector(dim) | ColumnType::Tensor(dim)) => Ok(dim),
103        Some(other) => Err(SQLError::Unsupported(format!(
104            "DROP INDEX `{}`: vector-index column `{}`.`{column}` is no longer VECTOR or TENSOR, got {other:?}",
105            relation.qualified_name(), table
106        ))),
107        None => Err(SQLError::Unsupported(format!(
108            "DROP INDEX `{}`: column `{}`.`{column}` does not exist",
109            relation.qualified_name(), table
110        ))),
111    }
112}