Skip to main content

uqa_sql/ir/
collation.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Fixed collation selection retained by executable expression rewrites.
8
9use crate::catalog::collations::derivation::Derivation;
10use crate::{ColumnType, SQLError};
11
12/// An analyzed result keeps its coercibility as well as its OID: an explicit selection outranks implicit inputs, and an indeterminate selection retains the conflicting identities for later key diagnostics.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
14pub struct ResultCollation {
15    derivation: Derivation,
16    initial_oid: u32,
17}
18
19impl<'de> serde::Deserialize<'de> for ResultCollation {
20    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
21        #[derive(serde::Deserialize)]
22        struct Stored {
23            derivation: Derivation,
24            initial_oid: u32,
25        }
26        let Stored {
27            derivation,
28            initial_oid,
29        } = <Stored as serde::Deserialize>::deserialize(deserializer)?;
30        let valid_oid = |oid| u32::try_from(oid).is_ok();
31        let valid = match derivation {
32            Derivation::None => true,
33            Derivation::Implicit(oid) | Derivation::Explicit(oid) => valid_oid(oid),
34            Derivation::Conflict(left, right) => valid_oid(left) && valid_oid(right),
35            Derivation::InvalidExplicit(..) | Derivation::InvalidImplicit(..) => false,
36        };
37        if !valid {
38            return Err(serde::de::Error::custom(
39                "invalid retained collation selection",
40            ));
41        }
42        Ok(Self::new(derivation, initial_oid))
43    }
44}
45
46impl ResultCollation {
47    pub(crate) fn new(derivation: Derivation, initial_oid: u32) -> Self {
48        Self {
49            derivation,
50            initial_oid,
51        }
52    }
53
54    pub(crate) fn derivation(self) -> Derivation {
55        self.derivation
56    }
57
58    pub(crate) fn initial_oid(self) -> u32 {
59        self.initial_oid
60    }
61
62    pub fn oid(self) -> u32 {
63        u32::try_from(self.derivation.oid()).expect("analyzed result collation OID")
64    }
65
66    /// A typed constant can directly carry an implicit selection only when its pre-assignment field collation is the same.
67    pub fn implicit_oid(self) -> Option<u32> {
68        match self.derivation {
69            Derivation::Implicit(_) if self.initial_oid == self.oid() => Some(self.oid()),
70            _ => None,
71        }
72    }
73
74    pub(crate) fn validate(self, ty: &ColumnType) -> Result<(), SQLError> {
75        self.derivation.validate()?;
76        crate::catalog::collations::expression::validate_literal_collation(
77            Some(ty),
78            Some(self.oid()),
79        )
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn result_collation_codec_preserves_coercibility_and_rejects_invalid_identities() {
89        for derivation in [
90            Derivation::None,
91            Derivation::Implicit(950),
92            Derivation::Explicit(100),
93            Derivation::Conflict(950, 951),
94        ] {
95            let selection = ResultCollation::new(derivation, 0);
96            let encoded = serde_json::to_string(&selection).unwrap();
97            let restored: ResultCollation = serde_json::from_str(&encoded).unwrap();
98            assert_eq!(restored, selection);
99            assert_eq!(restored.initial_oid(), 0);
100            assert!(restored.implicit_oid().is_none());
101        }
102        assert_eq!(
103            ResultCollation::new(Derivation::Implicit(950), 950).implicit_oid(),
104            Some(950)
105        );
106        for derivation in [
107            Derivation::Implicit(-1),
108            Derivation::Explicit(i64::from(u32::MAX) + 1),
109            Derivation::Conflict(950, -1),
110            Derivation::InvalidExplicit(950, 951),
111            Derivation::InvalidImplicit(950, 951),
112        ] {
113            let invalid = ResultCollation::new(derivation, 0);
114            assert!(serde_json::from_str::<ResultCollation>(
115                &serde_json::to_string(&invalid).unwrap()
116            )
117            .is_err());
118        }
119    }
120}