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_column_definitions(None, &columns);
103 if crate::semantics::sets::validation::expression_may_return_set(
104 engine,
105 engine,
106 &plan.scalar,
107 &schema,
108 &[],
109 )? {
110 return Err(index_error(
111 "0A000",
112 format!("set-returning functions are not allowed in {context}s"),
113 ));
114 }
115 let lowered = plan.clone();
116 let ty = crate::binding::bind_expression_plan_routines_for_storage(
117 engine,
118 &mut plan,
119 &[],
120 binding,
121 &schema,
122 )?;
123 let sites = crate::binding::syntax_sites::expression_syntax_sites(&lowered, &plan)?;
124 crate::catalog::stored_ast::bind_stored_expression_sites(expression, &sites)?;
125 Ok(ty)
126}
127
128pub(super) fn validate_index_expression_immutability(
129 engine: &dyn SchemaExpressionCatalog,
130 table: &str,
131 expression: &mut Expr,
132 predicate: bool,
133) -> Result<(), SQLError> {
134 let columns = engine
135 .schema_expression_columns(table)?
136 .ok_or_else(|| SQLError::UnknownTable(table.into()))?;
137 let planned = engine.plan_schema_expression(expression, &columns)?;
138 if !planned.immutable {
139 let context = if predicate { "predicate" } else { "expression" };
140 return Err(index_error(
141 "42P17",
142 format!("functions in index {context} must be marked IMMUTABLE"),
143 ));
144 }
145 *expression = planned.expression;
146 Ok(())
147}
148
149fn index_error(sqlstate: &str, message: String) -> SQLError {
150 SQLError::Routine {
151 sqlstate: sqlstate.into(),
152 message,
153 }
154}
155
156pub mod keys;
157
158pub mod names;
159
160pub mod unique;
161
162pub mod options;
163pub mod system_columns;
164pub mod vectors;
165
166pub mod removal;
167pub mod routines;