Skip to main content

uqa_sql/schema/
indexes.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Immutable index-expression binding in the indexed table's declared row type.
8
9use super::{
10    generated::{bind_schema_column_references, typing},
11    SchemaExpressionCatalog,
12};
13use crate::plan::ExpressionPlan;
14use crate::RowSchema;
15use crate::{ast::Expr, binding::context::BindingContext, ColumnType, SQLError};
16
17pub fn prepare_index_expression(
18    engine: &dyn SchemaExpressionCatalog,
19    binding: &BindingContext<'_>,
20    table: &str,
21    expression: &mut Expr,
22) -> Result<ColumnType, SQLError> {
23    let ty = bind_immutable_index_expression(engine, binding, table, expression, false)?;
24    if let Some(ty) = ty {
25        return Ok(ty);
26    }
27    *expression = Expr::Cast {
28        expr: Box::new(expression.clone()),
29        ty: "text".into(),
30    };
31    Ok(ColumnType::Text)
32}
33
34pub fn prepare_index_predicate(
35    engine: &dyn SchemaExpressionCatalog,
36    binding: &BindingContext<'_>,
37    table: &str,
38    expression: &mut Expr,
39) -> Result<(), SQLError> {
40    match bind_immutable_index_expression(engine, binding, table, expression, true)? {
41        Some(ColumnType::Boolean) => Ok(()),
42        None => {
43            if let Expr::Literal(value) = expression {
44                *value = crate::expr::cast_value(value, "boolean")?;
45            }
46            Ok(())
47        }
48        Some(_) => Err(SQLError::TypeMismatch(
49            "argument of WHERE must be type boolean".into(),
50        )),
51    }
52}
53
54fn bind_immutable_index_expression(
55    engine: &dyn SchemaExpressionCatalog,
56    binding: &BindingContext<'_>,
57    table: &str,
58    expression: &mut Expr,
59    predicate: bool,
60) -> Result<Option<ColumnType>, SQLError> {
61    let context = if predicate {
62        "index predicate"
63    } else {
64        "index expression"
65    };
66    let mut plan = ExpressionPlan::lower(expression.clone());
67    if !plan.subqueries.is_empty() {
68        return Err(index_error(
69            "0A000",
70            format!("cannot use subquery in {context}"),
71        ));
72    }
73    if crate::semantics::aggregates::contains_aggregate(engine, &plan.scalar) {
74        return Err(index_error(
75            "42803",
76            format!("aggregate functions are not allowed in {context}s"),
77        ));
78    }
79    if crate::semantics::windows::expr_has_window(&plan.scalar) {
80        return Err(index_error(
81            "42P20",
82            format!("window functions are not allowed in {context}s"),
83        ));
84    }
85    let columns = engine
86        .schema_expression_columns(table)?
87        .ok_or_else(|| SQLError::UnknownTable(table.into()))?;
88    let relation =
89        uqa_core::RelationIdentity::from_legacy_name(table).map_err(SQLError::Internal)?;
90    bind_schema_column_references(expression, &relation.name);
91    bind_schema_column_references(expression, table);
92    plan.scalar = ExpressionPlan::lower(expression.clone()).scalar;
93    let schema = RowSchema::with_types(
94        columns.iter().map(|column| column.name.clone()).collect(),
95        columns
96            .iter()
97            .map(|column| Some(column.ty.clone()))
98            .collect(),
99    );
100    if crate::semantics::sets::validation::expression_may_return_set(
101        engine,
102        engine,
103        &plan.scalar,
104        &schema,
105        &[],
106    )? {
107        return Err(index_error(
108            "0A000",
109            format!("set-returning functions are not allowed in {context}s"),
110        ));
111    }
112    typing::infer_generation_expression(engine, &columns, expression)?;
113    let ty = crate::binding::bind_expression_plan_routines_for_storage(
114        engine,
115        &mut plan,
116        &[],
117        binding,
118        &schema,
119    )?;
120    let references = crate::binding::stored_routines::collect_expression_routine_references(&plan)?;
121    crate::catalog::stored_ast::bind_stored_expression_routines(expression, &references)?;
122    Ok(ty)
123}
124
125fn index_error(sqlstate: &str, message: String) -> SQLError {
126    SQLError::Routine {
127        sqlstate: sqlstate.into(),
128        message,
129    }
130}
131
132pub mod keys;
133
134pub mod names;
135
136pub mod unique;
137
138pub mod options;
139pub mod vectors;
140
141pub mod removal;
142pub mod routines;