uqa_sql/schema/
indexes.rs1use super::{generated::bind_schema_column_references, SchemaExpressionCatalog};
10use crate::plan::ExpressionPlan;
11use crate::RowSchema;
12use crate::{ast::Expr, binding::context::BindingContext, ColumnType, SQLError};
13
14pub fn prepare_index_expression(
15 engine: &dyn SchemaExpressionCatalog,
16 binding: &BindingContext<'_>,
17 table: &str,
18 expression: &mut Expr,
19) -> Result<ColumnType, SQLError> {
20 let ty = bind_index_expression(engine, binding, table, expression, false)?;
21 validate_index_expression_immutability(engine, table, expression, false)?;
22 if let Some(ty) = ty {
23 return Ok(ty);
24 }
25 *expression = Expr::Cast {
26 implicit: true,
27 expr: Box::new(expression.clone()),
28 ty: "text".into(),
29 };
30 Ok(ColumnType::Text)
31}
32
33pub fn prepare_index_predicate(
34 engine: &dyn SchemaExpressionCatalog,
35 binding: &BindingContext<'_>,
36 table: &str,
37 expression: &mut Expr,
38) -> Result<(), SQLError> {
39 bind_index_predicate(engine, binding, table, expression)?;
40 validate_index_expression_immutability(engine, table, expression, true)
41}
42
43pub(super) fn bind_index_predicate(
44 engine: &dyn SchemaExpressionCatalog,
45 binding: &BindingContext<'_>,
46 table: &str,
47 expression: &mut Expr,
48) -> Result<(), SQLError> {
49 match bind_index_expression(engine, binding, table, expression, true)? {
50 Some(ColumnType::Boolean) => Ok(()),
51 None => {
52 if let Expr::Literal(value) = expression {
53 *value = crate::expr::cast_value(value, "boolean")?;
54 }
55 Ok(())
56 }
57 Some(_) => Err(SQLError::TypeMismatch(
58 "argument of WHERE must be type boolean".into(),
59 )),
60 }
61}
62
63pub(super) fn bind_index_expression(
64 engine: &dyn SchemaExpressionCatalog,
65 binding: &BindingContext<'_>,
66 table: &str,
67 expression: &mut Expr,
68 predicate: bool,
69) -> Result<Option<ColumnType>, SQLError> {
70 let context = if predicate {
71 "index predicate"
72 } else {
73 "index expression"
74 };
75 let mut plan = ExpressionPlan::lower(expression.clone());
76 if !plan.subqueries.is_empty() {
77 return Err(index_error(
78 "0A000",
79 format!("cannot use subquery in {context}"),
80 ));
81 }
82 if crate::semantics::aggregates::contains_aggregate(engine, &plan.scalar) {
83 return Err(index_error(
84 "42803",
85 format!("aggregate functions are not allowed in {context}s"),
86 ));
87 }
88 if crate::semantics::windows::expr_has_window(&plan.scalar) {
89 return Err(index_error(
90 "42P20",
91 format!("window functions are not allowed in {context}s"),
92 ));
93 }
94 let columns = engine
95 .schema_expression_columns(table)?
96 .ok_or_else(|| SQLError::UnknownTable(table.into()))?;
97 let relation =
98 uqa_core::RelationIdentity::from_legacy_name(table).map_err(SQLError::Internal)?;
99 bind_schema_column_references(expression, &relation.name);
100 bind_schema_column_references(expression, table);
101 plan.scalar = ExpressionPlan::lower(expression.clone()).scalar;
102 let schema = RowSchema::with_types(
103 columns.iter().map(|column| column.name.clone()).collect(),
104 columns
105 .iter()
106 .map(|column| Some(column.ty.clone()))
107 .collect(),
108 );
109 if crate::semantics::sets::validation::expression_may_return_set(
110 engine,
111 engine,
112 &plan.scalar,
113 &schema,
114 &[],
115 )? {
116 return Err(index_error(
117 "0A000",
118 format!("set-returning functions are not allowed in {context}s"),
119 ));
120 }
121 let lowered = plan.clone();
122 let ty = crate::binding::bind_expression_plan_routines_for_storage(
123 engine,
124 &mut plan,
125 &[],
126 binding,
127 &schema,
128 )?;
129 let sites = crate::binding::syntax_sites::expression_syntax_sites(&lowered, &plan)?;
130 crate::catalog::stored_ast::bind_stored_expression_sites(expression, &sites)?;
131 Ok(ty)
132}
133
134pub(super) fn validate_index_expression_immutability(
135 engine: &dyn SchemaExpressionCatalog,
136 table: &str,
137 expression: &mut Expr,
138 predicate: bool,
139) -> Result<(), SQLError> {
140 let columns = engine
141 .schema_expression_columns(table)?
142 .ok_or_else(|| SQLError::UnknownTable(table.into()))?;
143 let planned = engine.plan_schema_expression(expression, &columns)?;
144 if !planned.immutable {
145 let context = if predicate { "predicate" } else { "expression" };
146 return Err(index_error(
147 "42P17",
148 format!("functions in index {context} must be marked IMMUTABLE"),
149 ));
150 }
151 *expression = planned.expression;
152 Ok(())
153}
154
155fn index_error(sqlstate: &str, message: String) -> SQLError {
156 SQLError::Routine {
157 sqlstate: sqlstate.into(),
158 message,
159 }
160}
161
162pub mod keys;
163
164pub mod names;
165
166pub mod unique;
167
168pub mod options;
169pub mod system_columns;
170pub mod vectors;
171
172pub mod removal;
173pub mod routines;