Skip to main content

uqa_sql/schema/indexes/
keys.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Typed index-key preparation and column names assigned at index creation.
8
9use crate::ast::{Expr, GeneratedColumnKind, IndexKey};
10use crate::schema::SchemaBindingContext;
11use crate::{ast::CreateIndex, ColumnType, SQLError};
12
13pub fn key_names(keys: &[IndexKey]) -> Vec<String> {
14    let mut names = Vec::with_capacity(keys.len());
15    for key in keys {
16        let label = match key {
17            IndexKey::Column(column) => column.clone(),
18            IndexKey::Expression(expression) => {
19                expression_name(expression).map_or_else(|| "expr".into(), |(name, _)| name)
20            }
21        };
22        let mut name = label.clone();
23        let mut suffix = 1_u64;
24        while names.contains(&name) {
25            name = format!("{label}{suffix}");
26            suffix += 1;
27        }
28        names.push(name);
29    }
30    names
31}
32
33fn expression_name(expression: &Expr) -> Option<(String, bool)> {
34    match expression {
35        Expr::Column(name) | Expr::QualifiedColumn { column: name, .. } => {
36            Some((name.clone(), true))
37        }
38        Expr::Func { name, .. } => Some((
39            crate::parse_regobject_name(name)
40                .and_then(|mut names| names.pop())
41                .unwrap_or_else(|| name.clone()),
42            true,
43        )),
44        Expr::Cast { expr, ty } => {
45            let inner = expression_name(expr);
46            if inner.as_ref().is_some_and(|(_, strong)| *strong) {
47                inner
48            } else {
49                Some((
50                    crate::parse_regtype_name(ty)
51                        .ok()
52                        .flatten()
53                        .and_then(|mut name| name.names.pop())
54                        .unwrap_or_else(|| ty.clone()),
55                    false,
56                ))
57            }
58        }
59        Expr::Case { else_branch, .. } => {
60            let inner = else_branch.as_deref().and_then(expression_name);
61            Some(
62                inner
63                    .filter(|(_, strong)| *strong)
64                    .unwrap_or_else(|| ("case".into(), false)),
65            )
66        }
67        Expr::Array(_) => Some(("array".into(), true)),
68        Expr::Row(_) => Some(("row".into(), true)),
69        _ => None,
70    }
71}
72
73pub fn require_column_key<'a>(key: &'a IndexKey, method: &str) -> Result<&'a str, SQLError> {
74    key.column().ok_or_else(|| {
75        SQLError::Unsupported(format!(
76            "expression keys for access method `{method}` are not implemented"
77        ))
78    })
79}
80
81pub fn prepare_index_keys(
82    context: &SchemaBindingContext<'_, '_>,
83    statement: &mut CreateIndex,
84) -> Result<Vec<ColumnType>, SQLError> {
85    let definitions = context
86        .catalog
87        .schema_expression_columns(&statement.table)?
88        .ok_or_else(|| SQLError::UnknownTable(statement.table.clone()))?;
89    let mut types = Vec::with_capacity(statement.columns.len());
90    for key in &mut statement.columns {
91        match key {
92            IndexKey::Column(name) => {
93                let Some(column) = definitions.iter().find(|column| column.name == *name) else {
94                    if definitions.is_empty() {
95                        types.push(ColumnType::Text);
96                        continue;
97                    }
98                    return Err(SQLError::UnknownColumn(name.clone()));
99                };
100                if column
101                    .generated
102                    .as_ref()
103                    .is_some_and(|generated| generated.kind == GeneratedColumnKind::Virtual)
104                {
105                    return Err(SQLError::Unsupported(format!(
106                        "indexes on virtual generated column `{name}` are not supported"
107                    )));
108                }
109                types.push(column.ty.clone());
110            }
111            IndexKey::Expression(expression) => {
112                for column in &definitions {
113                    if column
114                        .generated
115                        .as_ref()
116                        .is_some_and(|generated| generated.kind == GeneratedColumnKind::Virtual)
117                        && crate::schema::dependencies::schema_expr_references_column(
118                            expression,
119                            &column.name,
120                        )
121                    {
122                        return Err(SQLError::Unsupported(format!(
123                            "index expressions cannot use virtual generated column `{}`",
124                            column.name
125                        )));
126                    }
127                }
128                let ty = super::prepare_index_expression(
129                    context.catalog,
130                    context.binding,
131                    &statement.table,
132                    expression,
133                )?;
134                let column = match expression.as_ref() {
135                    crate::ast::Expr::Column(name) => Some(name.clone()),
136                    crate::ast::Expr::Cast { expr, .. } => {
137                        if let crate::ast::Expr::Column(name) = expr.as_ref() {
138                            definitions
139                                .iter()
140                                .any(|column| column.name == *name && column.ty == ty)
141                                .then(|| name.clone())
142                        } else {
143                            None
144                        }
145                    }
146                    _ => None,
147                };
148                if let Some(column) = column {
149                    *key = IndexKey::Column(column);
150                }
151                types.push(ty);
152            }
153        }
154    }
155    let mut included = std::collections::BTreeSet::new();
156    for name in &statement.included_columns {
157        if !definitions.is_empty() && !definitions.iter().any(|column| column.name == *name) {
158            return Err(SQLError::UnknownColumn(name.clone()));
159        }
160        if !included.insert(name)
161            || statement
162                .columns
163                .iter()
164                .any(|key| key.column() == Some(name.as_str()))
165        {
166            return Err(SQLError::Routine {
167                sqlstate: "42701".into(),
168                message: format!("column \"{name}\" included more than once"),
169            });
170        }
171    }
172    if !included.is_empty() && statement.access_method == "gin" {
173        return Err(SQLError::Unsupported(
174            "access method \"gin\" does not support included columns".into(),
175        ));
176    }
177    Ok(types)
178}
179
180/// Bind keys and predicates and retain the public attribute names assigned before expression simplification.
181pub fn prepare_index_definition(
182    catalog: &dyn crate::schema::SchemaExpressionCatalog,
183    bindings: &dyn crate::semantics::conflict::InferenceBindingScope,
184    c: &mut CreateIndex,
185) -> Result<crate::catalog::index::IndexDefinition, SQLError> {
186    let attribute_keys = c
187        .columns
188        .iter()
189        .cloned()
190        .chain(
191            c.included_columns
192                .iter()
193                .cloned()
194                .map(crate::ast::IndexKey::Column),
195        )
196        .collect::<Vec<_>>();
197    let key_names = key_names(&attribute_keys);
198    let binding = bindings.binding_scope()?;
199    let key_types = prepare_index_keys(
200        &SchemaBindingContext {
201            catalog,
202            binding: &binding.context(),
203        },
204        c,
205    )?;
206    if let Some(predicate) = c.predicate.as_deref_mut() {
207        let binding = bindings.binding_scope()?;
208        crate::schema::indexes::prepare_index_predicate(
209            catalog,
210            &binding.context(),
211            &c.table,
212            predicate,
213        )?;
214    }
215    Ok(crate::catalog::index::IndexDefinition {
216        key_names,
217        key_types,
218        included_columns: c.included_columns.clone(),
219        column_order: c.column_order.clone(),
220        predicate: c.predicate.clone(),
221        unique: c.unique,
222        nulls_not_distinct: c.nulls_not_distinct,
223    })
224}