uqa_sql/assignment/
columns.rs1use 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
17pub trait AssignmentColumnCatalog {
18 fn try_describe_table(&self, table: &str)
19 -> Result<Option<Vec<ColumnDef>>, ColumnCatalogError>;
20 fn columns_declared(&self, table: &str) -> Result<bool, ColumnCatalogError>;
21 fn try_column_insert_default_expr(
22 &self,
23 table: &str,
24 column: &str,
25 ) -> Result<Option<Expr>, ColumnCatalogError>;
26}
27fn dml_storage_error(action: &str, error: impl std::fmt::Display) -> SQLError {
28 SQLError::Internal(format!("{action} failed in storage backend: {error}"))
29}
30fn ddl_storage_error(action: &str, error: ColumnCatalogError) -> SQLError {
31 crate::catalog::errors::storage_error(action, error.as_ref())
32}
33pub fn coerce_to_column_type(
35 assignment: &dyn AssignmentContext,
36 catalog: &dyn AssignmentColumnCatalog,
37 table: &str,
38 column: &str,
39 value: Value,
40) -> Result<Value, SQLError> {
41 coerce_to_column_type_from(assignment, catalog, table, column, value, None)
42}
43
44pub fn coerce_to_column_type_from(
45 assignment: &dyn AssignmentContext,
46 catalog: &dyn AssignmentColumnCatalog,
47 table: &str,
48 column: &str,
49 value: Value,
50 source: Option<&ColumnType>,
51) -> Result<Value, SQLError> {
52 let cols = match catalog
53 .try_describe_table(table)
54 .map_err(|err| ddl_storage_error("column type coercion", err))?
55 {
56 Some(c) => c,
57 None => return Ok(value),
58 };
59 let Some(def) = cols.iter().find(|c| c.name == column) else {
60 return Ok(value);
61 };
62 coerce_assignment_value(assignment, value, &def.ty, source)
63}
64
65pub fn validate_mutation_columns<'a>(
66 catalog: &dyn AssignmentColumnCatalog,
67 table: &str,
68 columns: impl IntoIterator<Item = &'a str>,
69 action: &str,
70) -> Result<(), SQLError> {
71 let definitions = catalog
72 .try_describe_table(table)
73 .map_err(|err| dml_storage_error(action, err))?
74 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
75 if definitions.is_empty() {
80 let declared = catalog
81 .columns_declared(table)
82 .map_err(|error| dml_storage_error(action, error))?;
83 if !declared {
84 return Ok(());
85 }
86 }
87 let known: BTreeSet<&str> = definitions
88 .iter()
89 .map(|definition| definition.name.as_str())
90 .collect();
91 let mut seen = BTreeSet::new();
92 for column in columns {
93 if !seen.insert(column) {
94 return Err(SQLError::TypeMismatch(format!(
95 "{action}: column `{column}` is specified more than once"
96 )));
97 }
98 if !known.contains(column) {
99 let relation = RelationIdentity::from_legacy_name(table).map_err(SQLError::Internal)?;
100 return Err(SQLError::Routine {
101 sqlstate: "42703".into(),
102 message: format!(
103 "column \"{column}\" of relation \"{}\" does not exist",
104 relation.name
105 ),
106 });
107 }
108 }
109 Ok(())
110}
111
112pub fn generated_column_kind(
113 catalog: &dyn AssignmentColumnCatalog,
114 table: &str,
115 column: &str,
116) -> Result<Option<GeneratedColumnKind>, SQLError> {
117 Ok(catalog
118 .try_describe_table(table)
119 .map_err(|error| SQLError::Internal(format!("read generated column: {error}")))?
120 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?
121 .into_iter()
122 .find(|definition| definition.name == column)
123 .and_then(|definition| definition.generated.map(|generated| generated.kind)))
124}