1use super::{
10 array_scalar_type, column_type_name, validate_vector_dimensions, ColumnType, SQLError,
11};
12use crate::expr::{
13 cast_value_from_with_control, parse_pg_array_literal_with_control,
14 value_to_tensor_with_control, value_to_text_with_control, value_to_vector_with_control,
15};
16use uqa_core::{
17 memory::{MemoryReservation, Produced, ProductionControl, ProductionString, ProductionVec},
18 ArrayValue, DecimalValue, Value,
19};
20
21type Result<T> = std::result::Result<T, SQLError>;
22
23pub fn convert_value_to_column_type_from_with_control(
25 value: Produced<Value>,
26 ty: &ColumnType,
27 source: Option<&ColumnType>,
28 control: &ProductionControl<'_>,
29) -> Result<Produced<Value>> {
30 control.check()?;
31 let (value, memory) = value.into_parts();
32 let value = control.finish(value, memory)?;
33 if source.is_some_and(|source| super::same_catalog_value_type(source, ty)) {
34 return super::normalize_legacy_vector_carrier_with_control(&value, ty, control)
35 .map(|normalized| normalized.unwrap_or(value));
36 }
37 if let (ColumnType::InternalChar, Some(source)) = (ty, source) {
38 let name = source.catalog_name_with_control(control)?;
39 return cast_value_from_with_control(&value, "\"char\"", Some(&name), control);
40 }
41 convert_value_to_column_type_with_control(value, ty, control)
42}
43
44#[expect(
45 clippy::too_many_lines,
46 reason = "assignment matrix preserves conversion and validation order"
47)]
48pub fn convert_value_to_column_type_with_control(
49 value: Produced<Value>,
50 ty: &ColumnType,
51 control: &ProductionControl<'_>,
52) -> Result<Produced<Value>> {
53 let (value, memory) = value.into_parts();
54 let value = control.finish(value, memory)?;
55 if matches!(&*value, Value::Null) {
56 return Ok(value);
57 }
58 match ty {
59 ColumnType::Named(name) => Err(SQLError::Routine {
60 sqlstate: "42704".into(),
61 message: format!("type \"{name}\" does not exist"),
62 }),
63 ColumnType::Enum(reference) => match &*value {
65 Value::Enum(label) if label.type_oid() == reference.oid => Ok(value),
66 _ => Err(SQLError::Internal(format!(
67 "enum input for type OID {} requires catalog-aware conversion",
68 reference.oid
69 ))),
70 },
71 ColumnType::Composite(reference) => match &*value {
73 Value::Record(_) => Ok(value),
74 _ => Err(SQLError::Internal(format!(
75 "composite input for type OID {} requires catalog-aware conversion",
76 reference.oid
77 ))),
78 },
79 ColumnType::SmallInteger => cast_value_from_with_control(&value, "smallint", None, control),
80 ColumnType::Integer => cast_value_from_with_control(&value, "integer", None, control),
81 ColumnType::BigInteger => cast_value_from_with_control(&value, "bigint", None, control),
82 ColumnType::Oid => cast_value_from_with_control(&value, "oid", None, control),
84 ColumnType::Xid => {
85 let converted = cast_value_from_with_control(&value, "bigint", None, control)?;
86 let Value::Int(number) = &*converted else {
87 unreachable!("bigint cast returned a non-integer value");
88 };
89 u32::try_from(*number).map_err(|_| {
90 SQLError::TypeMismatch(format!(
91 "value {number} is out of range for type {}",
92 column_type_name(ty)
93 ))
94 })?;
95 Ok(converted)
96 }
97 ColumnType::Boolean => match &*value {
98 Value::Bool(_) => Ok(value),
99 Value::Str(text) => {
100 let boolean = parse_boolean_text(text)
101 .ok_or_else(|| crate::expr::invalid_boolean_input(text))?;
102 Ok(control.finish(Value::Bool(boolean), control.empty_reservation())?)
103 }
104 other => Err(SQLError::TypeMismatch(format!(
105 "cannot cast {other:?} to boolean"
106 ))),
107 },
108 ColumnType::Void => Ok(control.finish(Value::Void, control.empty_reservation())?),
109 ColumnType::Text | ColumnType::RefCursor | ColumnType::Varchar(None) => {
110 text_value(value_to_text_with_control(&value, control)?, false, control)
111 }
112 ColumnType::Name => cast_value_from_with_control(&value, "name", None, control),
113 ColumnType::Uuid => cast_value_from_with_control(&value, "uuid", None, control),
114 ColumnType::Varchar(Some(length)) => character_value(&value, *length, false, control),
115 ColumnType::Bpchar => {
116 text_value(value_to_text_with_control(&value, control)?, true, control)
117 }
118 ColumnType::Character(length) => character_value(&value, *length, true, control),
119 ColumnType::Real => cast_value_from_with_control(&value, "real", None, control),
120 ColumnType::DoublePrecision => {
121 cast_value_from_with_control(&value, "double precision", None, control)
122 }
123 ColumnType::Numeric { precision, scale } => {
124 numeric_value(value, *precision, *scale, control)
125 }
126 ColumnType::Json => cast_value_from_with_control(&value, "json", None, control),
127 ColumnType::JsonB => cast_value_from_with_control(&value, "jsonb", None, control),
128 ColumnType::Bytea => match &*value {
130 Value::Bytes(_) => Ok(value),
131 Value::Str(_) | Value::FixedChar(_) => {
132 cast_value_from_with_control(&value, "bytea", Some("text"), control)
133 }
134 other => Err(SQLError::TypeMismatch(format!(
135 "cannot cast {other:?} to bytea"
136 ))),
137 },
138 ColumnType::InternalChar => {
139 let text = value_to_text_with_control(&value, control)?;
140 Ok(crate::expr::internal_char::carrier_with_control(
141 crate::expr::internal_char::input_byte(&text),
142 control,
143 )?)
144 }
145 ColumnType::Regproc
146 | ColumnType::Regprocedure
147 | ColumnType::Regclass
148 | ColumnType::Regcollation
149 | ColumnType::Regnamespace
150 | ColumnType::Regtype
151 | ColumnType::AclItem => {
152 if matches!(&*value, Value::Int(_) | Value::Str(_)) {
153 Ok(value)
154 } else {
155 text_value(value_to_text_with_control(&value, control)?, false, control)
156 }
157 }
158 ColumnType::PgNodeTree => {
159 cast_value_from_with_control(&value, "pg_node_tree", None, control)
160 }
161 ColumnType::Regrole => match &*value {
162 Value::Int(number) => {
163 u32::try_from(*number).map_err(|_| {
164 SQLError::TypeMismatch(format!(
165 "value {number} is out of range for type regrole"
166 ))
167 })?;
168 Ok(value)
169 }
170 Value::Str(_) | Value::FixedChar(_) => Err(SQLError::Internal(
171 "regrole name conversion requires catalog resolution".into(),
172 )),
173 other => Err(SQLError::TypeMismatch(format!(
174 "cannot cast {other:?} to regrole"
175 ))),
176 },
177 ColumnType::Int2Vector | ColumnType::OidVector => {
178 let name = column_type_name(ty);
179 if matches!(&*value, Value::LegacyVector(vector) if vector.kind().type_name() == name) {
180 Ok(value)
181 } else {
182 cast_value_from_with_control(&value, name, None, control)
183 }
184 }
185 ColumnType::AnyArray => {
186 if value.array_view().is_some() {
187 Ok(value)
188 } else {
189 Err(SQLError::TypeMismatch(format!(
190 "cannot cast {:?} to anyarray",
191 *value
192 )))
193 }
194 }
195 ColumnType::Record => match &*value {
196 Value::Record(_) => Ok(value),
197 Value::Row(_) => record_value(value, control),
198 other => Err(SQLError::TypeMismatch(format!(
199 "cannot cast {other:?} to record"
200 ))),
201 },
202 ColumnType::Array(element) => array_value(value, element, control),
203 ColumnType::Date
204 | ColumnType::Time
205 | ColumnType::TimePrecision(_)
206 | ColumnType::TimeTz
207 | ColumnType::TimeTzPrecision(_)
208 | ColumnType::Timestamp
209 | ColumnType::TimestampPrecision(_)
210 | ColumnType::TimestampTz
211 | ColumnType::TimestampTzPrecision(_)
212 | ColumnType::Interval
213 | ColumnType::IntervalWithFields { .. } => {
214 let name = ty.sql_name_with_control(control)?;
215 cast_value_from_with_control(&value, &name, None, control)
216 }
217 ColumnType::Range(subtype) => {
218 cast_value_from_with_control(&value, subtype.range_name(), None, control)
219 }
220 ColumnType::Multirange(subtype) => {
221 cast_value_from_with_control(&value, subtype.multirange_name(), None, control)
222 }
223 ColumnType::Vector(dimensions) => {
224 let vector = value_to_vector_with_control(&value, control)?;
225 validate_vector_dimensions(*dimensions, vector.len())?;
226 vector_value(&vector, control)
227 }
228 ColumnType::Tensor(dimensions) => {
229 let tensor = value_to_tensor_with_control(&value, control)?;
230 for vector in &*tensor {
231 control.check()?;
232 validate_vector_dimensions(*dimensions, vector.len())?;
233 }
234 let mut output = ProductionVec::new(*control);
235 output.reserve(tensor.len())?;
236 for vector in &*tensor {
237 output.push_produced(vector_value(vector, control)?)?;
238 }
239 let (values, memory) = output.finish()?.into_parts();
240 Ok(control.finish(Value::List(values), memory)?)
241 }
242 ColumnType::Domain { base, .. } => {
243 convert_value_to_column_type_with_control(value, base, control)
244 }
245 }
246}
247
248fn numeric_value(
249 value: Produced<Value>,
250 precision: Option<u32>,
251 scale: Option<i32>,
252 control: &ProductionControl<'_>,
253) -> Result<Produced<Value>> {
254 let decimal = match &*value {
255 Value::Decimal(_) => {
256 let (Value::Decimal(value), memory) = value.into_parts() else {
257 unreachable!();
258 };
259 control.finish(value, memory)?
260 }
261 Value::Int(number) => DecimalValue::from_i64_with_control(*number, control)?,
262 Value::Bool(boolean) => DecimalValue::from_i64_with_control(i64::from(*boolean), control)?,
263 Value::Float(number) => DecimalValue::from_f64_lossy_with_control(*number, control)?
264 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot cast {number:?} to numeric")))?,
265 Value::Str(text) => DecimalValue::parse_with_control(text, control)?
266 .ok_or_else(|| crate::expr::invalid_numeric_input(text))?,
267 other => {
268 return Err(SQLError::TypeMismatch(format!(
269 "cannot cast {other:?} to numeric"
270 )))
271 }
272 };
273 let rounded = match scale {
274 Some(scale) => decimal
275 .round_to_scale_with_control(scale, control)?
276 .ok_or_else(|| {
277 SQLError::TypeMismatch(format!("cannot round numeric to scale {scale}"))
278 })?,
279 None => decimal,
280 };
281 if let Some(precision) = precision {
282 let scale = scale.unwrap_or(0);
283 if !rounded.fits_precision_with_control(precision, scale, control)? {
284 return Err(numeric_field_overflow(precision, scale));
285 }
286 }
287 let (decimal, memory) = rounded.into_parts();
288 Ok(control.finish(Value::Decimal(decimal), memory)?)
289}
290
291fn text_value(
292 text: Produced<String>,
293 fixed: bool,
294 control: &ProductionControl<'_>,
295) -> Result<Produced<Value>> {
296 let (text, memory) = text.into_parts();
297 Ok(control.finish(
298 if fixed {
299 Value::FixedChar(text)
300 } else {
301 Value::Str(text)
302 },
303 memory,
304 )?)
305}
306
307fn character_value(
308 value: &Value,
309 length: u32,
310 fixed: bool,
311 control: &ProductionControl<'_>,
312) -> Result<Produced<Value>> {
313 let name = if fixed {
314 "character"
315 } else {
316 "character varying"
317 };
318 let length = usize::try_from(length).map_err(|_| {
319 SQLError::TypeMismatch(format!(
320 "{name} length {length} exceeds the platform addressable range"
321 ))
322 })?;
323 let text = value_to_text_with_control(value, control)?;
324 let mut count = 0;
325 let mut end = 0;
326 for (index, character) in text.char_indices() {
327 control.check()?;
328 if count < length {
329 count += 1;
330 end = index + character.len_utf8();
331 } else if character != ' ' {
332 return Err(SQLError::Routine {
333 sqlstate: "22001".into(),
334 message: format!("value too long for type {name}({length})"),
335 });
336 }
337 }
338 let mut output = ProductionString::from_produced(text, *control)?;
339 output.truncate(end)?;
340 if fixed {
341 for _ in count..length {
342 output.push(' ')?;
343 }
344 }
345 text_value(output.finish()?, fixed, control)
346}
347
348fn array_value(
349 value: Produced<Value>,
350 element: &ColumnType,
351 control: &ProductionControl<'_>,
352) -> Result<Produced<Value>> {
353 let array = match &*value {
354 Value::Array(_) => {
355 let (Value::Array(array), memory) = value.into_parts() else {
356 unreachable!();
357 };
358 control.finish(array, memory)?
359 }
360 Value::LegacyVector(_) => {
361 let (Value::LegacyVector(vector), memory) = value.into_parts() else {
362 unreachable!();
363 };
364 control.finish(vector.into_array(), memory)?
365 }
366 Value::List(_) => {
367 let (Value::List(elements), memory) = value.into_parts() else {
368 unreachable!();
369 };
370 ArrayValue::try_new_with_control(control.finish(elements, memory)?, control)?
371 .ok_or_else(array_shape_error)?
372 }
373 Value::Str(text) => parse_pg_array_literal_with_control(text, control)?,
374 other => {
375 return Err(SQLError::TypeMismatch(format!(
376 "cannot cast {other:?} to {}[]",
377 column_type_name(element)
378 )))
379 }
380 };
381 let converted = array_elements(array.elements(), array_scalar_type(element), control)?;
382 let mut bounds = ProductionVec::new(*control);
383 bounds.reserve(array.lower_bounds().len())?;
384 for bound in array.lower_bounds() {
385 bounds.push_copy(*bound)?;
386 }
387 let array = ArrayValue::with_lower_bounds_with_control(converted, bounds.finish()?, control)?
388 .ok_or_else(array_shape_error)?;
389 let (array, memory) = array.into_parts();
390 let oid = crate::catalog::type_metadata::pg_type_oid(array_scalar_type(element)) as u32;
391 Ok(control.finish(Value::Array(array.with_element_type_oid(Some(oid))), memory)?)
392}
393
394fn array_elements(
395 values: &[Value],
396 element: &ColumnType,
397 control: &ProductionControl<'_>,
398) -> Result<Produced<Vec<Value>>> {
399 let mut output = ProductionVec::new(*control);
400 output.reserve(values.len())?;
401 for value in values {
402 let value = match value {
403 Value::List(values) => {
404 let (values, memory) = array_elements(values, element, control)?.into_parts();
405 control.finish(Value::List(values), memory)?
406 }
407 scalar => convert_value_to_column_type_with_control(
408 control.copy_value(scalar)?,
409 element,
410 control,
411 )?,
412 };
413 output.push_produced(value)?;
414 }
415 Ok(output.finish()?)
416}
417
418fn array_shape_error() -> SQLError {
419 SQLError::TypeMismatch("multidimensional arrays must have matching dimensions".into())
420}
421
422fn record_value(
423 value: Produced<Value>,
424 control: &ProductionControl<'_>,
425) -> Result<Produced<Value>> {
426 let Value::Row(source) = &*value else {
427 unreachable!();
428 };
429 if source.field_types().is_some() {
431 return Ok(value);
432 }
433 let mut records = ProductionVec::new(*control);
434 records.reserve(source.len())?;
435 for index in 0..source.len() {
436 let (name, memory) = control.format(format_args!("f{}", index + 1))?.into_parts();
437 records.push_produced(control.finish((name, Value::Null), memory)?)?;
438 }
439 let (records, records_memory) = records.finish()?.into_parts();
440 let (Value::Row(source), source_memory) = value.into_parts() else {
441 unreachable!();
442 };
443 let old_buffer_bytes =
444 source.capacity() * size_of::<Value>() + uqa_core::RowValue::retained_header_bytes();
445 let mut parts = RecordParts {
446 source: source.into_values(),
447 records,
448 memory: control.combine(source_memory, records_memory),
449 };
450 for ((_, destination), source) in parts.records.iter_mut().zip(parts.source) {
451 control.check()?;
452 *destination = source;
453 }
454 if let Some(memory) = &mut parts.memory {
455 drop(memory.split(old_buffer_bytes));
456 }
457 let (record, memory) = uqa_core::RecordValue::with_control(
458 control.finish(parts.records, parts.memory)?,
459 None,
460 control,
461 )?
462 .into_parts();
463 Ok(control.finish(Value::Record(record), memory)?)
464}
465
466struct RecordParts {
467 source: Vec<Value>,
468 records: Vec<(String, Value)>,
469 memory: Option<MemoryReservation>,
470}
471
472fn vector_value(vector: &[f32], control: &ProductionControl<'_>) -> Result<Produced<Value>> {
473 let mut output = ProductionVec::new(*control);
474 output.reserve(vector.len())?;
475 for value in vector {
476 output.push_produced(
477 control.finish(Value::Float(f64::from(*value)), control.empty_reservation())?,
478 )?;
479 }
480 let (values, memory) = output.finish()?.into_parts();
481 Ok(control.finish(Value::List(values), memory)?)
482}
483
484fn parse_boolean_text(text: &str) -> Option<bool> {
485 let text = text.trim();
486 if ["true", "t", "yes", "y", "on", "1"]
487 .iter()
488 .any(|candidate| text.eq_ignore_ascii_case(candidate))
489 {
490 Some(true)
491 } else if ["false", "f", "no", "n", "off", "0"]
492 .iter()
493 .any(|candidate| text.eq_ignore_ascii_case(candidate))
494 {
495 Some(false)
496 } else {
497 None
498 }
499}
500
501#[cfg(test)]
502mod tests;
503
504#[must_use]
506pub fn numeric_field_overflow(precision: u32, scale: i32) -> SQLError {
507 let maxdigits = i64::from(precision) - i64::from(scale);
508 SQLError::Diagnostic {
509 sqlstate: "22003".into(),
510 message: "numeric field overflow".into(),
511 detail: Some(format!(
512 "A field with precision {precision}, scale {scale} must round to an absolute value less than {}.",
513 if maxdigits == 0 {
514 "1".to_string()
515 } else {
516 format!("10^{maxdigits}")
517 }
518 )),
519 hint: None,
520 }
521}