Skip to main content

uqa_sql/semantics/
mutation_qualifiers.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Relation qualifier visibility in mutation expressions.
8use crate::{SQLError, ScalarExpr};
9use std::collections::BTreeSet;
10
11pub fn validate_dml_expression_qualifiers(
12    expression: &ScalarExpr,
13    allowed: &BTreeSet<String>,
14) -> Result<(), SQLError> {
15    for qualifier in super::expr_qualifiers(expression) {
16        if !allowed.contains(&qualifier) {
17            return Err(SQLError::UnknownTable(qualifier));
18        }
19    }
20    Ok(())
21}
22
23/// Visible target column names, including schema-less relation columns.
24pub trait MutationTargetColumns {
25    fn try_query_table_columns(&self, table: &str) -> Result<Vec<String>, String>;
26}
27pub fn qualification_references_target(
28    catalog: &dyn MutationTargetColumns,
29    table: &str,
30    target_qualifier: &str,
31    operation: &str,
32    predicate: Option<&ScalarExpr>,
33) -> Result<bool, SQLError> {
34    let Some(predicate) = predicate else {
35        return Ok(false);
36    };
37    if super::expr_contains_subquery(predicate) {
38        return Ok(true);
39    }
40    let qualifiers = super::expr_qualifiers(predicate);
41    if qualifiers.iter().any(|qualifier| {
42        qualifier.eq_ignore_ascii_case(target_qualifier) || qualifier.eq_ignore_ascii_case(table)
43    }) {
44        return Ok(true);
45    }
46    if !super::expr_has_unqualified_column(predicate) {
47        return Ok(false);
48    }
49    let mut columns = std::collections::BTreeSet::new();
50    if !predicate.collect_columns(&mut columns) {
51        return Ok(true);
52    }
53    let target_columns = catalog
54        .try_query_table_columns(table)
55        .map_err(|error| SQLError::Internal(format!("read {operation} target columns: {error}")))?
56        .into_iter()
57        .chain([
58            super::DOC_ID_COLUMN.to_string(),
59            super::TABLE_OID_COLUMN.to_string(),
60            super::XMIN_COLUMN.to_string(),
61        ])
62        .collect::<std::collections::BTreeSet<_>>();
63    Ok(!columns.is_disjoint(&target_columns))
64}