1use crate::model::aggregation::Aggregation;
2use crate::model::column::Column;
3use crate::model::column_type::ColumnType;
4use crate::model::constraint::Constraint;
5use crate::model::initial_data::InitialData;
6use crate::model::key::Key;
7use crate::model::relation::Relation;
8use crate::model::trigger::Trigger;
9use crate::model::types::{BooleanMode, KeyType, LockEscalation, TableOption};
10use std::fmt;
11
12#[derive(Debug, Clone)]
13pub struct Table {
14 schema_name: Option<String>,
15 name: String,
16 export_date_column: Option<String>,
17 lock_escalation: LockEscalation,
18 no_export: bool,
19 columns: Vec<Column>,
20 keys: Vec<Key>,
21 indexes: Vec<Key>,
22 relations: Vec<Relation>,
23 reverse_relations: Vec<Relation>,
24 triggers: Vec<Trigger>,
25 constraints: Vec<Constraint>,
26 initial_data: Vec<InitialData>,
27 options: Vec<TableOption>,
28 aggregations: Vec<Aggregation>,
29}
30
31impl Table {
32 #[allow(clippy::too_many_arguments)]
33 pub fn new<S: Into<String>>(
34 schema_name: Option<S>,
35 name: S,
36 export_date_column: Option<S>,
37 lock_escalation: LockEscalation,
38 no_export: bool,
39 columns: Vec<Column>,
40 keys: Vec<Key>,
41 indexes: Vec<Key>,
42 relations: Vec<Relation>,
43 triggers: Vec<Trigger>,
44 constraints: Vec<Constraint>,
45 initial_data: Vec<InitialData>,
46 options: Vec<TableOption>,
47 aggregations: Vec<Aggregation>,
48 ) -> Self {
49 Self {
50 schema_name: schema_name.map(|s| s.into()),
51 name: name.into(),
52 export_date_column: export_date_column.map(|s| s.into()),
53 lock_escalation,
54 no_export,
55 columns,
56 keys,
57 indexes,
58 relations,
59 reverse_relations: Vec::new(),
60 triggers,
61 constraints,
62 initial_data,
63 options,
64 aggregations,
65 }
66 }
67
68 pub fn schema_name(&self) -> Option<&str> {
69 self.schema_name.as_deref()
70 }
71
72 pub fn name(&self) -> &str {
73 &self.name
74 }
75
76 pub fn export_date_column(&self) -> Option<&str> {
77 self.export_date_column.as_deref()
78 }
79
80 pub fn lock_escalation(&self) -> LockEscalation {
81 self.lock_escalation
82 }
83
84 pub fn is_no_export(&self) -> bool {
85 self.no_export
86 }
87
88 pub fn columns(&self) -> &[Column] {
89 &self.columns
90 }
91
92 pub fn keys(&self) -> &[Key] {
93 &self.keys
94 }
95
96 pub fn indexes(&self) -> &[Key] {
97 &self.indexes
98 }
99
100 pub fn relations(&self) -> &[Relation] {
101 &self.relations
102 }
103
104 pub fn reverse_relations(&self) -> &[Relation] {
105 &self.reverse_relations
106 }
107
108 pub fn triggers(&self) -> &[Trigger] {
109 &self.triggers
110 }
111
112 pub fn constraints(&self) -> &[Constraint] {
113 &self.constraints
114 }
115
116 pub fn initial_data(&self) -> &[InitialData] {
117 &self.initial_data
118 }
119
120 pub fn options(&self) -> &[TableOption] {
121 &self.options
122 }
123
124 pub fn aggregations(&self) -> &[Aggregation] {
125 &self.aggregations
126 }
127
128 pub fn column(&self, column_name: &str) -> &Column {
129 let lower = column_name.to_lowercase();
130 self.columns
131 .iter()
132 .find(|c| c.name().eq_ignore_ascii_case(&lower))
133 .unwrap_or_else(|| {
134 panic!(
135 "Unable to locate a column with the name '{}' in table '{}'",
136 column_name, self.name
137 )
138 })
139 }
140
141 pub fn primary_key(&self) -> Option<&Key> {
142 self.keys.iter().find(|k| k.key_type() == KeyType::Primary)
143 }
144
145 pub fn has_column(&self, column_name: &str) -> bool {
146 let lower = column_name.to_lowercase();
147 self.columns
148 .iter()
149 .any(|c| c.name().eq_ignore_ascii_case(&lower))
150 }
151
152 pub fn identity_column(&self) -> Option<&Column> {
153 self.columns.iter().find(|c| {
154 let t = c.column_type();
155 matches!(t, ColumnType::Sequence | ColumnType::LongSequence)
156 })
157 }
158
159 pub fn primary_key_columns(&self) -> Option<Vec<String>> {
160 self.primary_key()
161 .map(|k| k.columns().iter().map(|kc| kc.name().to_string()).collect())
162 }
163
164 pub fn has_option(&self, option: TableOption) -> bool {
165 self.options.iter().any(|o| *o == option)
166 }
167
168 pub fn has_column_constraints(&self, boolean_mode: BooleanMode) -> bool {
169 self.columns
170 .iter()
171 .any(|c| c.needs_check_constraints(boolean_mode))
172 }
173
174 pub fn columns_with_check_constraints(&self, boolean_mode: BooleanMode) -> Vec<Column> {
175 self.columns
176 .iter()
177 .filter(|c| c.needs_check_constraints(boolean_mode))
178 .cloned()
179 .collect()
180 }
181
182 pub fn column_relation(&self, column: &Column) -> Option<&Relation> {
183 let name = column.name();
184 self.relations
185 .iter()
186 .find(|r| r.from_column_name().eq_ignore_ascii_case(name))
187 }
188
189 pub fn fully_qualified_table_name(&self) -> String {
190 match self.schema_name() {
191 Some(schema_name) => format!("{}.{}", schema_name, self.name()),
192 None => self.name().to_string(),
193 }
194 }
195
196 pub fn add_reverse_relation(&mut self, relation: Relation) {
197 self.reverse_relations.push(relation);
198 }
199}
200
201impl fmt::Display for Table {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 match &self.schema_name {
204 Some(schema) => write!(f, "{}.{}", schema, self.name),
205 None => write!(f, "{}", self.name),
206 }
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use crate::model::types::LockEscalation;
214
215 fn sample_table() -> Table {
216 Table::new(
217 Some("schema"),
218 "table",
219 Option::<&str>::None,
220 LockEscalation::Auto,
221 false,
222 Vec::new(),
223 Vec::new(),
224 Vec::new(),
225 Vec::new(),
226 Vec::new(),
227 Vec::new(),
228 Vec::new(),
229 Vec::new(),
230 Vec::new(),
231 )
232 }
233
234 #[test]
235 fn constructor_and_basic_getters() {
236 let t = sample_table();
237 assert_eq!(t.schema_name().unwrap(), "schema");
238 assert_eq!(t.name(), "table");
239 assert_eq!(t.export_date_column(), None);
240 assert_eq!(t.lock_escalation(), LockEscalation::Auto);
241 assert!(!t.is_no_export());
242 assert_eq!(format!("{}", t), "schema.table");
243 }
244}