Skip to main content

nodedb_sql/planner/
sort.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! ORDER BY planning and sort key extraction.
4//!
5//! Search-triggering sort detection (vector_distance, bm25_score, rrf_score)
6//! is handled in select.rs as part of apply_order_by. This module provides
7//! sort key extraction utilities.
8
9use sqlparser::ast::{OrderByOptions, OrderBySort};
10
11use crate::error::{Result, SqlError};
12use crate::resolver::ColumnScope;
13use crate::resolver::columns::TableScope;
14use crate::resolver::expr::convert_expr;
15use crate::types::SortKey;
16
17/// Convert sqlparser OrderByExpr list to SortKey list.
18pub fn convert_sort_keys(
19    exprs: &[sqlparser::ast::OrderByExpr],
20    scope: &TableScope,
21) -> Result<Vec<SortKey>> {
22    let scope = ColumnScope::Relations(scope);
23    exprs
24        .iter()
25        .map(|o| {
26            Ok(SortKey {
27                expr: convert_expr(&o.expr, &scope)?,
28                ascending: sort_ascending(&o.options)?,
29                nulls_first: sort_nulls_first(&o.options)?,
30            })
31        })
32        .collect()
33}
34
35/// Whether an `ORDER BY` key sorts ascending. `USING <operator>` is refused:
36/// no operator orders values here.
37pub fn sort_ascending(options: &OrderByOptions) -> Result<bool> {
38    match &options.sort {
39        None | Some(OrderBySort::Asc) => Ok(true),
40        Some(OrderBySort::Desc) => Ok(false),
41        Some(OrderBySort::Using(operator)) => Err(SqlError::Unsupported {
42            detail: format!("ORDER BY ... USING {operator} is not supported"),
43        }),
44    }
45}
46
47/// Whether an `ORDER BY` key puts NULLs first. Unstated, NULLs sort last
48/// ascending and first descending, as in PostgreSQL.
49pub fn sort_nulls_first(options: &OrderByOptions) -> Result<bool> {
50    match options.nulls_first {
51        Some(first) => Ok(first),
52        None => Ok(!sort_ascending(options)?),
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use sqlparser::ast::{OrderByKind, Statement};
59
60    use super::*;
61    use crate::parser::statement::parse_sql;
62
63    /// The options of the first `ORDER BY` key of `SELECT a FROM t ORDER BY <key>`.
64    fn options(key: &str) -> OrderByOptions {
65        let statements = parse_sql(&format!("SELECT a FROM t ORDER BY {key}")).expect("parses");
66        let Statement::Query(query) = &statements[0] else {
67            panic!("expected a query");
68        };
69        let Some(OrderByKind::Expressions(keys)) = query.order_by.as_ref().map(|o| &o.kind) else {
70            panic!("expected ORDER BY expressions");
71        };
72        keys[0].options.clone()
73    }
74
75    #[test]
76    fn nulls_follow_the_direction_unless_stated() {
77        let asc = options("a");
78        assert!(sort_ascending(&asc).expect("ascending"));
79        assert!(!sort_nulls_first(&asc).expect("nulls last"));
80
81        let desc = options("a DESC");
82        assert!(!sort_ascending(&desc).expect("descending"));
83        assert!(sort_nulls_first(&desc).expect("nulls first"));
84
85        let stated = options("a DESC NULLS LAST");
86        assert!(!sort_nulls_first(&stated).expect("nulls last"));
87    }
88
89    #[test]
90    fn using_an_operator_is_refused() {
91        let using = options("a USING <");
92        let err = sort_ascending(&using).expect_err("USING is refused");
93        assert!(matches!(err, SqlError::Unsupported { .. }), "{err}");
94        assert!(sort_nulls_first(&using).is_err());
95    }
96}