uqa_sql/semantics/
generated_values.rs1use crate::{
10 assignment::columns::AssignmentColumnCatalog,
11 ast::{AutoIncrementKind, OverridingKind},
12 SQLError,
13};
14
15#[must_use]
17pub fn stored_columns_for_update(
18 columns: &[crate::ast::ColumnDef],
19 updated_columns: &[String],
20) -> Vec<String> {
21 columns
22 .iter()
23 .filter(|column| {
24 column.generated.as_ref().is_some_and(|generated| {
25 generated.kind == crate::ast::GeneratedColumnKind::Stored
26 && updated_columns.iter().any(|updated| {
27 crate::schema::dependencies::schema_expr_references_column(
28 &generated.expression,
29 updated,
30 )
31 })
32 })
33 })
34 .map(|column| column.name.clone())
35 .collect()
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40enum Generation {
41 IdentityAlways,
42 IdentityByDefault,
43 Expression,
44}
45
46pub struct GeneratedValueColumns {
48 columns: Vec<(String, Generation)>,
49}
50
51impl GeneratedValueColumns {
52 pub fn of(catalog: &dyn AssignmentColumnCatalog, table: &str) -> Result<Self, SQLError> {
54 let columns = catalog
55 .try_describe_table(table)
56 .map_err(|error| SQLError::Internal(format!("read generated columns: {error}")))?
57 .unwrap_or_default()
58 .into_iter()
59 .filter_map(|column| {
60 let generation = match column.auto_increment.as_ref() {
61 Some(provenance) if provenance.kind == AutoIncrementKind::IdentityAlways => {
62 Generation::IdentityAlways
63 }
64 Some(provenance) if provenance.is_identity() => Generation::IdentityByDefault,
65 _ if column.generated.is_some() => Generation::Expression,
66 _ => return None,
67 };
68 Some((column.name, generation))
69 })
70 .collect();
71 Ok(Self { columns })
72 }
73
74 #[must_use]
76 pub fn contains(&self, column: &str) -> bool {
77 self.columns.iter().any(|(name, generation)| {
78 name == column
79 && matches!(
80 generation,
81 Generation::IdentityAlways | Generation::IdentityByDefault
82 )
83 })
84 }
85
86 pub fn validate_insert<'a>(
88 &self,
89 targets: impl IntoIterator<Item = (&'a str, bool)>,
90 overriding: Option<OverridingKind>,
91 ) -> Result<(), SQLError> {
92 let supplied = targets
93 .into_iter()
94 .filter_map(|(column, supplied)| supplied.then_some(column))
95 .collect::<std::collections::BTreeSet<_>>();
96 for (column, generation) in &self.columns {
97 if !supplied.contains(column.as_str()) {
98 continue;
99 }
100 match generation {
101 Generation::Expression => return Err(generated_column_insert_error(column)),
102 Generation::IdentityAlways if overriding.is_none() => {
103 return Err(generated_always_insert_error(column));
104 }
105 Generation::IdentityAlways | Generation::IdentityByDefault => {}
106 }
107 }
108 Ok(())
109 }
110
111 pub fn validate_update<'a>(
113 &self,
114 assignments: impl IntoIterator<Item = (&'a str, bool)>,
115 ) -> Result<(), SQLError> {
116 let assigned = assignments
117 .into_iter()
118 .filter_map(|(column, default)| (!default).then_some(column))
119 .collect::<std::collections::BTreeSet<_>>();
120 for (column, generation) in &self.columns {
121 if !assigned.contains(column.as_str()) {
122 continue;
123 }
124 match generation {
125 Generation::Expression => return Err(generated_column_update_error(column)),
126 Generation::IdentityAlways => return Err(generated_always_update_error(column)),
127 Generation::IdentityByDefault => {}
128 }
129 }
130 Ok(())
131 }
132}
133
134#[must_use]
136pub fn generated_always_insert_error(column: &str) -> SQLError {
137 SQLError::Diagnostic {
138 sqlstate: "428C9".into(),
139 message: format!("cannot insert a non-DEFAULT value into column \"{column}\""),
140 detail: Some(format!(
141 "Column \"{column}\" is an identity column defined as GENERATED ALWAYS."
142 )),
143 hint: Some("Use OVERRIDING SYSTEM VALUE to override.".into()),
144 }
145}
146
147#[must_use]
149pub fn generated_always_update_error(column: &str) -> SQLError {
150 SQLError::Diagnostic {
151 sqlstate: "428C9".into(),
152 message: format!("column \"{column}\" can only be updated to DEFAULT"),
153 detail: Some(format!(
154 "Column \"{column}\" is an identity column defined as GENERATED ALWAYS."
155 )),
156 hint: None,
157 }
158}
159
160#[must_use]
162pub fn generated_column_insert_error(column: &str) -> SQLError {
163 SQLError::Diagnostic {
164 sqlstate: "428C9".into(),
165 message: format!("cannot insert a non-DEFAULT value into column \"{column}\""),
166 detail: Some(format!("Column \"{column}\" is a generated column.")),
167 hint: None,
168 }
169}
170
171#[must_use]
173pub fn generated_column_update_error(column: &str) -> SQLError {
174 SQLError::Diagnostic {
175 sqlstate: "428C9".into(),
176 message: format!("column \"{column}\" can only be updated to DEFAULT"),
177 detail: Some(format!("Column \"{column}\" is a generated column.")),
178 hint: None,
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 #[test]
185 fn stored_update_selection_follows_expression_inputs() {
186 let crate::Statement::CreateTable(table) = crate::compile("CREATE TABLE t(a int,b int,unrelated int,ga int GENERATED ALWAYS AS(coalesce(a,1)) STORED,gb int GENERATED ALWAYS AS(b+2) STORED,c int GENERATED ALWAYS AS(42) STORED,v int GENERATED ALWAYS AS(b+3) VIRTUAL)").unwrap().remove(0) else { panic!("table") };
187 for (targets, expected) in [
188 (vec!["a"], vec!["ga"]),
189 (vec!["b"], vec!["gb"]),
190 (vec!["a", "b"], vec!["ga", "gb"]),
191 (vec!["unrelated"], vec![]),
192 (vec!["ga", "c", "v"], vec![]),
193 ] {
194 let targets = targets.into_iter().map(str::to_owned).collect::<Vec<_>>();
195 assert_eq!(
196 super::stored_columns_for_update(&table.columns, &targets),
197 expected
198 );
199 }
200 }
201}