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