uqa_sql/semantics/
mutation_patch.rs1use crate::{
9 catalog::errors::dml_storage_error,
10 semantics::{
11 constraint_catalog::ConstraintCatalog,
12 referential::{referrers_to_for_actions, ReferentialCatalog},
13 },
14 SQLError,
15};
16use std::collections::{BTreeMap, BTreeSet};
17use uqa_core::Value;
18pub fn can_patch_update_without_full_row(
19 catalog: &dyn ConstraintCatalog,
20 table: &str,
21 referrers: &dyn ReferentialCatalog,
22 updates: &BTreeMap<String, Value>,
23) -> Result<bool, SQLError> {
24 if catalog
25 .try_check_constraint_definitions(table)
26 .map_err(|err| dml_storage_error("UPDATE", err))?
27 .iter()
28 .any(|constraint| constraint.enforced)
29 {
30 return Ok(false);
31 }
32 let update_keys: BTreeSet<&str> = updates.keys().map(String::as_str).collect();
33 if catalog
34 .try_describe_table(table)
35 .map_err(|err| dml_storage_error("UPDATE", err))?
36 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?
37 .iter()
38 .any(|col| {
39 col.not_null
40 && col.auto_increment.is_none()
41 && matches!(updates.get(&col.name), Some(Value::Null))
42 })
43 {
44 return Ok(false);
45 }
46 if catalog
47 .enforced_keys(table)
48 .map_err(|err| dml_storage_error("UPDATE", err))?
49 .iter()
50 .any(|constraint| {
51 constraint.predicate.as_deref().is_some_and(|predicate| {
52 update_keys.iter().any(|column| {
53 crate::schema::dependencies::schema_expr_references_column(predicate, column)
54 })
55 }) || constraint.keys.iter().any(|key| {
56 update_keys.iter().any(|column| {
57 crate::schema::dependencies::schema_expr_references_column(
58 &key.expression(),
59 column,
60 )
61 })
62 })
63 })
64 {
65 return Ok(false);
66 }
67 if catalog
68 .try_foreign_keys(table)
69 .map_err(|err| dml_storage_error("UPDATE", err))?
70 .iter()
71 .filter(|fk| fk.enforced)
72 .any(|fk| {
73 fk.local_columns
74 .iter()
75 .any(|column| update_keys.contains(column.as_str()))
76 })
77 {
78 return Ok(false);
79 }
80 if referrers_to_for_actions(referrers, table)?
81 .iter()
82 .any(|(_, fk)| {
83 fk.ref_columns
84 .iter()
85 .any(|column| update_keys.contains(column.as_str()))
86 })
87 {
88 return Ok(false);
89 }
90 Ok(true)
91}
92
93pub fn point_lookup_field_is_unique(
94 catalog: &dyn ConstraintCatalog,
95 table: &str,
96 lookup_field: &str,
97) -> Result<bool, SQLError> {
98 Ok(catalog
99 .enforced_keys(table)
100 .map_err(|err| dml_storage_error("UPDATE", err))?
101 .iter()
102 .any(|constraint| {
103 constraint.predicate.is_none()
104 && constraint.keys.len() == 1
105 && constraint.keys[0].column() == Some(lookup_field)
106 }))
107}