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