uqa_sql/semantics/
mutation_qualifiers.rs1use 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
23pub 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}