1use std::sync::Arc;
14
15use uqa_core::memory::{Produced, ProductionControl, ProductionVec};
16use uqa_core::{ArrayValue, EnumLabelKey, EnumValue, Value};
17
18use super::{Result, SQLError};
19use crate::ast::ColumnType;
20
21mod functions;
22mod physical;
23pub use functions::{
24 enum_function_value, enum_function_value_with_control, enum_function_value_with_state,
25};
26pub(in crate::expr) use physical::comparison_order;
27pub use physical::{
28 comparison_identity, eval_comparison, EnumComparisonState, EnumTypeComparisonStates,
29};
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct EnumTypeLabel {
34 pub oid: u32,
35 pub key: EnumLabelKey,
36 pub label: String,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct EnumTypeLabels {
42 pub type_oid: u32,
43 pub labels: Vec<EnumTypeLabel>,
44}
45
46impl EnumTypeLabels {
47 pub fn by_key(&self, key: &EnumLabelKey) -> Option<&EnumTypeLabel> {
48 self.labels
49 .binary_search_by(|label| label.key.cmp(key))
50 .ok()
51 .map(|index| &self.labels[index])
52 }
53
54 pub fn by_text(&self, text: &str) -> Option<&EnumTypeLabel> {
55 self.labels.iter().find(|label| label.label == text)
56 }
57
58 fn value(&self, label: &EnumTypeLabel) -> Value {
59 Value::Enum(
60 EnumValue::new(self.type_oid, label.key.clone()).with_label_oid(Some(label.oid)),
61 )
62 }
63
64 fn value_with_control(
65 &self,
66 label: &EnumTypeLabel,
67 control: &ProductionControl<'_>,
68 ) -> Result<Produced<Value>> {
69 let memory = control.reserve(label.key.as_bytes().len())?;
70 Ok(control.finish(self.value(label), memory)?)
71 }
72}
73
74pub use super::SQLValueCatalog as EnumLabelCatalog;
76
77pub(crate) fn enum_value_from_oid(
78 catalog: Option<&dyn EnumLabelCatalog>,
79 label_oid: u32,
80) -> Result<Value> {
81 catalog
82 .map(|catalog| catalog.enum_value_by_oid(label_oid))
83 .transpose()?
84 .flatten()
85 .map(Value::Enum)
86 .ok_or_else(|| SQLError::Routine {
87 sqlstate: "22P03".into(),
88 message: format!("invalid internal value for enum: {label_oid}"),
89 })
90}
91
92fn catalog_unavailable(type_oid: u32) -> SQLError {
93 SQLError::Internal(format!(
94 "enum type OID {type_oid} is not available in the statement catalog"
95 ))
96}
97
98pub(crate) fn labels(
99 catalog: Option<&dyn EnumLabelCatalog>,
100 type_oid: u32,
101) -> Result<Arc<EnumTypeLabels>> {
102 catalog
103 .ok_or_else(|| catalog_unavailable(type_oid))?
104 .enum_type_labels(type_oid)?
105 .ok_or_else(|| catalog_unavailable(type_oid))
106}
107
108pub(crate) fn type_name(catalog: Option<&dyn EnumLabelCatalog>, type_oid: u32) -> Result<String> {
109 catalog
110 .ok_or_else(|| catalog_unavailable(type_oid))?
111 .enum_type_name(type_oid)?
112 .ok_or_else(|| catalog_unavailable(type_oid))
113}
114
115fn check_safe(
117 catalog: Option<&dyn EnumLabelCatalog>,
118 labels: &EnumTypeLabels,
119 label: &EnumTypeLabel,
120) -> Result<()> {
121 if catalog.is_some_and(|catalog| catalog.enum_label_uncommitted(label.oid)) {
122 return Err(SQLError::Diagnostic {
123 sqlstate: "55P04".into(),
124 message: format!(
125 "unsafe use of new value \"{}\" of enum type {}",
126 label.label,
127 type_name(catalog, labels.type_oid)?
128 ),
129 detail: None,
130 hint: Some("New enum values must be committed before they can be used.".into()),
131 });
132 }
133 Ok(())
134}
135
136fn invalid_internal_value(value: &EnumValue) -> SQLError {
137 SQLError::Internal(format!(
138 "invalid internal value for enum type OID {}",
139 value.type_oid()
140 ))
141}
142
143pub(crate) fn enum_label(
145 catalog: Option<&dyn EnumLabelCatalog>,
146 value: &EnumValue,
147) -> Result<EnumTypeLabel> {
148 labels(catalog, value.type_oid())?
149 .by_key(value.key())
150 .cloned()
151 .ok_or_else(|| invalid_internal_value(value))
152}
153
154pub fn enum_label_text(
156 catalog: Option<&dyn EnumLabelCatalog>,
157 value: &EnumValue,
158) -> Result<String> {
159 enum_label(catalog, value).map(|label| label.label)
160}
161
162pub fn enum_label_oid(catalog: Option<&dyn EnumLabelCatalog>, value: &EnumValue) -> Result<u32> {
164 enum_label(catalog, value).map(|label| label.oid)
165}
166
167pub fn enum_value_from_text(
169 catalog: Option<&dyn EnumLabelCatalog>,
170 type_oid: u32,
171 text: &str,
172) -> Result<Value> {
173 let labels = labels(catalog, type_oid)?;
174 let Some(label) = labels.by_text(text) else {
175 return Err(SQLError::Routine {
176 sqlstate: "22P02".into(),
177 message: format!(
178 "invalid input value for enum {}: \"{text}\"",
179 type_name(catalog, type_oid)?
180 ),
181 });
182 };
183 check_safe(catalog, &labels, label)?;
184 Ok(labels.value(label))
185}
186
187pub fn enum_endpoint(
189 catalog: Option<&dyn EnumLabelCatalog>,
190 type_oid: u32,
191 last: bool,
192) -> Result<Value> {
193 enum_endpoint_with_control(catalog, type_oid, last, &ProductionControl::uncontrolled())
194 .map(|value| value.into_uncontrolled().expect("ordinary enum endpoint"))
195}
196
197fn enum_endpoint_with_control(
198 catalog: Option<&dyn EnumLabelCatalog>,
199 type_oid: u32,
200 last: bool,
201 control: &ProductionControl<'_>,
202) -> Result<Produced<Value>> {
203 control.check()?;
204 let labels = labels(catalog, type_oid)?;
205 let label = if last {
206 labels.labels.last()
207 } else {
208 labels.labels.first()
209 };
210 let Some(label) = label else {
211 return Err(SQLError::Routine {
212 sqlstate: "55000".into(),
213 message: format!("enum {} contains no values", type_name(catalog, type_oid)?),
214 });
215 };
216 check_safe(catalog, &labels, label)?;
217 labels.value_with_control(label, control)
218}
219
220pub fn enum_range(
222 catalog: Option<&dyn EnumLabelCatalog>,
223 type_oid: u32,
224 lower: Option<&EnumValue>,
225 upper: Option<&EnumValue>,
226) -> Result<Value> {
227 let lower = lower
228 .map(|value| enum_label_oid(catalog, value))
229 .transpose()?;
230 let upper = upper
231 .map(|value| enum_label_oid(catalog, value))
232 .transpose()?;
233 range_by_oid(catalog, type_oid, lower, upper)
234}
235
236fn range_by_oid(
237 catalog: Option<&dyn EnumLabelCatalog>,
238 type_oid: u32,
239 lower: Option<u32>,
240 upper: Option<u32>,
241) -> Result<Value> {
242 range_by_oid_with_control(
243 catalog,
244 type_oid,
245 lower,
246 upper,
247 &ProductionControl::uncontrolled(),
248 )
249 .map(|value| value.into_uncontrolled().expect("ordinary enum range"))
250}
251
252fn range_by_oid_with_control(
253 catalog: Option<&dyn EnumLabelCatalog>,
254 type_oid: u32,
255 lower: Option<u32>,
256 upper: Option<u32>,
257 control: &ProductionControl<'_>,
258) -> Result<Produced<Value>> {
259 control.check()?;
260 let labels = labels(catalog, type_oid)?;
261 let mut elements = ProductionVec::new(*control);
262 let mut include = lower.is_none_or(|oid| oid == 0);
263 for label in &labels.labels {
264 control.check()?;
265 if lower == Some(label.oid) {
266 include = true;
267 }
268 if include {
269 check_safe(catalog, &labels, label)?;
270 elements.push_produced(labels.value_with_control(label, control)?)?;
271 }
272 if upper == Some(label.oid) {
273 break;
274 }
275 }
276 let array = ArrayValue::try_new_with_control(elements.finish()?, control)?
277 .ok_or_else(|| SQLError::Internal("enum range array has invalid dimensions".into()))?;
278 let (array, memory) = array.into_parts();
279 Ok(control.finish(
280 Value::Array(array.with_element_type_oid(Some(type_oid))),
281 memory,
282 )?)
283}
284
285pub fn render_enum_labels(catalog: Option<&dyn EnumLabelCatalog>, value: &Value) -> Result<Value> {
287 map_enum_values(value, &mut |label| {
288 enum_label_text(catalog, label).map(Value::Str)
289 })
290}
291
292pub(crate) fn retain_enum_oids(
294 catalog: Option<&dyn EnumLabelCatalog>,
295 value: &Value,
296) -> Result<Value> {
297 map_enum_values(value, &mut |label| {
298 let oid = match label.label_oid() {
299 Some(oid) => oid,
300 None => enum_label_oid(catalog, label)?,
301 };
302 Ok(Value::Enum(label.clone().with_label_oid(Some(oid))))
303 })
304}
305
306fn map_enum_values(
307 value: &Value,
308 convert: &mut dyn FnMut(&EnumValue) -> Result<Value>,
309) -> Result<Value> {
310 Ok(match value {
311 Value::Enum(label) => convert(label)?,
312 Value::Array(array) => Value::Array(map_array(array, |element| {
313 map_enum_values(element, convert)
314 })?),
315 Value::List(values) => Value::List(
316 values
317 .iter()
318 .map(|element| map_enum_values(element, convert))
319 .collect::<Result<_>>()?,
320 ),
321 Value::Row(values) => Value::Row(
322 values.clone().with_values(
323 values
324 .iter()
325 .map(|element| map_enum_values(element, convert))
326 .collect::<Result<_>>()?,
327 )?,
328 ),
329 Value::Record(fields) => Value::Record(uqa_core::RecordValue::from_parts(
330 fields
331 .iter()
332 .map(|(name, element)| Ok((name.clone(), map_enum_values(element, convert)?)))
333 .collect::<Result<_>>()?,
334 fields.type_oid(),
335 )),
336 Value::Map(fields) => Value::Map(
337 fields
338 .iter()
339 .map(|(name, element)| Ok((name.clone(), map_enum_values(element, convert)?)))
340 .collect::<Result<_>>()?,
341 ),
342 other => other.clone(),
343 })
344}
345
346fn map_array(
348 array: &ArrayValue,
349 mut convert: impl FnMut(&Value) -> Result<Value>,
350) -> Result<ArrayValue> {
351 fn leaves(
352 values: &[Value],
353 convert: &mut dyn FnMut(&Value) -> Result<Value>,
354 ) -> Result<Vec<Value>> {
355 values
356 .iter()
357 .map(|value| match value {
358 Value::List(values) => leaves(values, convert).map(Value::List),
359 value => convert(value),
360 })
361 .collect()
362 }
363 let elements = leaves(array.elements(), &mut convert)?;
364 ArrayValue::with_lower_bounds(elements, array.lower_bounds().to_vec())
365 .filter(|converted| converted.dimensions() == array.dimensions())
366 .map(|converted| converted.with_element_type_oid(array.element_type_oid()))
367 .ok_or_else(|| SQLError::Internal("enum array conversion changed array dimensions".into()))
368}
369
370pub fn contains_enum_carrier(value: &Value) -> bool {
372 enum_carrier_matches(value, &|_| true)
373}
374
375pub(crate) fn has_missing_enum_oid(value: &Value) -> bool {
376 enum_carrier_matches(value, &|label| label.label_oid().is_none())
377}
378
379fn enum_carrier_matches(value: &Value, predicate: &dyn Fn(&EnumValue) -> bool) -> bool {
380 match value {
381 Value::Enum(label) => predicate(label),
382 Value::Array(array) => array
383 .elements()
384 .iter()
385 .any(|value| enum_carrier_matches(value, predicate)),
386 Value::List(values) => values
387 .iter()
388 .any(|value| enum_carrier_matches(value, predicate)),
389 Value::Row(values) => values
390 .iter()
391 .any(|value| enum_carrier_matches(value, predicate)),
392 Value::Record(fields) => fields
393 .iter()
394 .any(|(_, value)| enum_carrier_matches(value, predicate)),
395 Value::Map(fields) => fields
396 .values()
397 .any(|value| enum_carrier_matches(value, predicate)),
398 _ => false,
399 }
400}
401
402#[must_use]
404pub fn call_applies_output_functions(name: &str, arguments: &[(Option<String>, Value)]) -> bool {
405 match name {
406 "concat_op" => !arguments
407 .iter()
408 .any(|(_, value)| matches!(value, Value::Array(_) | Value::List(_))),
409 "concat" | "concat_ws" | "format" | "quote_literal" | "quote_nullable" | "to_json"
410 | "to_jsonb" | "row_to_json" | "array_to_json" | "json_build_object"
411 | "json_build_array" | "jsonb_build_object" | "jsonb_build_array" | "array_to_string" => {
412 true
413 }
414 _ => false,
415 }
416}
417
418pub fn render_call_arguments(
420 catalog: Option<&dyn EnumLabelCatalog>,
421 arguments: Vec<(Option<String>, Value)>,
422) -> Result<Vec<(Option<String>, Value)>> {
423 arguments
424 .into_iter()
425 .map(|(name, value)| {
426 if contains_enum_carrier(&value) {
427 Ok((name, render_enum_labels(catalog, &value)?))
428 } else {
429 Ok((name, value))
430 }
431 })
432 .collect()
433}
434
435pub fn render_host_arguments(
437 catalog: Option<&dyn EnumLabelCatalog>,
438 arguments: &[Value],
439) -> Result<Vec<Value>> {
440 arguments
441 .iter()
442 .map(|value| {
443 if contains_enum_carrier(value) {
444 render_enum_labels(catalog, value)
445 } else {
446 Ok(value.clone())
447 }
448 })
449 .collect()
450}
451
452#[must_use]
454pub fn is_enum_bearing(ty: &ColumnType) -> bool {
455 match ty {
456 ColumnType::Enum(_) => true,
457 ColumnType::Array(element) => is_enum_bearing(element),
458 _ => false,
459 }
460}
461
462pub fn fold_unknown_literal(
464 catalog: Option<&dyn EnumLabelCatalog>,
465 value: &Value,
466 target: &ColumnType,
467) -> Result<Option<Value>> {
468 match (target, value) {
469 (ColumnType::Enum(_) | ColumnType::Array(_), Value::Null) if is_enum_bearing(target) => {
470 Ok(Some(Value::Null))
471 }
472 (ColumnType::Enum(reference), Value::Str(text)) => {
473 enum_value_from_text(catalog, reference.oid, text).map(Some)
474 }
475 (ColumnType::Array(element), Value::Str(text)) if is_enum_bearing(element) => {
476 let mut leaf = element.as_ref();
477 while let ColumnType::Array(inner) = leaf {
478 leaf = inner;
479 }
480 let ColumnType::Enum(reference) = leaf else {
481 return Ok(None);
482 };
483 let parsed = super::casting::parse_pg_array_literal(text)?;
484 map_array(&parsed, |element| match element {
485 Value::Null => Ok(Value::Null),
486 Value::Str(text) => enum_value_from_text(catalog, reference.oid, text),
487 other => Err(SQLError::Internal(format!(
488 "array literal parsing produced a non-text element {other:?}"
489 ))),
490 })
491 .map(|array| {
492 Some(Value::Array(
493 array.with_element_type_oid(Some(reference.oid)),
494 ))
495 })
496 }
497 _ => Ok(None),
498 }
499}
500
501fn string_category(ty: &ColumnType) -> bool {
503 match ty {
504 ColumnType::Text
505 | ColumnType::Varchar(_)
506 | ColumnType::Bpchar
507 | ColumnType::Character(_)
508 | ColumnType::Name => true,
509 ColumnType::Domain { base, .. } => string_category(base),
510 _ => false,
511 }
512}
513
514fn cannot_cast(source: &str, target: &str) -> SQLError {
515 SQLError::Routine {
516 sqlstate: "42846".into(),
517 message: format!("cannot cast type {source} to {target}"),
518 }
519}
520
521pub fn cast_to_enum(
523 catalog: Option<&dyn EnumLabelCatalog>,
524 value: &Value,
525 source: Option<&ColumnType>,
526 target: &ColumnType,
527) -> Result<Option<Value>> {
528 let ColumnType::Enum(reference) = target else {
529 return Ok(None);
530 };
531 let source_name = |source: Option<&ColumnType>| -> Result<String> {
532 Ok(match (value, source) {
533 (Value::Enum(label), _) => type_name(catalog, label.type_oid())?,
534 (_, Some(ColumnType::Enum(source))) => type_name(catalog, source.oid)?,
535 (_, Some(source)) => source.sql_name(),
536 (_, None) => "unknown".into(),
537 })
538 };
539 match value {
540 Value::Null => Ok(Some(Value::Null)),
541 Value::Enum(label) if label.type_oid() == reference.oid => Ok(Some(value.clone())),
542 Value::Datum(datum) if datum.type_oid() == reference.oid => Ok(Some(value.clone())),
543 Value::Str(text) | Value::FixedChar(text) if source.is_none_or(string_category) => {
544 enum_value_from_text(catalog, reference.oid, text).map(Some)
545 }
546 _ => Err(cannot_cast(
547 &source_name(source)?,
548 &type_name(catalog, reference.oid)?,
549 )),
550 }
551}
552
553pub fn enum_output_for_cast(
555 catalog: Option<&dyn EnumLabelCatalog>,
556 label: &EnumValue,
557 target: &ColumnType,
558) -> Result<Value> {
559 if string_category(target) {
560 return enum_label_text(catalog, label).map(Value::Str);
561 }
562 Err(cannot_cast(
563 &type_name(catalog, label.type_oid())?,
564 &match target {
565 ColumnType::Enum(reference) => type_name(catalog, reference.oid)?,
566 other => other.sql_name(),
567 },
568 ))
569}