1use super::AssignmentContext;
10use crate::{ColumnType, SQLError};
11use uqa_core::{ArrayValue, DecimalValue, TemporalValue, Value};
12
13mod carrier;
14pub(super) use carrier::normalize_existing;
15pub use carrier::{contains_legacy_vectors, normalize_legacy_vector_carrier_with_control};
16
17pub fn coerce_assignment_value(
18 context: &dyn AssignmentContext,
19 value: Value,
20 target: &ColumnType,
21 source: Option<&ColumnType>,
22) -> Result<Value, SQLError> {
23 if source.is_some_and(|source| same_domain_identity(source, target)) {
24 return normalize_existing(value, target);
25 }
26 let value = if target.is_character_string() {
27 source
28 .map(|source| crate::expr::format_regtype_value(&value, source, Some(context)))
29 .transpose()?
30 .flatten()
31 .map(Value::Str)
32 .unwrap_or(value)
33 } else {
34 value
35 };
36 convert_value_to_column_type_with_context(context, value, target)
37}
38
39fn same_domain_identity(source: &ColumnType, target: &ColumnType) -> bool {
40 match (source, target) {
41 (ColumnType::Domain { oid: source, .. }, ColumnType::Domain { oid: target, .. }) => {
42 source == target
43 }
44 (ColumnType::Array(source), ColumnType::Array(target)) => {
45 same_domain_identity(source, target)
46 }
47 _ => false,
48 }
49}
50
51pub fn coerce_json_value(value: Value, jsonb: bool) -> Result<Value, SQLError> {
52 crate::expr::cast_value(&value, if jsonb { "jsonb" } else { "json" })
53}
54
55pub fn convert_declared_value_to_column_type(
56 context: &dyn AssignmentContext,
57 value: Value,
58 source_ty: &ColumnType,
59 target_ty: &ColumnType,
60) -> Result<Value, SQLError> {
61 match (source_ty, target_ty) {
62 (ColumnType::Domain { base, .. }, target) => {
63 convert_declared_value_to_column_type(context, value, base, target)
64 }
65 (source, ColumnType::Domain { base, .. }) => {
66 convert_declared_value_to_column_type(context, value, source, base)
67 }
68 (ColumnType::Array(source), ColumnType::Array(target)) => {
69 let Value::Array(array) = value else {
70 return Err(SQLError::TypeMismatch(format!(
71 "cannot cast a non-array value to {}[]",
72 column_type_name(target)
73 )));
74 };
75 let source = array_scalar_type(source);
76 let target = array_scalar_type(target);
77 let converted =
78 convert_declared_array_elements(context, array.elements(), source, target)?;
79 ArrayValue::with_lower_bounds(converted, array.lower_bounds().to_vec())
80 .map(Value::Array)
81 .ok_or_else(|| {
82 SQLError::TypeMismatch(
83 "multidimensional arrays must have matching dimensions".into(),
84 )
85 })
86 }
87 (ColumnType::Range(source), ColumnType::Range(target)) if source == target => {
88 crate::expr::cast_value_from(&value, target.range_name(), Some(source.range_name()))
89 }
90 (ColumnType::Range(source), ColumnType::Multirange(target)) if source == target => {
91 crate::expr::cast_value_from(
92 &value,
93 target.multirange_name(),
94 Some(source.range_name()),
95 )
96 }
97 (ColumnType::Multirange(source), ColumnType::Multirange(target)) if source == target => {
98 crate::expr::cast_value_from(
99 &value,
100 target.multirange_name(),
101 Some(source.multirange_name()),
102 )
103 }
104 (_, ColumnType::Range(_) | ColumnType::Multirange(_)) => {
105 Err(SQLError::TypeMismatch(format!(
106 "column cannot be cast automatically from type {} to type {}",
107 column_type_name(source_ty),
108 column_type_name(target_ty)
109 )))
110 }
111 (source, ColumnType::Oid)
112 if matches!(
113 source,
114 ColumnType::SmallInteger
115 | ColumnType::Integer
116 | ColumnType::BigInteger
117 | ColumnType::Oid
118 | ColumnType::Regproc
119 | ColumnType::Regprocedure
120 | ColumnType::Regclass
121 | ColumnType::Regnamespace
122 | ColumnType::Regrole
123 | ColumnType::Regtype
124 ) =>
125 {
126 crate::expr::cast_value_from(&value, "oid", Some(column_type_name(source)))
127 }
128 (ColumnType::Xid, ColumnType::Xid) => Ok(value),
129 (ColumnType::Bytea, ColumnType::Bytea) => Ok(value),
130 (_, ColumnType::Oid | ColumnType::Xid | ColumnType::Bytea) => {
131 Err(SQLError::TypeMismatch(format!(
132 "column cannot be cast automatically from type {} to type {}",
133 column_type_name(source_ty),
134 column_type_name(target_ty)
135 )))
136 }
137 _ => convert_value_to_column_type_with_context(context, value, target_ty),
138 }
139}
140
141fn type_requires_catalog_resolution(ty: &ColumnType) -> bool {
142 match ty {
143 ColumnType::Regrole | ColumnType::Domain { .. } => true,
144 ColumnType::Array(element) => type_requires_catalog_resolution(element),
145 _ => false,
146 }
147}
148
149pub fn convert_value_to_column_type_with_context(
150 context: &dyn AssignmentContext,
151 value: Value,
152 ty: &ColumnType,
153) -> Result<Value, SQLError> {
154 if let Some(value) = super::domain::assign_domain_value(context, &value, ty)? {
155 return Ok(value);
156 }
157 if matches!(value, Value::Null) {
158 return Ok(Value::Null);
159 }
160 if let ColumnType::Array(element) = ty {
161 if type_requires_catalog_resolution(element) {
162 return convert_catalog_array(context, value, element);
163 }
164 }
165 if type_requires_catalog_resolution(ty) {
166 return crate::expr::cast_value_with_type_resolution(
167 &value,
168 None,
169 &ty.sql_name(),
170 Some(context),
171 );
172 }
173 convert_value_to_column_type(value, ty)
174}
175
176fn convert_catalog_array(
177 context: &dyn AssignmentContext,
178 value: Value,
179 element: &ColumnType,
180) -> Result<Value, SQLError> {
181 let array = match value {
182 Value::Array(array) => array,
183 Value::Str(text) => crate::expr::parse_pg_array_literal(&text)?,
184 other => {
185 return Err(SQLError::TypeMismatch(format!(
186 "expected an array, got {other:?}"
187 )))
188 }
189 };
190 let values = convert_catalog_array_elements(context, array.elements(), element)?;
191 ArrayValue::with_lower_bounds(values, array.lower_bounds().to_vec())
192 .map(Value::Array)
193 .ok_or_else(|| {
194 SQLError::TypeMismatch("multidimensional arrays must have matching dimensions".into())
195 })
196}
197
198fn convert_catalog_array_elements(
199 context: &dyn AssignmentContext,
200 values: &[Value],
201 element: &ColumnType,
202) -> Result<Vec<Value>, SQLError> {
203 let mut element = element;
204 while let ColumnType::Array(nested) = element {
205 element = nested;
206 }
207 values
208 .iter()
209 .map(|value| match value {
210 Value::List(values) => {
211 convert_catalog_array_elements(context, values, element).map(Value::List)
212 }
213 value => convert_value_to_column_type_with_context(context, value.clone(), element),
214 })
215 .collect()
216}
217
218mod production;
219pub use production::convert_value_to_column_type_with_control;
220
221pub fn convert_value_to_column_type(value: Value, ty: &ColumnType) -> Result<Value, SQLError> {
222 let control = uqa_core::memory::ProductionControl::uncontrolled();
223 convert_value_to_column_type_with_control(control.finish(value, None)?, ty, &control)?
224 .into_uncontrolled()
225 .map_err(|_| SQLError::Internal("ordinary assignment production owner".into()))
226}
227
228fn convert_declared_array_elements(
229 context: &dyn AssignmentContext,
230 elements: &[Value],
231 source_type: &ColumnType,
232 target_type: &ColumnType,
233) -> Result<Vec<Value>, SQLError> {
234 elements
235 .iter()
236 .cloned()
237 .map(|element| match element {
238 Value::List(nested) => {
239 convert_declared_array_elements(context, &nested, source_type, target_type)
240 .map(Value::List)
241 }
242 scalar => {
243 convert_declared_value_to_column_type(context, scalar, source_type, target_type)
244 }
245 })
246 .collect()
247}
248
249fn array_scalar_type(mut ty: &ColumnType) -> &ColumnType {
250 while let ColumnType::Array(element) = ty {
251 ty = element;
252 }
253 ty
254}
255
256pub fn validate_vector_dimensions(expected: u32, actual: usize) -> Result<(), SQLError> {
257 let expected = usize::try_from(expected).map_err(|_| {
258 SQLError::TypeMismatch(format!(
259 "declared vector dimension {expected} exceeds the platform usize range"
260 ))
261 })?;
262 if actual == expected {
263 Ok(())
264 } else {
265 Err(SQLError::VectorDimMismatch { expected, actual })
266 }
267}
268
269pub use crate::catalog::type_metadata::column_type_name;
270
271pub use crate::expr::value_to_text;
272
273pub fn json_to_core_value(json: serde_json::Value) -> Value {
274 match json {
275 serde_json::Value::Null => Value::Null,
276 serde_json::Value::Bool(b) => Value::Bool(b),
277 serde_json::Value::Number(n) => {
278 if let Some(i) = n.as_i64() {
279 Value::Int(i)
280 } else if let Some(d) = DecimalValue::parse(&n.to_string()) {
281 Value::Decimal(d)
282 } else if let Some(f) = n.as_f64() {
283 Value::Float(f)
284 } else {
285 Value::Null
286 }
287 }
288 serde_json::Value::String(s) => Value::Str(s),
289 serde_json::Value::Array(items) => {
290 Value::List(items.into_iter().map(json_to_core_value).collect())
291 }
292 serde_json::Value::Object(obj) => {
293 if let Ok(temporal) =
294 serde_json::from_value::<TemporalValue>(serde_json::Value::Object(obj.clone()))
295 {
296 return Value::Temporal(temporal);
297 }
298 Value::Map(
299 obj.into_iter()
300 .map(|(k, v)| (k, json_to_core_value(v)))
301 .collect(),
302 )
303 }
304 }
305}
306
307pub use crate::expr::core_value_to_json;
308
309pub fn json_table_value_to_text(value: &serde_json::Value) -> Value {
310 match value {
311 serde_json::Value::Null => Value::Null,
312 serde_json::Value::String(s) => Value::Str(s.clone()),
313 serde_json::Value::Bool(b) => Value::Str(b.to_string()),
314 serde_json::Value::Number(n) => Value::Str(n.to_string()),
315 serde_json::Value::Array(_) | serde_json::Value::Object(_) => Value::Str(value.to_string()),
316 }
317}
318
319pub fn json_table_arg(value: &Value, name: &str) -> Result<serde_json::Value, SQLError> {
320 match value {
321 Value::Json(s) | Value::JsonB(s) | Value::Str(s) => {
322 serde_json::from_str::<serde_json::Value>(s)
323 .map_err(|e| SQLError::TypeMismatch(format!("{name}: invalid JSON: {e}")))
324 }
325 other => Ok(core_value_to_json(other)),
326 }
327}