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 self.columns.push(Column {
155 name: name.clone(),
156 data_type: data_type.clone(),
157 default: default.clone(),
158 is_nullable: !not_null,
159 avg_width: None,
160 default_expr_text: None,
161 type_modifier: None,
162 });
163 }
164 }
165 ColumnAction::Drop { name } => {
166 self.columns.retain(|c| c.name != *name);
167 }
168 ColumnAction::Rename { from, to } => {
169 if let Some(pos) = self.columns.iter().position(|c| c.name == *from)
170 && !self.columns.iter().any(|c| c.name == *to)
171 {
172 self.columns[pos].name = to.clone();
173 }
174 }
175 ColumnAction::SetNotNull { name } => {
176 if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
177 col.is_nullable = false;
178 }
179 }
180 ColumnAction::DropNotNull { name } => {
181 if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
182 col.is_nullable = true;
183 }
184 }
185 ColumnAction::SetType { name, data_type } => {
186 if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
187 col.data_type = Some(data_type.clone());
188 }
189 }
190 ColumnAction::SetDefault { name, default } => {
191 if let Some(col) = self.columns.iter_mut().find(|c| c.name == *name) {
192 col.default = default.clone();
193 }
194 }
195 }
196 }
197
198 pub fn has_column(&self, name: &str) -> bool {
199 self.columns.iter().any(|c| c.name == name)
200 }
201
202 pub fn get_column(&self, name: &str) -> Option<&Column> {
203 self.columns.iter().find(|c| c.name == name)
204 }
205
206 pub fn is_stale(&self) -> bool {
207 self.last_analyze.is_none() && self.last_autoanalyze.is_none()
208 }
209}
210
211#[derive(Debug, Clone, PartialEq)]
212pub enum ColumnAction {
213 Add {
214 name: String,
215 data_type: Option<String>,
216 not_null: bool,
217 default: Option<crate::analysis::expr_ir::ExprIr>,
218 },
219 Drop {
220 name: String,
221 },
222 Rename {
223 from: String,
224 to: String,
225 },
226 SetNotNull {
227 name: String,
228 },
229 DropNotNull {
230 name: String,
231 },
232 SetType {
233 name: String,
234 data_type: String,
235 },
236 SetDefault {
237 name: String,
238 default: Option<crate::analysis::expr_ir::ExprIr>,
239 },
240}
241
242#[allow(clippy::large_enum_variant)]
243#[derive(Debug, Clone, PartialEq)]
244pub enum RelationOverlay {
245 Present(RelationState),
246 Dropped,
247}