Skip to main content

inillucent_sql/bind/
collation.rs

1//! Which collation a comparison, a sort or a grouping uses, and where an
2//! explicit `COLLATE` comes from.
3//!
4//! Invariant: **every collation the binder decides comes from the rules in
5//! this file, and they are SQLite's `sqlite3ExprCollSeq` and
6//! `sqlite3BinaryCompareCollSeq`, graded against the pinned 3.53.4 shell.**
7//! The executor's `expression_collation` used to be a second copy of them
8//! that looked only at the top node, and the binder's own two helpers did the
9//! same, so an explicit `COLLATE` inside an operand of `||` reached neither a
10//! comparison nor a `GROUP BY` (task-2089). One copy, here, is what keeps a
11//! comparison, an `ORDER BY`, a `DISTINCT` and a `PARTITION BY` agreeing
12//! about which values are equal. The cases that grade these rules are
13//! `crates/inillucent-compat/tests/corpora/differential-part8/task2089.cases`.
14
15use inillucent_value::Collation;
16
17use super::BoundExpr;
18use crate::ast::UnaryOp;
19
20impl BoundExpr {
21    /// Returns the collation this expression carries, if it has one.
22    ///
23    /// SQLite's `sqlite3ExprCollSeq`, in its order: a column has its declared
24    /// collation, a `CAST` and a unary `+` have their operand's, and any other
25    /// expression has the explicit collation of an operand, if one has one.
26    /// So `CAST(n AS TEXT) = 'A'` on a NOCASE column `n` compares with NOCASE,
27    /// and `n || '' = 'A'` compares with BINARY. Measured against 3.53.4
28    /// (task-2089): `CAST(n AS TEXT) = 'A'` and `+n = 'A'` answered 0 here
29    /// where SQLite answers 1.
30    pub fn collation(&self) -> Option<Collation> {
31        match self {
32            BoundExpr::Column { collation, .. } | BoundExpr::Generated { collation, .. } => {
33                Some(*collation)
34            }
35            BoundExpr::Cast { operand, .. }
36            | BoundExpr::Unary {
37                op: UnaryOp::Identity,
38                operand,
39            } => operand.collation(),
40            // One column of a subquery used as a row value reads the collation
41            // of the column it stands for, as SQLite does for a `SELECT_COLUMN`.
42            // A scalar subquery written alone carries Binary here and none.
43            BoundExpr::Subquery {
44                kind: super::SubqueryKind::Scalar,
45                collation,
46                ..
47            } if *collation != Collation::Binary => Some(*collation),
48            // The `coalesce` SQLite builds for a merged `USING` column of a
49            // `FULL` join reads the collation of its first argument, so a
50            // NOCASE column keeps comparing as NOCASE after the merge.
51            BoundExpr::Function {
52                func: crate::function::ScalarFunc::UsingCoalesce,
53                arguments,
54                ..
55            } => arguments.first().and_then(BoundExpr::collation),
56            other => other.explicit_collation(),
57        }
58    }
59
60    /// Returns the collation an explicit `COLLATE` forced on this expression.
61    ///
62    /// This is *not* the same question as [`BoundExpr::collation`]. A column
63    /// declared `COLLATE NOCASE` has an implicit collation; `x COLLATE BINARY`
64    /// has an explicit one, and an explicit collation on either side of a
65    /// comparison beats an implicit one on the other side.
66    ///
67    /// **An explicit collation reaches up through every operator and function
68    /// argument (task-2089).** SQLite marks a node `EP_Collate` when any
69    /// operand has the mark, and reads the collation from the first operand
70    /// that has it, left first. This used to look only at the top node, so
71    /// `('a' COLLATE NOCASE || 'x') = 'AX'` compared with BINARY and answered
72    /// 0 where 3.53.4 answers 1, and `'a' COLLATE BINARY || 'b' COLLATE
73    /// NOCASE` has to answer BINARY because the left operand is asked first.
74    /// [`BoundExpr::children`] lists operands in SQLite's order for every node
75    /// whose value is text. A scalar subquery has no children here, and SQLite
76    /// does not carry a `COLLATE` out of one either. An aggregate or window
77    /// call has no children here either, because its arguments live in the
78    /// block's lists, so its reference carries the answer for them: see
79    /// `explicit_argument_collation`, in `aggregate.rs`.
80    pub fn explicit_collation(&self) -> Option<Collation> {
81        match self {
82            BoundExpr::Collate { collation, .. } => Some(*collation),
83            BoundExpr::Generated { .. } => None,
84            BoundExpr::Aggregate { collation, .. } | BoundExpr::WindowRef { collation, .. } => {
85                *collation
86            }
87            other => other
88                .children()
89                .into_iter()
90                .find_map(BoundExpr::explicit_collation),
91        }
92    }
93}
94
95/// Returns the collation a result column compares with.
96///
97/// `DISTINCT` and `GROUP BY` compare result values, and a NOCASE column makes
98/// `blue` and `Blue` the same value for both. Comparing them with BINARY
99/// instead returns more rows than SQLite does, which looks like a duplicate
100/// rather than like a bug.
101pub fn result_collation(expr: &BoundExpr) -> Collation {
102    expr.explicit_collation()
103        .or_else(|| expr.collation())
104        .unwrap_or(Collation::Binary)
105}
106
107/// Wraps an expression in the collation an explicit `COLLATE` names.
108///
109/// **A `COLLATE` above a comparison does not reach the comparison (task-1979,
110/// F5).** `a = b COLLATE NOCASE` parses as `a = (b COLLATE NOCASE)`, because
111/// `COLLATE` binds tighter than `=`, and the comparison then reads NOCASE off
112/// its own right operand through [`comparison_rules`]. `(a = b) COLLATE
113/// NOCASE` is the other tree: the comparison is finished and NOCASE applies to
114/// the integer it produced, where a text collation does nothing. This function
115/// used to stamp the collation onto a `BoundExpr::Compare` it was handed, which
116/// made the two trees answer the same and made the outer name win over the
117/// inner one: measured against 3.53.4, `SELECT ('B'<'a') COLLATE NOCASE`
118/// answered 0 where SQLite answers 1, and
119/// `SELECT ('a' = 'A' COLLATE NOCASE) COLLATE BINARY` answered 0 where SQLite
120/// answers 1 because the inner NOCASE is the comparison's and the outer BINARY
121/// is the result's.
122///
123/// The wrapper is what carries the collation onward: [`comparison_rules`] asks
124/// an operand for its [`BoundExpr::explicit_collation`], so a `COLLATE` on a
125/// literal still reaches the comparison that uses it.
126///
127/// @param expr - the operand the `COLLATE` was written on
128/// @param collation - the collation it names
129pub(super) fn apply_collation(expr: BoundExpr, collation: Collation) -> BoundExpr {
130    BoundExpr::Collate {
131        operand: Box::new(expr),
132        collation,
133    }
134}
135
136#[cfg(test)]
137mod tests;