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