1mod ddl;
10mod graph;
11
12pub use ddl::*;
13pub use graph::*;
14
15use ahash::AHashMap;
16use std::fmt;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct TableId(pub u32);
21
22impl fmt::Display for TableId {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "TableId({})", self.0)
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub struct ColumnId(pub u16);
31
32impl fmt::Display for ColumnId {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 write!(f, "ColumnId({})", self.0)
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum ColumnType {
41 Int,
43 BigInt,
45 Text,
47 Uuid,
49 Decimal,
51 DateTime,
53 Bool,
55 Other(String),
57}
58
59impl ColumnType {
60 pub fn from_sql_type(type_str: &str) -> Self {
63 let type_lower = type_str.to_lowercase();
64 let base_type = type_lower.split('(').next().unwrap_or(&type_lower).trim();
65
66 match base_type {
67 "int" | "integer" | "tinyint" | "smallint" | "mediumint" | "int4" | "int2" => {
69 ColumnType::Int
70 }
71 "serial" | "smallserial" => ColumnType::Int,
73 "bigint" | "int8" | "bigserial" => ColumnType::BigInt,
74 "char" | "varchar" | "text" | "tinytext" | "mediumtext" | "longtext" | "enum"
76 | "set" | "character" => ColumnType::Text,
77 "decimal" | "numeric" | "float" | "double" | "real" | "float4" | "float8" | "money" => {
79 ColumnType::Decimal
80 }
81 "date" | "datetime" | "timestamp" | "time" | "year" | "timestamptz" | "timetz"
83 | "interval" => ColumnType::DateTime,
84 "bool" | "boolean" => ColumnType::Bool,
86 "binary" | "varbinary" | "blob" | "bytea" => {
88 if type_lower.contains("16") {
90 ColumnType::Uuid
91 } else {
92 ColumnType::Other(type_str.to_string())
93 }
94 }
95 "uuid" => ColumnType::Uuid,
96 _ => ColumnType::Other(type_str.to_string()),
97 }
98 }
99
100 pub fn from_mysql_type(type_str: &str) -> Self {
102 Self::from_sql_type(type_str)
103 }
104}
105
106#[derive(Debug, Clone)]
108pub struct Column {
109 pub name: String,
111 pub col_type: ColumnType,
113 pub ordinal: ColumnId,
115 pub is_primary_key: bool,
117 pub is_nullable: bool,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct IndexDef {
124 pub name: String,
126 pub columns: Vec<String>,
128 pub is_unique: bool,
130 pub index_type: Option<String>,
132}
133
134#[derive(Debug, Clone)]
136pub struct ForeignKey {
137 pub name: Option<String>,
139 pub columns: Vec<ColumnId>,
141 pub column_names: Vec<String>,
143 pub referenced_table: String,
145 pub referenced_columns: Vec<String>,
147 pub referenced_table_id: Option<TableId>,
149}
150
151#[derive(Debug, Clone)]
153pub struct TableSchema {
154 pub name: String,
156 pub id: TableId,
158 pub columns: Vec<Column>,
160 pub primary_key: Vec<ColumnId>,
162 pub foreign_keys: Vec<ForeignKey>,
164 pub indexes: Vec<IndexDef>,
166 pub create_statement: Option<String>,
168}
169
170impl TableSchema {
171 pub fn new(name: String, id: TableId) -> Self {
173 Self {
174 name,
175 id,
176 columns: Vec::new(),
177 primary_key: Vec::new(),
178 foreign_keys: Vec::new(),
179 indexes: Vec::new(),
180 create_statement: None,
181 }
182 }
183
184 pub fn get_column(&self, name: &str) -> Option<&Column> {
186 self.columns
187 .iter()
188 .find(|c| c.name.eq_ignore_ascii_case(name))
189 }
190
191 pub fn get_column_id(&self, name: &str) -> Option<ColumnId> {
193 self.get_column(name).map(|c| c.ordinal)
194 }
195
196 pub fn column(&self, id: ColumnId) -> Option<&Column> {
198 self.columns.get(id.0 as usize)
199 }
200
201 pub fn is_pk_column(&self, col_id: ColumnId) -> bool {
203 self.primary_key.contains(&col_id)
204 }
205
206 pub fn fk_column_ids(&self) -> Vec<ColumnId> {
208 self.foreign_keys
209 .iter()
210 .flat_map(|fk| fk.columns.iter().copied())
211 .collect()
212 }
213}
214
215#[derive(Debug)]
217pub struct Schema {
218 pub tables: AHashMap<String, TableId>,
220 pub table_schemas: Vec<TableSchema>,
222}
223
224impl Schema {
225 pub fn new() -> Self {
227 Self {
228 tables: AHashMap::new(),
229 table_schemas: Vec::new(),
230 }
231 }
232
233 pub fn get_table_id(&self, name: &str) -> Option<TableId> {
235 if let Some(&id) = self.tables.get(name) {
237 return Some(id);
238 }
239 let name_lower = name.to_lowercase();
241 self.tables
242 .iter()
243 .find(|(k, _)| k.to_lowercase() == name_lower)
244 .map(|(_, &id)| id)
245 }
246
247 pub fn table(&self, id: TableId) -> Option<&TableSchema> {
249 self.table_schemas.get(id.0 as usize)
250 }
251
252 pub fn table_mut(&mut self, id: TableId) -> Option<&mut TableSchema> {
254 self.table_schemas.get_mut(id.0 as usize)
255 }
256
257 pub fn get_table(&self, name: &str) -> Option<&TableSchema> {
259 self.get_table_id(name).and_then(|id| self.table(id))
260 }
261
262 pub fn add_table(&mut self, mut schema: TableSchema) -> TableId {
264 let id = TableId(self.table_schemas.len() as u32);
265 schema.id = id;
266 self.tables.insert(schema.name.clone(), id);
267 self.table_schemas.push(schema);
268 id
269 }
270
271 pub fn resolve_foreign_keys(&mut self) {
273 let table_ids: AHashMap<String, TableId> = self.tables.clone();
274
275 for table in &mut self.table_schemas {
276 for fk in &mut table.foreign_keys {
277 fk.referenced_table_id = table_ids
278 .get(&fk.referenced_table)
279 .or_else(|| {
280 let lower = fk.referenced_table.to_lowercase();
282 table_ids
283 .iter()
284 .find(|(k, _)| k.to_lowercase() == lower)
285 .map(|(_, v)| v)
286 })
287 .copied();
288 }
289 }
290 }
291
292 pub fn len(&self) -> usize {
294 self.table_schemas.len()
295 }
296
297 pub fn is_empty(&self) -> bool {
299 self.table_schemas.is_empty()
300 }
301
302 pub fn iter(&self) -> impl Iterator<Item = &TableSchema> {
304 self.table_schemas.iter()
305 }
306}
307
308impl Default for Schema {
309 fn default() -> Self {
310 Self::new()
311 }
312}