1use crate::ast::identifiers::ObjectId;
3use crate::model::column::Column;
4use serde::{Deserialize, Serialize};
5use std::collections::{HashMap, HashSet};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub enum Privilege {
9 Select,
10 Insert,
11 Update,
12 Delete,
13 Truncate,
14 References,
15 Trigger,
16 All,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
20pub struct PrivilegeMatrix {
21 pub grants: HashMap<ObjectId, HashSet<Privilege>>,
23}
24
25impl PrivilegeMatrix {
26 pub fn grant(&mut self, role: ObjectId, privileges: HashSet<Privilege>) {
27 self.grants.entry(role).or_default().extend(privileges);
28 }
29
30 pub fn revoke(&mut self, role: &ObjectId, privileges: &HashSet<Privilege>) {
31 if let Some(owned) = self.grants.get_mut(role) {
32 if privileges.contains(&Privilege::All) {
33 owned.clear();
34 } else {
35 for p in privileges {
36 owned.remove(p);
37 }
38 }
39 }
40 }
41
42 pub fn has_privilege(&self, role: &ObjectId, privilege: Privilege) -> bool {
43 self.grants.get(role).is_some_and(|set| {
44 set.contains(&privilege)
45 || (privilege != Privilege::All && set.contains(&Privilege::All))
46 })
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51pub enum RelationKind {
52 Table,
53 View,
54 MaterializedView,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58pub enum Persistence {
59 Permanent,
60 Temporary,
61 Unlogged,
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct RelationState {
66 pub id: ObjectId,
67 pub owner: ObjectId,
68 pub columns: Vec<Column>,
69 pub generation: u64,
70 pub estimated_rows: Option<u64>,
71 pub relpages: Option<u64>,
72 pub kind: RelationKind,
73 pub persistence: Persistence,
74 pub triggers: HashSet<String>,
75 pub policies: HashSet<String>,
76 pub last_analyze: Option<String>,
77 pub last_autoanalyze: Option<String>,
78 pub created_at_tx_depth: usize, pub privileges: PrivilegeMatrix,
80 pub partition_type: Option<String>, pub partition_by: Option<String>, #[serde(default)]
83 pub is_fk_dependency: bool,
84}
85
86impl Default for RelationState {
87 fn default() -> Self {
88 Self {
89 id: ObjectId::new("public", "dummy"),
90 owner: ObjectId::new("public", "postgres"),
91 columns: Vec::new(),
92 generation: 0,
93 estimated_rows: Some(0),
94 relpages: None,
95 kind: RelationKind::Table,
96 persistence: Persistence::Permanent,
97 triggers: HashSet::new(),
98 policies: HashSet::new(),
99 last_analyze: None,
100 last_autoanalyze: None,
101 created_at_tx_depth: 0,
102 privileges: PrivilegeMatrix::default(),
103 partition_type: None,
104 partition_by: None,
105 is_fk_dependency: false,
106 }
107 }
108}
109
110impl RelationState {
111 pub fn new(
112 id: ObjectId,
113 owner: ObjectId,
114 generation: u64,
115 estimated_rows: Option<u64>,
116 kind: RelationKind,
117 persistence: Persistence,
118 created_at_tx_depth: usize,
119 ) -> Self {
120 Self {
121 id,
122 owner,
123 columns: Vec::new(),
124 generation,
125 estimated_rows,
126 relpages: None,
127 kind,
128 persistence,
129 triggers: HashSet::new(),
130 policies: HashSet::new(),
131 last_analyze: None,
132 last_autoanalyze: None,
133 created_at_tx_depth,
134 privileges: PrivilegeMatrix::default(),
135 partition_type: None,
136 partition_by: None,
137 is_fk_dependency: false,
138 }
139 }
140
141 pub fn mark_fk_dependency(&mut self) {
142 self.is_fk_dependency = true;
143 }
144
145 pub fn apply_column_action(&mut self, action: &ColumnAction) {
146 match action {
147 ColumnAction::Add {
148 name,
149 data_type,
150 not_null,
151 default,
152 } => {
153 if !self.columns.iter().any(|c| c.name == *name) {
154 let serial_type = data_type
155 .as_deref()
156 .map(str::trim)
157 .map(str::to_ascii_lowercase)
158 .and_then(|ty| match ty.as_str() {
159 "smallserial" | "serial2" => Some("smallint"),
160 "serial" | "serial4" => Some("integer"),
161 "bigserial" | "serial8" => Some("bigint"),
162 _ => None,
163 });
164 let is_serial = serial_type.is_some();
165 let normalized_default = if is_serial {
166 Some(crate::analysis::expr_ir::ExprIr::FunctionCall {
167 name: "nextval".to_string(),
168 args: Vec::new(),
169 })
170 } else if matches!(
171 default,
172 Some(crate::analysis::expr_ir::ExprIr::Literal(value))
173 if value.trim().eq_ignore_ascii_case("null")
174 ) {
175 None
176 } else {
177 default.clone()
178 };
179 self.columns.push(Column {
180 name: name.clone(),
181 data_type: serial_type
182 .map(str::to_string)
183 .or_else(|| data_type.clone()),
184 default: normalized_default,
185 is_nullable: !(*not_null || is_serial),
186 avg_width: None,
187 default_expr_text: None,
188 type_modifier: None,
189 });
190 }
191 }
192 ColumnAction::Drop { name } => {
193 self.columns.retain(|c| c.name != *name);
194 }
195 ColumnAction::Rename { from, to } => {
196 if let Some(pos) = self.columns.iter().position(|c| c.name == *from)
197 && !self.columns.iter().any(|c| c.name == *to)
198 {
199 self.columns[pos].name = to.clone();
200 }
201 }
202 ColumnAction::SetNotNull { name } => {
203 if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
204 col.is_nullable = false;
205 }
206 }
207 ColumnAction::DropNotNull { name } => {
208 if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
209 col.is_nullable = true;
210 }
211 }
212 ColumnAction::SetType { name, data_type } => {
213 if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
214 col.data_type = Some(data_type.clone());
215 }
216 }
217 ColumnAction::SetDefault { name, default } => {
218 if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
219 col.default = if matches!(
220 default,
221 Some(crate::analysis::expr_ir::ExprIr::Literal(value))
222 if value.trim().eq_ignore_ascii_case("null")
223 ) {
224 None
225 } else {
226 default.clone()
227 };
228 col.default_expr_text = None;
230 }
231 }
232 }
233 }
234
235 pub fn has_column(&self, name: &str) -> bool {
236 self.columns.iter().any(|c| c.name == name)
237 }
238
239 pub fn get_column(&self, name: &str) -> Option<&Column> {
240 self.columns.iter().find(|c| c.name == name)
241 }
242
243 pub fn is_stale(&self) -> bool {
244 self.last_analyze.is_none() && self.last_autoanalyze.is_none()
245 }
246}
247
248#[derive(Debug, Clone, PartialEq)]
249pub enum ColumnAction {
250 Add {
251 name: String,
252 data_type: Option<String>,
253 not_null: bool,
254 default: Option<crate::analysis::expr_ir::ExprIr>,
255 },
256 Drop {
257 name: String,
258 },
259 Rename {
260 from: String,
261 to: String,
262 },
263 SetNotNull {
264 name: String,
265 },
266 DropNotNull {
267 name: String,
268 },
269 SetType {
270 name: String,
271 data_type: String,
272 },
273 SetDefault {
274 name: String,
275 default: Option<crate::analysis::expr_ir::ExprIr>,
276 },
277}
278
279#[allow(clippy::large_enum_variant)]
280#[derive(Debug, Clone, PartialEq)]
281pub enum RelationOverlay {
282 Present(RelationState),
283 Dropped,
284}