1use std::collections::HashSet;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5pub enum TableKind {
6 #[default]
8 Table,
9 View,
11 MaterializedView,
13}
14
15#[derive(Debug, Clone, Default)]
17pub struct Table {
18 pub schema: String,
19 pub name: String,
20 pub kind: TableKind,
21 pub columns: Vec<Column>,
22 pub primary_keys: Vec<String>,
23 pub foreign_keys: Vec<ForeignKey>,
24 pub not_null_cols: HashSet<String>,
25 pub unique_constraints: Vec<UniqueConstraint>,
26 pub check_constraints: Vec<CheckConstraint>,
27 pub indexes: Vec<IndexInfo>,
28}
29
30impl Table {
31 #[must_use]
33 pub fn key(&self) -> (&str, &str) {
34 (&self.schema, &self.name)
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct Column {
41 pub name: String,
42 pub data_type: String,
43 pub default: Option<String>,
46}
47
48#[derive(Debug, Clone)]
50pub struct ForeignKey {
51 pub name: String,
52 pub from_columns: Vec<String>,
53 pub to_schema: String,
54 pub to_table: String,
55 pub to_columns: Vec<String>,
56}
57
58#[derive(Debug, Clone)]
60pub struct UniqueConstraint {
61 pub name: String,
62 pub columns: Vec<String>,
63}
64
65#[derive(Debug, Clone)]
67pub struct CheckConstraint {
68 pub name: String,
69 pub definition: String,
70}
71
72#[derive(Debug, Clone)]
74pub struct IndexInfo {
75 pub name: String,
76 pub columns: Vec<String>,
77 pub is_unique: bool,
78}
79
80pub const SYSTEM_SCHEMAS: [&str; 2] = ["information_schema", "pg_catalog"];
82
83#[must_use]
91pub fn filter_tables(
92 tables: Vec<Table>,
93 schemas: &[&str],
94 tables_filter: &[&str],
95 ignore_tables: &[&str],
96) -> Vec<Table> {
97 tables
98 .into_iter()
99 .filter(|t| {
100 let schema_ok = if schemas.is_empty() {
101 !SYSTEM_SCHEMAS.contains(&t.schema.as_str())
102 } else {
103 schemas.contains(&t.schema.as_str())
104 };
105
106 let table_ok = tables_filter.is_empty() || tables_filter.contains(&t.name.as_str());
107 let not_ignored = !ignore_tables.contains(&t.name.as_str());
108
109 schema_ok && table_ok && not_ignored
110 })
111 .collect()
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 fn table(schema: &str, name: &str) -> Table {
119 Table {
120 schema: schema.to_string(),
121 name: name.to_string(),
122 ..Table::default()
123 }
124 }
125
126 fn view(schema: &str, name: &str, kind: TableKind) -> Table {
127 Table {
128 schema: schema.to_string(),
129 name: name.to_string(),
130 kind,
131 ..Table::default()
132 }
133 }
134
135 #[test]
136 fn filter_tables_excludes_system_schemas_by_default() {
137 let tables = vec![
138 table("public", "users"),
139 table("pg_catalog", "pg_class"),
140 table("information_schema", "columns"),
141 ];
142
143 let result = filter_tables(tables, &[], &[], &[]);
144
145 assert_eq!(result.len(), 1);
146 assert_eq!(result[0].name, "users");
147 }
148
149 #[test]
150 fn filter_tables_keeps_only_requested_schemas() {
151 let tables = vec![table("public", "users"), table("extended", "audit")];
152
153 let result = filter_tables(tables, &["extended"], &[], &[]);
154
155 assert_eq!(result.len(), 1);
156 assert_eq!(result[0].schema, "extended");
157 }
158
159 #[test]
160 fn filter_tables_combines_schema_and_table_filters() {
161 let tables = vec![
162 table("public", "users"),
163 table("public", "orders"),
164 table("extended", "users"),
165 ];
166
167 let result = filter_tables(tables, &["public"], &["users"], &[]);
168
169 assert_eq!(result.len(), 1);
170 assert_eq!(result[0].key(), ("public", "users"));
171 }
172
173 #[test]
174 fn filter_tables_excludes_ignored_tables() {
175 let tables = vec![
176 table("public", "users"),
177 table("public", "logs"),
178 table("extended", "logs"),
179 ];
180
181 let result = filter_tables(tables, &[], &[], &["logs"]);
182
183 assert_eq!(result.len(), 1);
184 assert_eq!(result[0].name, "users");
185 }
186
187 #[test]
188 fn filter_tables_treats_views_and_materialized_views_like_tables() {
189 let tables = vec![
190 table("public", "users"),
191 view("public", "active_users", TableKind::View),
192 view("public", "users_summary", TableKind::MaterializedView),
193 view("extended", "audit_view", TableKind::View),
194 ];
195
196 let result = filter_tables(tables.clone(), &["public"], &["active_users"], &[]);
199 assert_eq!(result.len(), 1);
200 assert_eq!(result[0].key(), ("public", "active_users"));
201 assert_eq!(result[0].kind, TableKind::View);
202
203 let result = filter_tables(tables, &["public"], &[], &["users_summary"]);
206 let names: Vec<&str> = result.iter().map(|t| t.name.as_str()).collect();
207 assert_eq!(names, vec!["users", "active_users"]);
208 }
209
210 #[test]
211 fn filter_tables_combines_schema_and_ignore_filters() {
212 let tables = vec![
213 table("public", "users"),
214 table("public", "logs"),
215 table("extended", "logs"),
216 ];
217
218 let result = filter_tables(tables, &["public"], &[], &["logs"]);
219
220 assert_eq!(result.len(), 1);
221 assert_eq!(result[0].key(), ("public", "users"));
222 }
223}