Skip to main content

uqa_execution/relational/
sort.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! External sort wrapper and SQL key comparison.
8
9use super::{
10    Batch, DefaultExpressionEvaluator, ExecResult, PhysicalOperator, SQLParam, ScalarExpr,
11    SharedExpressionEvaluator, Value,
12};
13
14#[derive(Debug, Clone)]
15pub struct SortKey {
16    pub expr: ScalarExpr,
17    pub descending: bool,
18    /// `Some(true)` forces NULLS FIRST, `Some(false)` forces NULLS
19    /// LAST. `None` falls back to the SQL-standard default - NULLS
20    /// LAST for ASC and NULLS FIRST for DESC.
21    pub nulls_first: Option<bool>,
22}
23
24/// Byte-bounded blocking sort backed by the external merge-sort implementation.
25/// Compatibility constructors use a 64 MiB budget; engine callers should pass
26/// the active session's `work_mem` through [`Self::with_evaluator_and_work_mem`].
27const DEFAULT_SORT_WORK_MEM_BYTES: usize = 64 * 1024 * 1024;
28
29pub struct Sort<'a> {
30    inner: crate::external_sort::ExternalSort<'a>,
31}
32
33impl Sort<'static> {
34    pub fn new(
35        child: Box<dyn PhysicalOperator>,
36        keys: Vec<SortKey>,
37        params: Vec<SQLParam>,
38    ) -> Self {
39        Self::with_evaluator(child, keys, DefaultExpressionEvaluator::shared(params))
40    }
41
42    /// Top-K variant: retain only the first `keep` rows of the sorted
43    /// order. Uses a partial selection, so the cost is `O(n + k log k)`
44    /// instead of `O(n log n)`.
45    pub fn with_keep(
46        child: Box<dyn PhysicalOperator>,
47        keys: Vec<SortKey>,
48        params: Vec<SQLParam>,
49        keep: usize,
50    ) -> Self {
51        Self::with_evaluator_and_keep(
52            child,
53            keys,
54            DefaultExpressionEvaluator::shared(params),
55            keep,
56        )
57    }
58}
59
60impl<'a> Sort<'a> {
61    pub fn with_evaluator(
62        child: Box<dyn PhysicalOperator + 'a>,
63        keys: Vec<SortKey>,
64        evaluator: SharedExpressionEvaluator<'a>,
65    ) -> Self {
66        Self::with_evaluator_and_work_mem(child, keys, evaluator, DEFAULT_SORT_WORK_MEM_BYTES)
67    }
68
69    pub fn with_evaluator_and_work_mem(
70        child: Box<dyn PhysicalOperator + 'a>,
71        mut keys: Vec<SortKey>,
72        evaluator: SharedExpressionEvaluator<'a>,
73        work_mem_bytes: usize,
74    ) -> Self {
75        for key in &mut keys {
76            let expression = std::mem::replace(&mut key.expr, ScalarExpr::Literal(Value::Null));
77            key.expr = evaluator.bind_type_introspection(expression, child.row_schema());
78        }
79        Self {
80            inner: crate::external_sort::ExternalSort::new(
81                child,
82                keys,
83                evaluator,
84                None,
85                work_mem_bytes,
86            ),
87        }
88    }
89
90    pub fn with_evaluator_and_keep(
91        child: Box<dyn PhysicalOperator + 'a>,
92        mut keys: Vec<SortKey>,
93        evaluator: SharedExpressionEvaluator<'a>,
94        keep: usize,
95    ) -> Self {
96        for key in &mut keys {
97            let expression = std::mem::replace(&mut key.expr, ScalarExpr::Literal(Value::Null));
98            key.expr = evaluator.bind_type_introspection(expression, child.row_schema());
99        }
100        Self {
101            inner: crate::external_sort::ExternalSort::new(
102                child,
103                keys,
104                evaluator,
105                Some(keep),
106                DEFAULT_SORT_WORK_MEM_BYTES,
107            ),
108        }
109    }
110}
111
112/// Compare two pre-computed sort-key vectors under `keys` semantics:
113/// per-key direction plus `PostgreSQL` NULLS placement (default NULLS
114/// LAST for ascending, NULLS FIRST for descending).
115pub fn compare_sort_key_values(keys: &[SortKey], av: &[Value], bv: &[Value]) -> std::cmp::Ordering {
116    compare_sort_key_values_by(keys, |index| (&av[index], &bv[index]))
117}
118
119pub(crate) fn compare_sort_key_values_by<'a>(
120    keys: &[SortKey],
121    mut values: impl FnMut(usize) -> (&'a Value, &'a Value),
122) -> std::cmp::Ordering {
123    use std::cmp::Ordering;
124    for (i, k) in keys.iter().enumerate() {
125        let (a, b) = values(i);
126        let a_null = matches!(a, Value::Null);
127        let b_null = matches!(b, Value::Null);
128        let nulls_first = k.nulls_first.unwrap_or(k.descending);
129        if a_null || b_null {
130            let null_cmp = if a_null == b_null {
131                Ordering::Equal
132            } else if a_null {
133                if nulls_first {
134                    Ordering::Less
135                } else {
136                    Ordering::Greater
137                }
138            } else if nulls_first {
139                Ordering::Greater
140            } else {
141                Ordering::Less
142            };
143            if null_cmp != Ordering::Equal {
144                return null_cmp;
145            }
146            continue;
147        }
148        let ord = compare_values(a, b);
149        let ord = if k.descending { ord.reverse() } else { ord };
150        if ord != Ordering::Equal {
151            return ord;
152        }
153    }
154    Ordering::Equal
155}
156
157pub(super) fn compare_values(a: &Value, b: &Value) -> std::cmp::Ordering {
158    use std::cmp::Ordering::*;
159    match (a, b) {
160        (Value::Null, Value::Null) => Equal,
161        (Value::Null, _) => Less,
162        (_, Value::Null) => Greater,
163        (Value::Temporal(x), Value::Str(y)) => x
164            .parse_same_kind(y)
165            .map_or_else(|| a.cmp(b), |parsed| x.cmp(&parsed)),
166        (Value::Str(x), Value::Temporal(y)) => y
167            .parse_same_kind(x)
168            .map_or_else(|| a.cmp(b), |parsed| parsed.cmp(y)),
169        _ => a.cmp(b),
170    }
171}
172
173impl PhysicalOperator for Sort<'_> {
174    fn row_schema(&self) -> &super::RowSchema {
175        self.inner.row_schema()
176    }
177
178    fn output_ordering(&self) -> &[crate::PhysicalOrder] {
179        self.inner.output_ordering()
180    }
181
182    fn backward_scan_support(&self) -> crate::BackwardScanSupport {
183        crate::BackwardScanSupport::Materialize
184    }
185
186    fn open(&mut self) -> ExecResult<()> {
187        self.inner.open()
188    }
189
190    fn next(&mut self) -> ExecResult<Option<Batch>> {
191        self.inner.next()
192    }
193
194    fn close(&mut self) -> ExecResult<()> {
195        self.inner.close()
196    }
197}
198
199// -------------------------------------------------------------------------
200// Limit / Offset
201// -------------------------------------------------------------------------