Skip to main content

pgevolve_core/ir/
cast.rs

1//! `CAST` IR — a global (non-schema-scoped) object. Identity is `(source, target)`.
2
3use serde::{Deserialize, Serialize};
4
5use crate::identifier::QualifiedName;
6use crate::ir::column_type::ColumnType;
7
8/// A `CREATE CAST` object. Identity is `(source, target)`.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct Cast {
11    /// Source type (part of identity).
12    pub source: QualifiedName,
13    /// Target type (part of identity).
14    pub target: QualifiedName,
15    /// How the cast is performed.
16    pub method: CastMethod,
17    /// When the cast is applied implicitly.
18    pub context: CastContext,
19    /// Optional comment.
20    pub comment: Option<String>,
21}
22
23/// The conversion mechanism for a `CREATE CAST`.
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub enum CastMethod {
26    /// `WITH FUNCTION fn(argtypes)` — a managed function.
27    ///
28    /// `arg_types` is the recorded conversion-function signature
29    /// (1–3 args: source\[, int4, bool\]).
30    Function {
31        /// Schema-qualified function name.
32        name: QualifiedName,
33        /// Argument types of the conversion function.
34        arg_types: Vec<ColumnType>,
35    },
36    /// `WITH INOUT` — uses the types' I/O functions.
37    Inout,
38    /// `WITHOUT FUNCTION` — binary-coercible; no function required.
39    Binary,
40}
41
42/// Controls when Postgres automatically applies the cast.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(rename_all = "snake_case")]
45pub enum CastContext {
46    /// Default — explicit casts only (`CAST(x AS t)` or `x::t`).
47    Explicit,
48    /// `AS ASSIGNMENT` — implicitly applied on assignment.
49    Assignment,
50    /// `AS IMPLICIT` — applied implicitly in expressions.
51    Implicit,
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use crate::identifier::{Identifier, QualifiedName};
58
59    fn id(s: &str) -> Identifier {
60        Identifier::from_unquoted(s).unwrap()
61    }
62
63    fn qname(schema: &str, name: &str) -> QualifiedName {
64        QualifiedName::new(id(schema), id(name))
65    }
66
67    fn cast_function() -> Cast {
68        Cast {
69            source: qname("app", "my_type"),
70            target: qname("pg_catalog", "text"),
71            method: CastMethod::Function {
72                name: qname("app", "my_type_to_text"),
73                arg_types: vec![ColumnType::Integer],
74            },
75            context: CastContext::Explicit,
76            comment: Some("Converts my_type to text.".to_string()),
77        }
78    }
79
80    fn cast_inout() -> Cast {
81        Cast {
82            source: qname("app", "domain_a"),
83            target: qname("app", "domain_b"),
84            method: CastMethod::Inout,
85            context: CastContext::Assignment,
86            comment: None,
87        }
88    }
89
90    fn cast_binary() -> Cast {
91        Cast {
92            source: qname("app", "type_x"),
93            target: qname("app", "type_y"),
94            method: CastMethod::Binary,
95            context: CastContext::Implicit,
96            comment: None,
97        }
98    }
99
100    #[test]
101    fn cast_function_serde_round_trip() {
102        let c = cast_function();
103        let json = serde_json::to_string(&c).unwrap();
104        let back: Cast = serde_json::from_str(&json).unwrap();
105        assert_eq!(c, back);
106    }
107
108    #[test]
109    fn cast_inout_serde_round_trip() {
110        let c = cast_inout();
111        let json = serde_json::to_string(&c).unwrap();
112        let back: Cast = serde_json::from_str(&json).unwrap();
113        assert_eq!(c, back);
114    }
115
116    #[test]
117    fn cast_binary_serde_round_trip() {
118        let c = cast_binary();
119        let json = serde_json::to_string(&c).unwrap();
120        let back: Cast = serde_json::from_str(&json).unwrap();
121        assert_eq!(c, back);
122    }
123
124    #[test]
125    fn cast_context_serde_variants() {
126        let explicit = serde_json::to_string(&CastContext::Explicit).unwrap();
127        let assignment = serde_json::to_string(&CastContext::Assignment).unwrap();
128        let implicit = serde_json::to_string(&CastContext::Implicit).unwrap();
129        assert_eq!(explicit, "\"explicit\"");
130        assert_eq!(assignment, "\"assignment\"");
131        assert_eq!(implicit, "\"implicit\"");
132
133        let back_e: CastContext = serde_json::from_str(&explicit).unwrap();
134        let back_a: CastContext = serde_json::from_str(&assignment).unwrap();
135        let back_i: CastContext = serde_json::from_str(&implicit).unwrap();
136        assert_eq!(back_e, CastContext::Explicit);
137        assert_eq!(back_a, CastContext::Assignment);
138        assert_eq!(back_i, CastContext::Implicit);
139    }
140}