Skip to main content

pgevolve_core/ir/
partition.rs

1//! Partitioning IR — partition-by clauses on partitioned parents and
2//! partition-of declarations on partition children.
3
4use serde::{Deserialize, Serialize};
5
6use crate::identifier::{Identifier, QualifiedName};
7use crate::ir::default_expr::NormalizedExpr;
8
9/// Describes the partitioning strategy and key of a partitioned parent table.
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct PartitionBy {
12    /// The partitioning strategy (`RANGE`, `LIST`, or `HASH`).
13    pub strategy: PartitionStrategy,
14    /// The partition key columns (or expressions).
15    pub columns: Vec<PartitionColumn>,
16}
17
18/// The partitioning strategy declared by `PARTITION BY <strategy>`.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case")]
21pub enum PartitionStrategy {
22    /// `PARTITION BY RANGE (...)`.
23    Range,
24    /// `PARTITION BY LIST (...)`.
25    List,
26    /// `PARTITION BY HASH (...)`.
27    Hash,
28}
29
30/// A single element of the partition key.
31#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
32pub struct PartitionColumn {
33    /// The key element — a plain column reference or an arbitrary expression.
34    pub kind: PartitionColumnKind,
35    /// Optional collation override.
36    pub collation: Option<QualifiedName>,
37    /// Optional operator class override.
38    pub opclass: Option<QualifiedName>,
39}
40
41/// Whether the partition key element is a column reference or an expression.
42#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
43#[serde(rename_all = "snake_case")]
44pub enum PartitionColumnKind {
45    /// A plain column reference.
46    Column(Identifier),
47    /// A parenthesized expression.
48    Expr(NormalizedExpr),
49}
50
51/// Declares that this table is itself a partition of a parent table.
52///
53/// Corresponds to `PARTITION OF <parent> <bounds>` in the DDL.
54#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
55pub struct PartitionOf {
56    /// The schema-qualified parent table.
57    pub parent: QualifiedName,
58    /// The partition bounds clause.
59    pub bounds: PartitionBounds,
60}
61
62/// The bounds of a partition child table.
63#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
64#[serde(rename_all = "snake_case", tag = "kind")]
65pub enum PartitionBounds {
66    /// `FOR VALUES FROM (...) TO (...)` — range partition.
67    Range {
68        /// The `FROM` bound datums (one per key column).
69        from: Vec<BoundDatum>,
70        /// The `TO` bound datums (one per key column).
71        to: Vec<BoundDatum>,
72    },
73    /// `FOR VALUES IN (...)` — list partition.
74    List {
75        /// The listed bound datums.
76        values: Vec<BoundDatum>,
77    },
78    /// `FOR VALUES WITH (MODULUS m, REMAINDER r)` — hash partition.
79    Hash {
80        /// Hash modulus.
81        modulus: u32,
82        /// Hash remainder.
83        remainder: u32,
84    },
85    /// `DEFAULT` — catches rows not matched by any other partition.
86    Default,
87}
88
89/// A single datum in a partition bound clause.
90#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
91#[serde(rename_all = "snake_case")]
92pub enum BoundDatum {
93    /// A concrete literal expression (e.g., `'2024-01-01'`, `42`).
94    Literal(NormalizedExpr),
95    /// The pseudo-datum `MINVALUE`.
96    MinValue,
97    /// The pseudo-datum `MAXVALUE`.
98    MaxValue,
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn partition_strategy_round_trip() {
107        let s = PartitionStrategy::Range;
108        let json = serde_json::to_string(&s).unwrap();
109        let back: PartitionStrategy = serde_json::from_str(&json).unwrap();
110        assert_eq!(s, back);
111    }
112
113    #[test]
114    fn hash_bounds_round_trip() {
115        let b = PartitionBounds::Hash {
116            modulus: 4,
117            remainder: 1,
118        };
119        let json = serde_json::to_string(&b).unwrap();
120        let back: PartitionBounds = serde_json::from_str(&json).unwrap();
121        assert_eq!(b, back);
122    }
123
124    #[test]
125    fn default_bounds_round_trip() {
126        let b = PartitionBounds::Default;
127        let json = serde_json::to_string(&b).unwrap();
128        let back: PartitionBounds = serde_json::from_str(&json).unwrap();
129        assert_eq!(b, back);
130    }
131}