Skip to main content

uqa_sql/expr/
array_transform.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Shared `PostgreSQL` 18 signatures for array transformations.
8
9use super::{validate_named_argument_order_with_control, Result, Value};
10use uqa_core::memory::{Produced, ProductionControl, ProductionVec};
11
12/// Map call-order arguments onto the declared `array_sort` and `array_reverse` slots. `None` means the arity or a named argument does not select a catalogued overload.
13pub fn argument_positions(
14    name: &str,
15    argument_names: &[Option<&str>],
16) -> Result<Option<Vec<usize>>> {
17    argument_positions_with_control(name, argument_names, &ProductionControl::uncontrolled()).map(
18        |positions| {
19            positions.map(|positions| {
20                positions
21                    .into_uncontrolled()
22                    .expect("ordinary argument positions")
23            })
24        },
25    )
26}
27
28pub fn argument_positions_with_control(
29    name: &str,
30    argument_names: &[Option<&str>],
31    control: &ProductionControl<'_>,
32) -> Result<Option<Produced<Vec<usize>>>> {
33    control.check()?;
34    validate_named_argument_order_with_control(argument_names.iter().copied(), control)?;
35    let function = name
36        .get(..11)
37        .filter(|prefix| prefix.eq_ignore_ascii_case("pg_catalog."))
38        .map_or(name, |_| &name[11..]);
39    let parameter_names: &[Option<&str>] = if argument_names.len() == 1
40        && (function.eq_ignore_ascii_case("array_reverse")
41            || function.eq_ignore_ascii_case("array_sort"))
42    {
43        &[None]
44    } else if function.eq_ignore_ascii_case("array_sort") {
45        match argument_names.len() {
46            2 => &[Some("array"), Some("descending")],
47            3 => &[Some("array"), Some("descending"), Some("nulls_first")],
48            _ => return Ok(None),
49        }
50    } else {
51        return Ok(None);
52    };
53    let mut occupied = [false; 3];
54    let mut positions = ProductionVec::new(*control);
55    positions.reserve(argument_names.len())?;
56    let mut positional = 0;
57    for argument_name in argument_names {
58        let position = if let Some(argument_name) = argument_name {
59            parameter_names
60                .iter()
61                .position(|candidate| *candidate == Some(*argument_name))
62        } else {
63            let position = positional;
64            positional += 1;
65            Some(position)
66        };
67        let Some(position) = position.filter(|position| *position < parameter_names.len()) else {
68            return Ok(None);
69        };
70        if occupied[position] {
71            return Ok(None);
72        }
73        occupied[position] = true;
74        positions.push_copy(position)?;
75    }
76    Ok(occupied[..parameter_names.len()]
77        .iter()
78        .all(|slot| *slot)
79        .then(|| positions.finish())
80        .transpose()?)
81}
82
83pub(super) fn reorder_named_values_with_control(
84    function: &str,
85    call_args: &[(Option<String>, Value)],
86    control: &ProductionControl<'_>,
87) -> Result<Option<Produced<Vec<Value>>>> {
88    let names = super::call_arguments::evaluated_argument_names_with_control(call_args, control)?;
89    let positions = match argument_positions_with_control(function, &names, control) {
90        Ok(Some(positions)) => positions,
91        Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => return Err(error),
92        Ok(None) | Err(_) => return Ok(None),
93    };
94    let mut values = [None; 3];
95    for ((_, value), position) in call_args.iter().zip(positions.iter().copied()) {
96        values[position] = Some(value);
97    }
98    let mut output = ProductionVec::new(*control);
99    output.reserve(call_args.len())?;
100    for value in &values[..call_args.len()] {
101        let Some(value) = value else { return Ok(None) };
102        output.push_produced(control.copy_value(value)?)?;
103    }
104    Ok(Some(output.finish()?))
105}