uqa_sql/assignment/
domain.rs1use super::AssignmentContext;
10use crate::{ColumnType, ResultRow, RowSchema, SQLError};
11use uqa_core::Value;
12
13pub fn cast_domain_value(
14 context: &dyn AssignmentContext,
15 value: &Value,
16 source: Option<&str>,
17 ty: &ColumnType,
18) -> Result<Option<Value>, SQLError> {
19 convert_domain_value(context, value, source, ty, false)
20}
21
22pub fn assign_domain_value(
23 context: &dyn AssignmentContext,
24 value: &Value,
25 ty: &ColumnType,
26) -> Result<Option<Value>, SQLError> {
27 convert_domain_value(context, value, None, ty, true)
28}
29
30fn convert_domain_value(
31 context: &dyn AssignmentContext,
32 value: &Value,
33 source: Option<&str>,
34 ty: &ColumnType,
35 assignment: bool,
36) -> Result<Option<Value>, SQLError> {
37 let ColumnType::Domain { oid, .. } = ty else {
38 return Ok(None);
39 };
40 let Some(domain) = context.domain_by_oid(*oid) else {
41 return Ok(None);
42 };
43 if source
44 .and_then(|name| context.resolve_type_name(name).ok().flatten())
45 .as_ref()
46 == Some(ty)
47 {
48 return Ok(Some(value.clone()));
49 }
50 let mut chain = vec![domain.clone()];
51 let mut base = domain.definition.base.clone();
52 while let ColumnType::Domain {
53 oid,
54 base: underlying,
55 ..
56 } = &base
57 {
58 if let Some(parent) = context.domain_by_oid(*oid) {
59 base = parent.definition.base.clone();
60 chain.push(parent);
61 } else {
62 base = *underlying.clone();
63 }
64 }
65 let value = if assignment {
66 super::conversion::convert_value_to_column_type_with_context(context, value.clone(), &base)?
67 } else {
68 crate::expr::cast_value_with_type_resolution(
69 value,
70 source,
71 &base.sql_name(),
72 Some(context),
73 )?
74 };
75 if matches!(value, Value::Null)
76 && chain
77 .iter()
78 .any(|domain| domain.definition.not_null.is_some())
79 {
80 return Err(domain_error(
81 "23502",
82 format!(
83 "domain {} does not allow null values",
84 domain_display_name(context, domain.oid)?
85 ),
86 ));
87 }
88 let row = ResultRow::from([("value".into(), value.clone())]);
89 let schema = RowSchema::with_types(
90 vec!["value".into()],
91 vec![Some(domain.definition.base.clone())],
92 );
93 for check in chain
94 .iter()
95 .rev()
96 .flat_map(|domain| &domain.definition.checks)
97 {
98 let result = context.evaluate_domain_check(&check.expression, &row, &schema)?;
99 if result == Value::Bool(false) {
100 return Err(domain_error(
101 "23514",
102 format!(
103 "value for domain {} violates check constraint \"{}\"",
104 domain_display_name(context, domain.oid)?,
105 check.name.as_deref().expect("bound domain constraint")
106 ),
107 ));
108 }
109 }
110 Ok(Some(value))
111}
112
113fn domain_display_name(context: &dyn AssignmentContext, oid: u32) -> Result<String, SQLError> {
114 context
115 .resolve_regtype_output(&ColumnType::Regtype, i64::from(oid))
116 .map_err(SQLError::Internal)?
117 .ok_or_else(|| SQLError::Internal("domain type has no catalog display name".into()))
118}
119
120pub fn domain_error(sqlstate: &str, message: impl Into<String>) -> SQLError {
121 SQLError::Routine {
122 sqlstate: sqlstate.into(),
123 message: message.into(),
124 }
125}