Skip to main content

pgevolve_core/ir/
aggregate.rs

1//! `AGGREGATE` IR — a schema-scoped object. Ordinary aggregates only
2//! (state function + state type + optional final function / initcond).
3
4use serde::{Deserialize, Serialize};
5
6use crate::identifier::{Identifier, QualifiedName};
7use crate::ir::column_type::ColumnType;
8use crate::ir::difference::Difference;
9use crate::ir::eq::{Equiv, field_difference};
10
11/// A `CREATE AGGREGATE` object. Identity is `(qname, arg_types)`.
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct Aggregate {
14    /// Schema-qualified aggregate name.
15    pub qname: QualifiedName,
16    /// Aggregate argument types (part of identity; aggregates are overloadable).
17    pub arg_types: Vec<ColumnType>,
18    /// State type (`STYPE`).
19    pub state_type: ColumnType,
20    /// State transition function (`SFUNC`) — a managed function name.
21    pub sfunc: QualifiedName,
22    /// Optional final function (`FINALFUNC`).
23    pub finalfunc: Option<QualifiedName>,
24    /// Optional initial condition (`INITCOND`), as text.
25    pub initcond: Option<String>,
26    /// Lenient owner (`None` = unmanaged).
27    pub owner: Option<Identifier>,
28    /// Optional comment.
29    pub comment: Option<String>,
30}
31
32impl Equiv for Aggregate {
33    fn differences(&self, other: &Self) -> Vec<Difference> {
34        // Field-completeness guard: the compiler errors if a field is added
35        // without being handled below. Bindings are unused (read via `self`).
36        let Self {
37            qname: _,
38            arg_types: _,
39            state_type: _,
40            sfunc: _,
41            finalfunc: _,
42            initcond: _,
43            owner: _,
44            comment: _,
45        } = self;
46        let mut out = Vec::new();
47        out.extend(field_difference("qname", &self.qname, &other.qname));
48        out.extend(field_difference(
49            "arg_types",
50            &format!("{:?}", self.arg_types),
51            &format!("{:?}", other.arg_types),
52        ));
53        out.extend(field_difference(
54            "state_type",
55            &format!("{:?}", self.state_type),
56            &format!("{:?}", other.state_type),
57        ));
58        out.extend(field_difference("sfunc", &self.sfunc, &other.sfunc));
59        out.extend(field_difference(
60            "finalfunc",
61            &format!("{:?}", self.finalfunc),
62            &format!("{:?}", other.finalfunc),
63        ));
64        out.extend(field_difference(
65            "initcond",
66            &format!("{:?}", self.initcond),
67            &format!("{:?}", other.initcond),
68        ));
69        out.extend(field_difference(
70            "owner",
71            &format!("{:?}", self.owner),
72            &format!("{:?}", other.owner),
73        ));
74        out.extend(field_difference(
75            "comment",
76            &format!("{:?}", self.comment),
77            &format!("{:?}", other.comment),
78        ));
79        out
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    fn id(s: &str) -> Identifier {
88        Identifier::from_unquoted(s).unwrap()
89    }
90
91    fn qname(schema: &str, name: &str) -> QualifiedName {
92        QualifiedName::new(id(schema), id(name))
93    }
94
95    fn sample_aggregate() -> Aggregate {
96        Aggregate {
97            qname: qname("app", "my_sum"),
98            arg_types: vec![ColumnType::Integer],
99            state_type: ColumnType::BigInt,
100            sfunc: qname("app", "my_sum_sfunc"),
101            finalfunc: Some(qname("app", "my_sum_final")),
102            initcond: Some("0".to_string()),
103            owner: Some(id("app_owner")),
104            comment: Some("A custom sum aggregate.".to_string()),
105        }
106    }
107
108    #[test]
109    fn aggregate_serde_round_trip() {
110        let agg = sample_aggregate();
111        let json = serde_json::to_string(&agg).unwrap();
112        let back: Aggregate = serde_json::from_str(&json).unwrap();
113        assert_eq!(agg, back);
114    }
115}