uqa_sql/semantics/
generated_values.rs1use crate::{
10 assignment::columns::AssignmentColumnCatalog,
11 ast::{AutoIncrementKind, OverridingKind},
12 SQLError,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17enum Generation {
18 IdentityAlways,
19 IdentityByDefault,
20 Expression,
21}
22
23pub struct GeneratedValueColumns {
25 columns: Vec<(String, Generation)>,
26}
27
28impl GeneratedValueColumns {
29 pub fn of(catalog: &dyn AssignmentColumnCatalog, table: &str) -> Result<Self, SQLError> {
31 let columns = catalog
32 .try_describe_table(table)
33 .map_err(|error| SQLError::Internal(format!("read generated columns: {error}")))?
34 .unwrap_or_default()
35 .into_iter()
36 .filter_map(|column| {
37 let generation = match column.auto_increment.as_ref() {
38 Some(provenance) if provenance.kind == AutoIncrementKind::IdentityAlways => {
39 Generation::IdentityAlways
40 }
41 Some(provenance) if provenance.is_identity() => Generation::IdentityByDefault,
42 _ if column.generated.is_some() => Generation::Expression,
43 _ => return None,
44 };
45 Some((column.name, generation))
46 })
47 .collect();
48 Ok(Self { columns })
49 }
50
51 #[must_use]
53 pub fn contains(&self, column: &str) -> bool {
54 self.columns.iter().any(|(name, generation)| {
55 name == column
56 && matches!(
57 generation,
58 Generation::IdentityAlways | Generation::IdentityByDefault
59 )
60 })
61 }
62
63 pub fn validate_insert<'a>(
65 &self,
66 targets: impl IntoIterator<Item = (&'a str, bool)>,
67 overriding: Option<OverridingKind>,
68 ) -> Result<(), SQLError> {
69 let supplied = targets
70 .into_iter()
71 .filter_map(|(column, supplied)| supplied.then_some(column))
72 .collect::<std::collections::BTreeSet<_>>();
73 for (column, generation) in &self.columns {
74 if !supplied.contains(column.as_str()) {
75 continue;
76 }
77 match generation {
78 Generation::Expression => return Err(generated_column_insert_error(column)),
79 Generation::IdentityAlways if overriding.is_none() => {
80 return Err(generated_always_insert_error(column));
81 }
82 Generation::IdentityAlways | Generation::IdentityByDefault => {}
83 }
84 }
85 Ok(())
86 }
87
88 pub fn validate_update<'a>(
90 &self,
91 assignments: impl IntoIterator<Item = (&'a str, bool)>,
92 ) -> Result<(), SQLError> {
93 let assigned = assignments
94 .into_iter()
95 .filter_map(|(column, default)| (!default).then_some(column))
96 .collect::<std::collections::BTreeSet<_>>();
97 for (column, generation) in &self.columns {
98 if !assigned.contains(column.as_str()) {
99 continue;
100 }
101 match generation {
102 Generation::Expression => return Err(generated_column_update_error(column)),
103 Generation::IdentityAlways => return Err(generated_always_update_error(column)),
104 Generation::IdentityByDefault => {}
105 }
106 }
107 Ok(())
108 }
109}
110
111#[must_use]
113pub fn generated_always_insert_error(column: &str) -> SQLError {
114 SQLError::Diagnostic {
115 sqlstate: "428C9".into(),
116 message: format!("cannot insert a non-DEFAULT value into column \"{column}\""),
117 detail: Some(format!(
118 "Column \"{column}\" is an identity column defined as GENERATED ALWAYS."
119 )),
120 hint: Some("Use OVERRIDING SYSTEM VALUE to override.".into()),
121 }
122}
123
124#[must_use]
126pub fn generated_always_update_error(column: &str) -> SQLError {
127 SQLError::Diagnostic {
128 sqlstate: "428C9".into(),
129 message: format!("column \"{column}\" can only be updated to DEFAULT"),
130 detail: Some(format!(
131 "Column \"{column}\" is an identity column defined as GENERATED ALWAYS."
132 )),
133 hint: None,
134 }
135}
136
137#[must_use]
139pub fn generated_column_insert_error(column: &str) -> SQLError {
140 SQLError::Diagnostic {
141 sqlstate: "428C9".into(),
142 message: format!("cannot insert a non-DEFAULT value into column \"{column}\""),
143 detail: Some(format!("Column \"{column}\" is a generated column.")),
144 hint: None,
145 }
146}
147
148#[must_use]
150pub fn generated_column_update_error(column: &str) -> SQLError {
151 SQLError::Diagnostic {
152 sqlstate: "428C9".into(),
153 message: format!("column \"{column}\" can only be updated to DEFAULT"),
154 detail: Some(format!("Column \"{column}\" is a generated column.")),
155 hint: None,
156 }
157}