inillucent_sql/bind/set_rules.rs
1//! The affinity and collation rules for a value list and for a compound query.
2//!
3//! Invariant: **these two functions are SQLite's rules for `x IN (list)` and
4//! for the columns of a compound, measured against the pinned 3.53.4 shell**,
5//! and they differ from the rules for a comparison between two operands in
6//! `collation.rs`: a list takes everything from its left operand, and a
7//! compound takes its collation from the leftmost arm that has one.
8
9use inillucent_value::{Affinity, Collation};
10
11use super::{comparison_rules, result_collation, BoundExpr};
12
13/// Returns the collation of a compound's result column, one expression per arm.
14///
15/// SQLite's `multiSelectCollSeq`: the leftmost arm whose expression has a
16/// collation of its own decides, and an arm whose expression has none (a
17/// literal, an arithmetic result) passes the question to the next arm on the
18/// right. A plain column always has one, BINARY at least, so it ends the
19/// search. Measured against 3.53.4: `SELECT 'A' UNION SELECT 'a' COLLATE
20/// NOCASE` is one row, and `SELECT 'A' COLLATE NOCASE UNION SELECT 'a'` is
21/// one row too.
22///
23/// @param exprs - the column's expression in each arm, leftmost first
24pub fn compound_collation<'a>(exprs: impl IntoIterator<Item = &'a BoundExpr>) -> Collation {
25 exprs
26 .into_iter()
27 .find_map(|expr| expr.explicit_collation().or_else(|| expr.collation()))
28 .unwrap_or(Collation::Binary)
29}
30
31/// Returns the affinity and collation `operand IN (list)` compares with.
32///
33/// Measured against 3.53.4: the list items are stored under the LEFT operand's
34/// affinity and looked up under the LEFT operand's collation, so an item's own
35/// affinity (`1 IN (CAST('1' AS TEXT), 2)` is 0) and column collation
36/// (`'A' IN (nocase_column, 'x')` is 0) do not count, and neither does an
37/// explicit `COLLATE` on an item. The one exception is a list of a single
38/// constant item, which SQLite's parser rewrites to `operand = +item`: then an
39/// explicit `COLLATE` on the item counts as it does for `=`, and the unary plus
40/// still hides the item's affinity.
41///
42/// @param operand - the left operand
43/// @param list - the bound items between the parentheses
44pub fn in_list_rules(operand: &BoundExpr, list: &[BoundExpr]) -> (Option<Affinity>, Collation) {
45 let affinity = operand.affinity();
46 let collation = match list {
47 [only] if only.is_constant() => comparison_rules(operand, only).1,
48 _ => result_collation(operand),
49 };
50 (affinity, collation)
51}