1use super::{conversion::coerce_assignment_value, AssignmentContext};
9use crate::{
10 ast::{ColumnDef, ColumnType, Expr, GeneratedColumnKind},
11 SQLError,
12};
13use std::collections::BTreeSet;
14use uqa_core::{RelationIdentity, Value};
15pub type ColumnCatalogError = Box<dyn std::error::Error>;
16
17#[derive(Debug, Clone, PartialEq)]
19pub struct ColumnShape {
20 pub ty: ColumnType,
21 pub generated: Option<GeneratedColumnKind>,
22 pub identity_sequence: Option<String>,
24}
25
26fn identity_sequence(definition: &ColumnDef) -> Option<String> {
28 definition
29 .auto_increment
30 .as_ref()
31 .filter(|provenance| provenance.is_identity())
32 .and_then(|provenance| provenance.sequence.clone())
33}
34
35pub trait AssignmentColumnCatalog {
36 fn try_describe_table(&self, table: &str)
37 -> Result<Option<Vec<ColumnDef>>, ColumnCatalogError>;
38 fn columns_declared(&self, table: &str) -> Result<bool, ColumnCatalogError>;
39 fn try_column_insert_default_expr(
40 &self,
41 table: &str,
42 column: &str,
43 ) -> Result<Option<Expr>, ColumnCatalogError>;
44 fn try_identity_column_sequence(
46 &self,
47 table: &str,
48 column: &str,
49 ) -> Result<Option<String>, SQLError> {
50 Ok(self
51 .try_column_shape(table, column)
52 .map_err(|error| SQLError::Internal(format!("read identity column: {error}")))?
53 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?
54 .and_then(|shape| shape.identity_sequence))
55 }
56 fn try_column_shape(
58 &self,
59 table: &str,
60 column: &str,
61 ) -> Result<Option<Option<ColumnShape>>, ColumnCatalogError> {
62 Ok(self.try_describe_table(table)?.map(|columns| {
63 columns
64 .into_iter()
65 .find(|definition| definition.name == column)
66 .map(|definition| ColumnShape {
67 identity_sequence: identity_sequence(&definition),
68 ty: definition.ty,
69 generated: definition.generated.map(|generated| generated.kind),
70 })
71 }))
72 }
73}
74fn dml_storage_error(action: &str, error: impl std::fmt::Display) -> SQLError {
75 SQLError::Internal(format!("{action} failed in storage backend: {error}"))
76}
77fn ddl_storage_error(action: &str, error: ColumnCatalogError) -> SQLError {
78 crate::catalog::errors::storage_error(action, error.as_ref())
79}
80pub fn coerce_to_column_type(
82 assignment: &dyn AssignmentContext,
83 catalog: &dyn AssignmentColumnCatalog,
84 table: &str,
85 column: &str,
86 value: Value,
87) -> Result<Value, SQLError> {
88 coerce_to_column_type_from(assignment, catalog, table, column, value, None)
89}
90
91pub fn coerce_to_column_type_from(
92 assignment: &dyn AssignmentContext,
93 catalog: &dyn AssignmentColumnCatalog,
94 table: &str,
95 column: &str,
96 value: Value,
97 source: Option<&ColumnType>,
98) -> Result<Value, SQLError> {
99 let Some(Some(shape)) = catalog
100 .try_column_shape(table, column)
101 .map_err(|err| ddl_storage_error("column type coercion", err))?
102 else {
103 return Ok(value);
104 };
105 coerce_assignment_value(assignment, value, &shape.ty, source)
106}
107
108pub fn validate_mutation_columns<'a>(
109 catalog: &dyn AssignmentColumnCatalog,
110 table: &str,
111 columns: impl IntoIterator<Item = &'a str>,
112 action: &str,
113) -> Result<(), SQLError> {
114 let definitions = catalog
115 .try_describe_table(table)
116 .map_err(|err| dml_storage_error(action, err))?
117 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
118 if definitions.is_empty() {
123 let declared = catalog
124 .columns_declared(table)
125 .map_err(|error| dml_storage_error(action, error))?;
126 if !declared {
127 return Ok(());
128 }
129 }
130 let known: BTreeSet<&str> = definitions
131 .iter()
132 .map(|definition| definition.name.as_str())
133 .collect();
134 let mut seen = BTreeSet::new();
135 for column in columns {
136 if !seen.insert(column) {
137 return Err(SQLError::TypeMismatch(format!(
138 "{action}: column `{column}` is specified more than once"
139 )));
140 }
141 if !known.contains(column) {
142 let relation = RelationIdentity::from_legacy_name(table).map_err(SQLError::Internal)?;
143 return Err(SQLError::Routine {
144 sqlstate: "42703".into(),
145 message: format!(
146 "column \"{column}\" of relation \"{}\" does not exist",
147 relation.name
148 ),
149 });
150 }
151 }
152 Ok(())
153}
154
155pub fn identity_column_sequence(
157 catalog: &dyn AssignmentColumnCatalog,
158 table: &str,
159 column: &str,
160) -> Result<Option<String>, SQLError> {
161 catalog.try_identity_column_sequence(table, column)
162}
163
164pub fn generated_column_kind(
165 catalog: &dyn AssignmentColumnCatalog,
166 table: &str,
167 column: &str,
168) -> Result<Option<GeneratedColumnKind>, SQLError> {
169 Ok(catalog
170 .try_column_shape(table, column)
171 .map_err(|error| SQLError::Internal(format!("read generated column: {error}")))?
172 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?
173 .and_then(|shape| shape.generated))
174}
175
176pub fn validate_mutation_targets<'a, E: 'a>(
178 catalog: &dyn AssignmentColumnCatalog,
179 table: &str,
180 targets: impl IntoIterator<Item = &'a crate::ast::AssignmentTarget<E>>,
181 action: &str,
182 insert: bool,
183) -> Result<(), SQLError> {
184 let targets = targets.into_iter().collect::<Vec<_>>();
185 super::targets::validate_repeated_targets(targets.iter().copied(), insert)?;
186 let mut seen = BTreeSet::new();
187 let names = targets
188 .iter()
189 .map(|target| target.column.as_str())
190 .filter(|name| seen.insert(*name));
191 validate_mutation_columns(catalog, table, names, action)
192}