1use std::fmt::Write;
10
11use crate::ast::ColumnType;
12use crate::expr::{format_regtype_value, value_to_string, vector_value_to_string, EngineHook};
13use crate::SQLError;
14use uqa_core::Value;
15
16pub fn format_postgres_text(
18 value: &Value,
19 ty: &ColumnType,
20 engine: Option<&dyn EngineHook>,
21) -> Result<String, SQLError> {
22 if let ColumnType::Domain { base, .. } = ty {
23 return format_postgres_text(value, base, engine);
24 }
25 if let Some(text) = format_regtype_value(value, ty, engine)? {
26 return Ok(text);
27 }
28 if matches!(ty, ColumnType::Int2Vector | ColumnType::OidVector) {
29 return vector_value_to_string(value)?
30 .ok_or_else(|| SQLError::Internal("invalid catalog vector result carrier".into()));
31 }
32 if let ColumnType::Array(element) = ty {
33 return format_array(value, element, engine);
34 }
35 if let (ColumnType::Composite(reference), Value::Record(fields)) = (ty, value) {
36 return format_record(fields, reference.oid, engine);
37 }
38 Ok(match value {
39 Value::Bool(value) => if *value { "t" } else { "f" }.into(),
40 Value::FixedChar(value) => value.clone(),
41 Value::Float(value) if matches!(ty, ColumnType::Real) => {
42 crate::expr::format_real(*value as f32)
43 }
44 Value::Float(value) => uqa_core::format_float_pg(*value),
45 Value::Enum(label) => {
46 crate::expr::enums::enum_label_text(engine.and_then(EngineHook::enum_labels), label)?
47 }
48 _ if crate::expr::enums::contains_enum_carrier(value) => {
50 value_to_string(&crate::expr::enums::render_enum_labels(
51 engine.and_then(EngineHook::enum_labels),
52 value,
53 )?)?
54 }
55 _ => value_to_string(value)?,
56 })
57}
58
59fn format_array(
60 value: &Value,
61 element: &ColumnType,
62 engine: Option<&dyn EngineHook>,
63) -> Result<String, SQLError> {
64 let mut element = element;
67 while let ColumnType::Array(inner) = element {
68 element = inner;
69 }
70 let (values, prefix) = match value {
71 Value::Array(array) => array_parts(array),
72 Value::LegacyVector(vector) => array_parts(vector.as_array()),
73 Value::List(values) => (values.as_slice(), String::new()),
74 _ => return Err(SQLError::Internal("invalid array result carrier".into())),
75 };
76 let mut fields = Vec::with_capacity(values.len());
77 for value in values {
78 fields.push(match value {
79 Value::Null => "NULL".into(),
80 Value::List(_) | Value::Array(_) => format_array(value, element, engine)?,
81 _ => {
82 let text = format_postgres_text(value, element, engine)?;
83 if text.is_empty()
84 || text.eq_ignore_ascii_case("null")
85 || text.chars().any(|c| {
86 c.is_ascii_whitespace() || matches!(c, ',' | '{' | '}' | '"' | '\\')
87 })
88 {
89 format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
90 } else {
91 text
92 }
93 }
94 });
95 }
96 Ok(format!("{prefix}{{{}}}", fields.join(",")))
97}
98
99fn format_record(
101 fields: &[(String, Value)],
102 type_oid: u32,
103 engine: Option<&dyn EngineHook>,
104) -> Result<String, SQLError> {
105 let descriptor = crate::expr::composites::descriptor(
106 engine.and_then(EngineHook::composite_types),
107 type_oid,
108 )?;
109 let mut text = String::from("(");
110 for (index, (name, field)) in fields.iter().enumerate() {
111 if index != 0 {
112 text.push(',');
113 }
114 if matches!(field, Value::Null) {
115 continue;
116 }
117 let rendered = match descriptor.attribute(name) {
118 Some((_, attribute)) => format_postgres_text(field, &attribute.ty, engine)?,
119 None => format_postgres_text(
120 field,
121 &crate::type_resolution::value_type(field).unwrap_or(ColumnType::Text),
122 engine,
123 )?,
124 };
125 let quoted = rendered.is_empty()
126 || rendered.chars().any(|character| {
127 character.is_ascii_whitespace() || matches!(character, ',' | '(' | ')' | '"' | '\\')
128 });
129 if quoted {
130 text.push('"');
131 for character in rendered.chars() {
132 if matches!(character, '"' | '\\') {
133 text.push(character);
134 }
135 text.push(character);
136 }
137 text.push('"');
138 } else {
139 text.push_str(&rendered);
140 }
141 }
142 text.push(')');
143 Ok(text)
144}
145
146fn array_parts(array: &uqa_core::ArrayValue) -> (&[Value], String) {
147 let mut prefix = String::new();
148 if !array.elements().is_empty() && array.lower_bounds().iter().any(|lower| *lower != 1) {
149 for (lower, length) in array.lower_bounds().iter().zip(array.dimensions()) {
150 let upper = i64::from(*lower) + *length as i64 - 1;
151 write!(prefix, "[{lower}:{upper}]").expect("writing to String cannot fail");
152 }
153 prefix.push('=');
154 }
155 (array.elements(), prefix)
156}