Skip to main content

safe_migrate/model/
relation.rs

1// FILE: src/model/relation.rs
2use crate::ast::identifiers::ObjectId;
3use crate::model::column::Column;
4use serde::{Deserialize, Serialize};
5use std::collections::HashSet;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub enum RelationKind {
9    Table,
10    View,
11    MaterializedView,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub enum Persistence {
16    Permanent,
17    Temporary,
18    Unlogged,
19}
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct RelationState {
23    pub id: ObjectId,
24    pub columns: Vec<Column>,
25    pub generation: u64,
26    pub estimated_rows: Option<u64>,
27    pub relpages: Option<u64>,
28    pub kind: RelationKind,
29    pub persistence: Persistence,
30    pub triggers: HashSet<String>,
31    pub policies: HashSet<String>,
32    pub last_analyze: Option<String>,
33    pub last_autoanalyze: Option<String>,
34    pub created_at_tx_depth: usize, // Phase 1 FIX: Same-Transaction index tracking
35}
36
37impl Default for RelationState {
38    fn default() -> Self {
39        Self {
40            id: ObjectId::new("public", "dummy"),
41            columns: Vec::new(),
42            generation: 0,
43            estimated_rows: Some(0),
44            relpages: None,
45            kind: RelationKind::Table,
46            persistence: Persistence::Permanent,
47            triggers: HashSet::new(),
48            policies: HashSet::new(),
49            last_analyze: None,
50            last_autoanalyze: None,
51            created_at_tx_depth: 0,
52        }
53    }
54}
55
56impl RelationState {
57    pub fn new(
58        id: ObjectId,
59        generation: u64,
60        estimated_rows: Option<u64>,
61        kind: RelationKind,
62        persistence: Persistence,
63        created_at_tx_depth: usize,
64    ) -> Self {
65        Self {
66            id,
67            columns: Vec::new(),
68            generation,
69            estimated_rows,
70            relpages: None,
71            kind,
72            persistence,
73            triggers: HashSet::new(),
74            policies: HashSet::new(),
75            last_analyze: None,
76            last_autoanalyze: None,
77            created_at_tx_depth,
78        }
79    }
80
81    pub fn apply_column_action(&mut self, action: &ColumnAction) {
82        match action {
83            ColumnAction::Add {
84                name,
85                data_type,
86                not_null,
87                default,
88            } => {
89                if !self.columns.iter().any(|c| c.name == *name) {
90                    self.columns.push(Column {
91                        name: name.clone(),
92                        data_type: data_type.clone(),
93                        default: default.clone(),
94                        is_nullable: !not_null,
95                        avg_width: None,
96                    });
97                }
98            }
99            ColumnAction::Drop { name } => {
100                self.columns.retain(|c| c.name != *name);
101            }
102            ColumnAction::Rename { from, to } => {
103                if let Some(col) = self.columns.iter_mut().find(|c| c.name == *from) {
104                    col.name = to.clone();
105                }
106            }
107            ColumnAction::SetNotNull { name } => {
108                if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
109                    col.is_nullable = false;
110                }
111            }
112            ColumnAction::DropNotNull { name } => {
113                if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
114                    col.is_nullable = true;
115                }
116            }
117            ColumnAction::SetType { name, data_type } => {
118                if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
119                    col.data_type = Some(data_type.clone());
120                }
121            }
122            ColumnAction::SetDefault { name, default } => {
123                if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
124                    col.default = default.clone();
125                }
126            }
127        }
128    }
129
130    pub fn has_column(&self, name: &str) -> bool {
131        self.columns.iter().any(|c| c.name == name)
132    }
133
134    pub fn get_column(&self, name: &str) -> Option<&Column> {
135        self.columns.iter().find(|c| c.name == name)
136    }
137
138    pub fn is_stale(&self) -> bool {
139        self.last_analyze.is_none() && self.last_autoanalyze.is_none()
140    }
141}
142
143#[derive(Debug, Clone, PartialEq)]
144pub enum ColumnAction {
145    Add {
146        name: String,
147        data_type: Option<String>,
148        not_null: bool,
149        default: Option<crate::analysis::expr_ir::ExprIr>,
150    },
151    Drop {
152        name: String,
153    },
154    Rename {
155        from: String,
156        to: String,
157    },
158    SetNotNull {
159        name: String,
160    },
161    DropNotNull {
162        name: String,
163    },
164    SetType {
165        name: String,
166        data_type: String,
167    },
168    SetDefault {
169        name: String,
170        default: Option<crate::analysis::expr_ir::ExprIr>,
171    },
172}
173
174#[allow(clippy::large_enum_variant)]
175#[derive(Debug, Clone, PartialEq)]
176pub enum RelationOverlay {
177    Present(RelationState),
178    Dropped,
179}