1
0
magic-sql-gen/src/magicdraw_parser/mod.rs

280 lines
8.3 KiB
Rust

mod utils;
mod uml_model_parser;
mod ddl_parser;
mod sql_types_parser;
use serde::{Serialize, Deserialize};
use std::{io::{Read, Seek}, collections::HashSet, fmt::Display};
use anyhow::{Result, Context};
use lazy_regex::regex_captures;
use zip::ZipArchive;
use crate::unwrap_opt_continue;
use self::{uml_model_parser::{parse_uml_model, UMLModel, UMLClass, UMLModifier, UMLNullableModifier, UMLPrimaryKeyModifier, UMLTypeModifier, UMLForeignKeyModifier}, ddl_parser::parse_ddl_scripts, sql_types_parser::{parse_sql_types, SQLTypeName}};
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum SQLType {
Int,
Decimal,
Date,
Time,
Datetime,
Float,
Bool,
Char(u8),
Varchar(u16),
}
impl Display for SQLType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SQLType::Int => write!(f, "INT"),
SQLType::Decimal => write!(f, "DECIMAL"),
SQLType::Date => write!(f, "DATE"),
SQLType::Time => write!(f, "TIME"),
SQLType::Datetime => write!(f, "DATETIME"),
SQLType::Float => write!(f, "FLOAT"),
SQLType::Bool => write!(f, "BOOL"),
SQLType::Char(size) => write!(f, "CHAR({})", size),
SQLType::Varchar(size) => write!(f, "VARCHAR({})", size),
}
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum SQLCheckConstraint {
OneOf(Vec<String>),
Freeform(String)
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct SQLColumn {
pub name: String,
pub sql_type: SQLType,
pub primary_key: bool,
pub nullable: bool,
pub foreign_key: Option<(String, String)>,
pub check_constraint: Option<SQLCheckConstraint>
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct SQLTable {
pub name: String,
pub columns: Vec<SQLColumn>,
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct SQLTableCollection {
pub tables: Vec<SQLTable>
}
fn find_class_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UMLClass> {
for model in models {
for package in &model.packages {
if let Some(class) = package.classess.iter().find(|t| t.id.eq(id)) {
return Some(class);
}
}
}
None
}
fn is_nullabe(modifiers: &[UMLModifier], property: &str) -> bool {
for modifier in modifiers {
if let UMLModifier::Nullable(UMLNullableModifier { property_id, nullable }) = modifier {
if property_id.eq(property) {
return *nullable;
}
}
}
false
}
fn is_primary_key(modifiers: &[UMLModifier], property: &str) -> bool {
for modifier in modifiers {
if let UMLModifier::PirmaryKey(UMLPrimaryKeyModifier { property_id }) = modifier {
if property_id.eq(property) {
return true
}
}
}
false
}
fn get_type_modifier<'a>(modifiers: &'a [UMLModifier], property: &str) -> Option<&'a str> {
for modifier in modifiers {
if let UMLModifier::Type(UMLTypeModifier { property_id, modifier }) = modifier {
if property_id.eq(property) {
return Some(modifier)
}
}
}
None
}
fn get_foreign_key_constraint<'a>(modifiers: &'a [UMLModifier], from_id: &str) -> Option<&'a str> {
for modifier in modifiers {
if let UMLModifier::ForeignKey(UMLForeignKeyModifier { from_property_id, to_property_id }) = modifier {
if from_property_id.eq(from_id) {
return Some(&to_property_id)
}
}
}
None
}
fn get_foreign_key(modifiers: &[UMLModifier], classess: &[&UMLClass], property: &str) -> Result<Option<(String, String)>> {
let to_id = get_foreign_key_constraint(modifiers, property);
if to_id.is_none() {
return Ok(None)
}
let to_id = to_id.unwrap();
for class in classess {
for property in &class.properties {
if property.id.eq(to_id) {
let property_name = property.name.clone().context("Missing property name")?;
let class_name = class.name.clone().context("Missing class name")?;
return Ok(Some((class_name, property_name)));
}
}
}
Ok(None)
}
fn parse_check_constraint(str: &str) -> SQLCheckConstraint {
fn try_parse_one_of(str: &str) -> Option<SQLCheckConstraint> {
let (_, inner) = regex_captures!(r#"^in \((.+)\)$"#, str)?;
let mut variants = vec![];
for part in inner.split(", ") {
let (_, variant) = regex_captures!(r#"^'(.+)'$"#, part)?;
variants.push(variant.to_string());
}
Some(SQLCheckConstraint::OneOf(variants))
}
try_parse_one_of(str)
.unwrap_or(SQLCheckConstraint::Freeform(str.to_string()))
}
// TODO: Refactor this function, less nesting would be good
fn get_sql_check_constraint<'a>(models: &'a [UMLModel], property_name: &str) -> Option<SQLCheckConstraint> {
for model in models {
for package in &model.packages {
for class in &package.classess {
for constraint in &class.constraints {
let prop_name = unwrap_opt_continue!(&constraint.property_name);
let body = unwrap_opt_continue!(&constraint.body);
if prop_name.eq(property_name) && constraint.body.is_some() {
return Some(parse_check_constraint(body));
}
}
}
}
}
None
}
fn get_sql_type(modifiers: &[UMLModifier], type_name: SQLTypeName, property: &str) -> Result<SQLType> {
Ok(match type_name {
SQLTypeName::Int => SQLType::Int,
SQLTypeName::Date => SQLType::Date,
SQLTypeName::Float => SQLType::Float,
SQLTypeName::Bool => SQLType::Bool,
SQLTypeName::Decimal => SQLType::Decimal,
SQLTypeName::Char => {
if let Some(type_modifier) = get_type_modifier(modifiers, property) {
let (_, size) = regex_captures!(r#"^\((\d+)\)$"#, type_modifier)
.context("Type modifier doesn't match format")?;
SQLType::Char(size.parse()?)
} else {
// TODO: Add better error message to say which table is missing type modifier
// For now just pick a defautl arbitrarily
SQLType::Char(31)
}
},
SQLTypeName::Varchar => {
if let Some(type_modifier) = get_type_modifier(modifiers, property) {
let (_, size) = regex_captures!(r#"^\((\d+)\)$"#, type_modifier)
.context("Type modifier doesn't match format")?;
SQLType::Varchar(size.parse()?)
} else {
// TODO: Add better error message to say which table is missing type modifier
// For now just pick a defautl arbitrarily
SQLType::Varchar(255)
}
},
})
}
fn get_used_types<'a>(models: &'a [UMLModel]) -> HashSet<&'a String> {
models.iter()
.flat_map(|model| &model.packages)
.flat_map(|package| &package.classess)
.flat_map(|class| &class.properties)
.filter_map(|property| property.type_href.as_ref())
.collect::<HashSet<_>>()
}
pub fn parse_project<R: Read + Seek>(project_file: R) -> Result<Vec<SQLTableCollection>> {
let mut zip = ZipArchive::new(project_file).unwrap();
let (models, modifiers) = parse_uml_model(&mut zip)?;
let ddl_scripts = parse_ddl_scripts(&mut zip)?;
let sql_type_names = parse_sql_types(&mut zip, &get_used_types(&models))?;
let mut collections = vec![];
for ddl_project in ddl_scripts {
for ddl_script in ddl_project.scripts {
let mut tables = vec![];
let model_properties = ddl_script.classess.iter()
.flat_map(|class| class.property_ids.iter().map(|prop| (&class.class_id, prop)))
.collect::<Vec<_>>();
let mut model_classess = vec![];
for ddl_class in &ddl_script.classess {
let model_class = find_class_by_id(&models, &ddl_class.class_id).context("UML class not found")?;
model_classess.push(model_class);
}
for (ddl_class, model_class) in ddl_script.classess.iter().zip(&model_classess) {
let name = model_class.name.clone().context("UML class name not found")?;
let mut columns = vec![];
for property_id in &ddl_class.property_ids {
let property = model_class.properties.iter().find(|p| p.id.eq(property_id)).context("Property not found")?;
let prop_name = unwrap_opt_continue!(&property.name).clone();
let type_href = unwrap_opt_continue!(&property.type_href);
let type_name = sql_type_names.get(type_href).context("Proerty type name conversion not found")?;
let check_constraint = get_sql_check_constraint(&models, &prop_name);
let foreign_key = get_foreign_key(&modifiers, &model_classess, property_id)?;
columns.push(SQLColumn {
name: prop_name,
sql_type: get_sql_type(&modifiers, *type_name, property_id)?,
primary_key: is_primary_key(&modifiers, property_id),
nullable: is_nullabe(&modifiers, property_id),
foreign_key,
check_constraint,
})
}
tables.push(SQLTable {
name,
columns
})
}
collections.push(SQLTableCollection { tables })
}
}
Ok(collections)
}