mod ddl;
mod graph;
pub use ddl::*;
pub use graph::*;
use ahash::AHashMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TableId(pub u32);
impl fmt::Display for TableId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TableId({})", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ColumnId(pub u16);
impl fmt::Display for ColumnId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ColumnId({})", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ColumnType {
Int,
BigInt,
Text,
Uuid,
Decimal,
DateTime,
Bool,
Other(String),
}
impl ColumnType {
pub fn from_sql_type(type_str: &str) -> Self {
let type_lower = type_str.to_lowercase();
let base_type = type_lower.split('(').next().unwrap_or(&type_lower).trim();
match base_type {
"int" | "integer" | "tinyint" | "smallint" | "mediumint" | "int4" | "int2" => {
ColumnType::Int
}
"serial" | "smallserial" => ColumnType::Int,
"bigint" | "int8" | "bigserial" => ColumnType::BigInt,
"char" | "varchar" | "text" | "tinytext" | "mediumtext" | "longtext" | "enum"
| "set" | "character" => ColumnType::Text,
"decimal" | "numeric" | "float" | "double" | "real" | "float4" | "float8" | "money" => {
ColumnType::Decimal
}
"date" | "datetime" | "timestamp" | "time" | "year" | "timestamptz" | "timetz"
| "interval" => ColumnType::DateTime,
"bool" | "boolean" => ColumnType::Bool,
"binary" | "varbinary" | "blob" | "bytea" => {
if type_lower.contains("16") {
ColumnType::Uuid
} else {
ColumnType::Other(type_str.to_string())
}
}
"uuid" => ColumnType::Uuid,
_ => ColumnType::Other(type_str.to_string()),
}
}
pub fn from_mysql_type(type_str: &str) -> Self {
Self::from_sql_type(type_str)
}
}
#[derive(Debug, Clone)]
pub struct Column {
pub name: String,
pub col_type: ColumnType,
pub ordinal: ColumnId,
pub is_primary_key: bool,
pub is_nullable: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexDef {
pub name: String,
pub columns: Vec<String>,
pub is_unique: bool,
pub index_type: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ForeignKey {
pub name: Option<String>,
pub columns: Vec<ColumnId>,
pub column_names: Vec<String>,
pub referenced_table: String,
pub referenced_columns: Vec<String>,
pub referenced_table_id: Option<TableId>,
}
#[derive(Debug, Clone)]
pub struct TableSchema {
pub name: String,
pub id: TableId,
pub columns: Vec<Column>,
pub primary_key: Vec<ColumnId>,
pub foreign_keys: Vec<ForeignKey>,
pub indexes: Vec<IndexDef>,
pub create_statement: Option<String>,
}
impl TableSchema {
pub fn new(name: String, id: TableId) -> Self {
Self {
name,
id,
columns: Vec::new(),
primary_key: Vec::new(),
foreign_keys: Vec::new(),
indexes: Vec::new(),
create_statement: None,
}
}
pub fn get_column(&self, name: &str) -> Option<&Column> {
self.columns
.iter()
.find(|c| c.name.eq_ignore_ascii_case(name))
}
pub fn get_column_id(&self, name: &str) -> Option<ColumnId> {
self.get_column(name).map(|c| c.ordinal)
}
pub fn column(&self, id: ColumnId) -> Option<&Column> {
self.columns.get(id.0 as usize)
}
pub fn is_pk_column(&self, col_id: ColumnId) -> bool {
self.primary_key.contains(&col_id)
}
}
#[derive(Debug)]
pub struct Schema {
pub tables: AHashMap<String, TableId>,
pub table_schemas: Vec<TableSchema>,
}
impl Schema {
pub fn new() -> Self {
Self {
tables: AHashMap::new(),
table_schemas: Vec::new(),
}
}
pub fn get_table_id(&self, name: &str) -> Option<TableId> {
if let Some(&id) = self.tables.get(name) {
return Some(id);
}
let name_lower = name.to_lowercase();
self.tables
.iter()
.find(|(k, _)| k.to_lowercase() == name_lower)
.map(|(_, &id)| id)
}
pub fn table(&self, id: TableId) -> Option<&TableSchema> {
self.table_schemas.get(id.0 as usize)
}
pub fn table_mut(&mut self, id: TableId) -> Option<&mut TableSchema> {
self.table_schemas.get_mut(id.0 as usize)
}
pub fn get_table(&self, name: &str) -> Option<&TableSchema> {
self.get_table_id(name).and_then(|id| self.table(id))
}
pub fn add_table(&mut self, mut schema: TableSchema) -> TableId {
let id = TableId(self.table_schemas.len() as u32);
schema.id = id;
self.tables.insert(schema.name.clone(), id);
self.table_schemas.push(schema);
id
}
pub fn resolve_foreign_keys(&mut self) {
let table_ids: AHashMap<String, TableId> = self.tables.clone();
for table in &mut self.table_schemas {
for fk in &mut table.foreign_keys {
fk.referenced_table_id = table_ids
.get(&fk.referenced_table)
.or_else(|| {
let lower = fk.referenced_table.to_lowercase();
table_ids
.iter()
.find(|(k, _)| k.to_lowercase() == lower)
.map(|(_, v)| v)
})
.copied();
}
}
}
pub fn len(&self) -> usize {
self.table_schemas.len()
}
pub fn is_empty(&self) -> bool {
self.table_schemas.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &TableSchema> {
self.table_schemas.iter()
}
}
impl Default for Schema {
fn default() -> Self {
Self::new()
}
}