1
0

reformat code

This commit is contained in:
Rokas Puzonas 2023-03-05 13:47:52 +02:00
parent bb52e3e84f
commit 004560ca3d
12 changed files with 686 additions and 449 deletions

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
hard_tabs = true

View File

@ -1,18 +1,18 @@
use std::cell::RefCell; use gloo::console::{console, console_dbg};
use std::io::Cursor;
use std::collections::{HashMap, self};
use std::rc::Rc;
use gloo::console::{console_dbg, console};
use gloo::file::callbacks::FileReader; use gloo::file::callbacks::FileReader;
use gloo::file::File; use gloo::file::File;
use gloo::storage::{LocalStorage, Storage}; use gloo::storage::{LocalStorage, Storage};
use std::cell::RefCell;
use std::collections::{self, HashMap};
use std::io::Cursor;
use std::rc::Rc;
use web_sys::{DragEvent, Event, FileList, HtmlInputElement, MouseEvent}; use web_sys::{DragEvent, Event, FileList, HtmlInputElement, MouseEvent};
use yew::html::TargetCast; use yew::html::TargetCast;
use yew::{html, Callback, Component, Context, Html}; use yew::{html, Callback, Component, Context, Html};
use crate::generate_sql::{SQLValueGuess, generate_table_guessess, generate_fake_entries};
use crate::magicdraw_parser::{parse_project, SQLTableCollection, SQLTable};
use crate::components::sql_column_info::SQLTableColumnInfo; use crate::components::sql_column_info::SQLTableColumnInfo;
use crate::generate_sql::{generate_fake_entries, generate_table_guessess, SQLValueGuess};
use crate::magicdraw_parser::{parse_project, SQLTable, SQLTableCollection};
const COLLECTION_STORE_KEY: &str = "current_collection"; const COLLECTION_STORE_KEY: &str = "current_collection";
const DEFAULT_ROWS_PER_TABLE: u32 = 20; const DEFAULT_ROWS_PER_TABLE: u32 = 20;
@ -37,7 +37,7 @@ pub struct App {
currently_shown_table: usize, currently_shown_table: usize,
all_good_confirmed: bool, all_good_confirmed: bool,
generated_sql: Option<String>, generated_sql: Option<String>,
rows_per_table: u32 rows_per_table: u32,
} }
impl Component for App { impl Component for App {
@ -63,7 +63,7 @@ impl Component for App {
all_good_confirmed: true, // TODO: make this false, by default all_good_confirmed: true, // TODO: make this false, by default
generated_sql: None, generated_sql: None,
current_guessess, current_guessess,
rows_per_table: DEFAULT_ROWS_PER_TABLE rows_per_table: DEFAULT_ROWS_PER_TABLE,
} }
} }
@ -93,16 +93,13 @@ impl Component for App {
gloo::file::callbacks::read_as_bytes(&file, move |res| { gloo::file::callbacks::read_as_bytes(&file, move |res| {
// TODO: show error message // TODO: show error message
link.send_message(Msg::Loaded( link.send_message(Msg::Loaded(file_name, res.expect("failed to read file")))
file_name,
res.expect("failed to read file"),
))
}) })
}; };
self.active_readers.insert(file_name, task); self.active_readers.insert(file_name, task);
true true
}, }
Msg::Noop => false, Msg::Noop => false,
Msg::UpdateCurrentProject(collection) => { Msg::UpdateCurrentProject(collection) => {
if let Some(collection) = collection { if let Some(collection) = collection {
@ -115,38 +112,40 @@ impl Component for App {
let guess = generate_table_guessess(table); let guess = generate_table_guessess(table);
self.current_guessess.push(Rc::new(RefCell::new(guess))); self.current_guessess.push(Rc::new(RefCell::new(guess)));
} }
self.current_collection = Some(collection.tables.into_iter().map(Rc::new).collect()); self.current_collection =
Some(collection.tables.into_iter().map(Rc::new).collect());
} else { } else {
LocalStorage::delete(COLLECTION_STORE_KEY); LocalStorage::delete(COLLECTION_STORE_KEY);
self.current_collection = None self.current_collection = None
} }
true true
}, }
Msg::ShowNextTable => { Msg::ShowNextTable => {
if let Some(collection) = &self.current_collection { if let Some(collection) = &self.current_collection {
self.currently_shown_table = (self.currently_shown_table + 1).min(collection.len()-1); self.currently_shown_table =
(self.currently_shown_table + 1).min(collection.len() - 1);
return true; return true;
} }
false false
}, }
Msg::ShowPrevTable => { Msg::ShowPrevTable => {
if self.currently_shown_table > 0 { if self.currently_shown_table > 0 {
self.currently_shown_table = self.currently_shown_table - 1; self.currently_shown_table = self.currently_shown_table - 1;
return true; return true;
} }
false false
}, }
Msg::AllGoodConfirmation => { Msg::AllGoodConfirmation => {
self.all_good_confirmed = true; self.all_good_confirmed = true;
true true
}, }
Msg::UpdateGenarator(column, generator) => { Msg::UpdateGenarator(column, generator) => {
let mut guessess = self.current_guessess[self.currently_shown_table].borrow_mut(); let mut guessess = self.current_guessess[self.currently_shown_table].borrow_mut();
let entry = guessess.get_mut(&column).unwrap(); let entry = guessess.get_mut(&column).unwrap();
*entry = generator; *entry = generator;
true true
}, }
Msg::GenerateSQL => { Msg::GenerateSQL => {
let tables = self.current_collection.as_ref().unwrap(); let tables = self.current_collection.as_ref().unwrap();
let guessess = self.current_guessess.iter().map(|v| v.borrow()).collect(); let guessess = self.current_guessess.iter().map(|v| v.borrow()).collect();
@ -157,7 +156,7 @@ impl Component for App {
self.generated_sql = None self.generated_sql = None
} }
true true
}, }
Msg::UpdateRowsPerTable(rows_per_table) => { Msg::UpdateRowsPerTable(rows_per_table) => {
self.rows_per_table = rows_per_table; self.rows_per_table = rows_per_table;
false false

View File

@ -1,16 +1,17 @@
use std::{collections::HashMap, str::FromStr}; use std::{collections::HashMap, str::FromStr};
use yew::{Html, html, Callback, TargetCast, AttrValue};
use web_sys::{Event, HtmlInputElement}; use web_sys::{Event, HtmlInputElement};
use yew::{html, AttrValue, Callback, Html, TargetCast};
use crate::{generate_sql::{SQLValueGuess, SQLStringValueGuess, SQLBoolValueGuess, SQLTimeValueGuess, SQLIntValueGuess}, magicdraw_parser::{SQLColumn, SQLCheckConstraint}}; use crate::{
generate_sql::{
SQLBoolValueGuess, SQLIntValueGuess, SQLStringValueGuess, SQLTimeValueGuess, SQLValueGuess,
},
magicdraw_parser::{SQLCheckConstraint, SQLColumn},
};
fn show_dropdown_picker( fn show_dropdown_picker(selected: &str, options: &[AttrValue], onchange: Callback<String>) -> Html {
selected: &str, html! {
options: &[AttrValue],
onchange: Callback<String>
) -> Html {
html!{
<select onchange={Callback::from(move |e: Event| { <select onchange={Callback::from(move |e: Event| {
let value = e.target_unchecked_into::<HtmlInputElement>().value(); let value = e.target_unchecked_into::<HtmlInputElement>().value();
onchange.emit(value); onchange.emit(value);
@ -27,24 +28,30 @@ fn show_dropdown_picker(
fn show_enum_dropdown<T: PartialEq + Clone + 'static>( fn show_enum_dropdown<T: PartialEq + Clone + 'static>(
selected: &T, selected: &T,
options: HashMap<AttrValue, T>, options: HashMap<AttrValue, T>,
onchange: Callback<T> onchange: Callback<T>,
) -> Html { ) -> Html {
let keys = options.keys().map(AttrValue::clone).collect::<Vec<_>>(); let keys = options.keys().map(AttrValue::clone).collect::<Vec<_>>();
let guess_str = options.iter().find(|(_, v)| v.eq(&selected)).unwrap().0.clone(); let guess_str = options
.iter()
.find(|(_, v)| v.eq(&selected))
.unwrap()
.0
.clone();
show_dropdown_picker(&guess_str, &keys, onchange.reform(move |value_str: String| { show_dropdown_picker(
options.get(value_str.as_str()).unwrap().clone() &guess_str,
})) &keys,
onchange.reform(move |value_str: String| options.get(value_str.as_str()).unwrap().clone()),
)
} }
fn show_range_picker<T: FromStr + ToString + Clone + 'static>( fn show_range_picker<T: FromStr + ToString + Clone + 'static>(
min: T, min: T,
max: T, max: T,
default_min: T, default_min: T,
default_max: T, default_max: T,
onchange: Callback<(T, T)>, onchange: Callback<(T, T)>,
) -> Html { ) -> Html {
let onchange_min = { let onchange_min = {
let onchange = onchange.clone(); let onchange = onchange.clone();
let default_min = default_min.clone(); let default_min = default_min.clone();
@ -91,13 +98,13 @@ fn show_range_picker<T: FromStr + ToString + Clone + 'static>(
pub fn generator_picker( pub fn generator_picker(
column: &SQLColumn, column: &SQLColumn,
value: &SQLValueGuess, value: &SQLValueGuess,
onchange: Callback<SQLValueGuess> onchange: Callback<SQLValueGuess>,
) -> Html { ) -> Html {
// TODO: Refacotr 'time', 'datetime', and 'date'. They are very similar // TODO: Refacotr 'time', 'datetime', and 'date'. They are very similar
match value { match value {
SQLValueGuess::Int(guess) => { SQLValueGuess::Int(guess) => {
if column.primary_key { if column.primary_key {
return html!("Auto increment") return html!("Auto increment");
} }
let mut min = 0; let mut min = 0;
@ -108,59 +115,73 @@ pub fn generator_picker(
} }
// TODO: Disallow entering floating point numbers // TODO: Disallow entering floating point numbers
show_range_picker(min, max, 0, 100, onchange.reform(|(min, max)| { show_range_picker(
SQLValueGuess::Int(SQLIntValueGuess::Range(min, max)) min,
})) max,
}, 0,
SQLValueGuess::Float(min, max) => { 100,
show_range_picker(*min, *max, 0.0, 100.0, onchange.reform(|(min, max)| { onchange.reform(|(min, max)| SQLValueGuess::Int(SQLIntValueGuess::Range(min, max))),
SQLValueGuess::Float(min, max) )
})) }
}, SQLValueGuess::Float(min, max) => show_range_picker(
*min,
*max,
0.0,
100.0,
onchange.reform(|(min, max)| SQLValueGuess::Float(min, max)),
),
SQLValueGuess::Date(guess) => { SQLValueGuess::Date(guess) => {
let options = HashMap::from([ let options = HashMap::from([
("Now".into() , SQLTimeValueGuess::Now), ("Now".into(), SQLTimeValueGuess::Now),
("Future".into(), SQLTimeValueGuess::Future), ("Future".into(), SQLTimeValueGuess::Future),
("Past".into() , SQLTimeValueGuess::Past), ("Past".into(), SQLTimeValueGuess::Past),
]); ]);
show_enum_dropdown(guess, options, onchange.reform(|enum_value| { show_enum_dropdown(
SQLValueGuess::Date(enum_value) guess,
})) options,
}, onchange.reform(|enum_value| SQLValueGuess::Date(enum_value)),
)
}
SQLValueGuess::Time(guess) => { SQLValueGuess::Time(guess) => {
let options = HashMap::from([ let options = HashMap::from([
("Now".into() , SQLTimeValueGuess::Now), ("Now".into(), SQLTimeValueGuess::Now),
("Future".into(), SQLTimeValueGuess::Future), ("Future".into(), SQLTimeValueGuess::Future),
("Past".into() , SQLTimeValueGuess::Past), ("Past".into(), SQLTimeValueGuess::Past),
]); ]);
show_enum_dropdown(guess, options, onchange.reform(|enum_value| { show_enum_dropdown(
SQLValueGuess::Time(enum_value) guess,
})) options,
}, onchange.reform(|enum_value| SQLValueGuess::Time(enum_value)),
)
}
SQLValueGuess::Datetime(guess) => { SQLValueGuess::Datetime(guess) => {
let options = HashMap::from([ let options = HashMap::from([
("Now".into() , SQLTimeValueGuess::Now), ("Now".into(), SQLTimeValueGuess::Now),
("Future".into(), SQLTimeValueGuess::Future), ("Future".into(), SQLTimeValueGuess::Future),
("Past".into() , SQLTimeValueGuess::Past), ("Past".into(), SQLTimeValueGuess::Past),
]); ]);
show_enum_dropdown(guess, options, onchange.reform(|enum_value| { show_enum_dropdown(
SQLValueGuess::Datetime(enum_value) guess,
})) options,
onchange.reform(|enum_value| SQLValueGuess::Datetime(enum_value)),
)
} }
SQLValueGuess::Bool(guess) => { SQLValueGuess::Bool(guess) => {
let options = HashMap::from([ let options = HashMap::from([
("Random".into(), SQLBoolValueGuess::Random), ("Random".into(), SQLBoolValueGuess::Random),
("True".into() , SQLBoolValueGuess::True), ("True".into(), SQLBoolValueGuess::True),
("False".into() , SQLBoolValueGuess::False), ("False".into(), SQLBoolValueGuess::False),
]); ]);
show_enum_dropdown(guess, options, onchange.reform(|enum_value| { show_enum_dropdown(
SQLValueGuess::Bool(enum_value) guess,
})) options,
}, onchange.reform(|enum_value| SQLValueGuess::Bool(enum_value)),
)
}
SQLValueGuess::String(max_size, guess) => { SQLValueGuess::String(max_size, guess) => {
if let Some(constraint) = &column.check_constraint { if let Some(constraint) = &column.check_constraint {
if let SQLCheckConstraint::OneOf(_) = constraint { if let SQLCheckConstraint::OneOf(_) = constraint {
@ -169,22 +190,24 @@ pub fn generator_picker(
} }
let options = HashMap::from([ let options = HashMap::from([
("Lorem Ipsum".into() , SQLStringValueGuess::LoremIpsum), ("Lorem Ipsum".into(), SQLStringValueGuess::LoremIpsum),
("First Name".into() , SQLStringValueGuess::FirstName), ("First Name".into(), SQLStringValueGuess::FirstName),
("Last Name".into() , SQLStringValueGuess::LastName), ("Last Name".into(), SQLStringValueGuess::LastName),
("Full Name".into() , SQLStringValueGuess::FullName), ("Full Name".into(), SQLStringValueGuess::FullName),
("Empty".into() , SQLStringValueGuess::Empty), ("Empty".into(), SQLStringValueGuess::Empty),
("Phone number".into() , SQLStringValueGuess::PhoneNumber), ("Phone number".into(), SQLStringValueGuess::PhoneNumber),
("City name".into() , SQLStringValueGuess::CityName), ("City name".into(), SQLStringValueGuess::CityName),
("Address".into() , SQLStringValueGuess::Address), ("Address".into(), SQLStringValueGuess::Address),
("Email".into() , SQLStringValueGuess::Email), ("Email".into(), SQLStringValueGuess::Email),
("URL".into() , SQLStringValueGuess::URL), ("URL".into(), SQLStringValueGuess::URL),
]); ]);
let max_size = *max_size; let max_size = *max_size;
show_enum_dropdown(guess, options, onchange.reform(move |enum_value| { show_enum_dropdown(
SQLValueGuess::String(max_size, enum_value) guess,
})) options,
}, onchange.reform(move |enum_value| SQLValueGuess::String(max_size, enum_value)),
)
}
} }
} }

View File

@ -1,2 +1,2 @@
pub mod sql_column_info;
pub mod generator_picker; pub mod generator_picker;
pub mod sql_column_info;

View File

@ -1,61 +1,68 @@
use std::{rc::Rc, collections::HashMap, cell::RefCell}; use std::{cell::RefCell, collections::HashMap, rc::Rc};
use yew::{Properties, html, function_component, Html, Callback}; use yew::{function_component, html, Callback, Html, Properties};
use crate::{magicdraw_parser::SQLTable, generate_sql::SQLValueGuess, components::generator_picker::generator_picker}; use crate::{
components::generator_picker::generator_picker, generate_sql::SQLValueGuess,
magicdraw_parser::SQLTable,
};
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct SQLTableColumnInfoProps { pub struct SQLTableColumnInfoProps {
pub table: Rc<SQLTable>, pub table: Rc<SQLTable>,
pub guessess: Rc<RefCell<HashMap<String, SQLValueGuess>>>, pub guessess: Rc<RefCell<HashMap<String, SQLValueGuess>>>,
pub onchange: Callback<(String, SQLValueGuess)> pub onchange: Callback<(String, SQLValueGuess)>,
} }
const CHECK_MARK: &str = "✔️"; const CHECK_MARK: &str = "✔️";
const CROSS_MARK: &str = ""; const CROSS_MARK: &str = "";
fn bool_to_mark(value: bool) -> &'static str { fn bool_to_mark(value: bool) -> &'static str {
if value { CHECK_MARK } else { CROSS_MARK } if value {
CHECK_MARK
} else {
CROSS_MARK
}
} }
#[function_component] #[function_component]
pub fn SQLTableColumnInfo(props: &SQLTableColumnInfoProps) -> Html { pub fn SQLTableColumnInfo(props: &SQLTableColumnInfoProps) -> Html {
let table = &props.table; let table = &props.table;
let rows = table.columns.iter() let rows = table.columns.iter().map(|col| {
.map(|col| { let guessess = &props.guessess.borrow();
let guessess = &props.guessess.borrow(); let generator = guessess.get(&col.name);
let generator = guessess.get(&col.name);
let foreign_key; let foreign_key;
if let Some((table_name, prop_name)) = &col.foreign_key { if let Some((table_name, prop_name)) = &col.foreign_key {
foreign_key = format!("{} {}", table_name, prop_name); foreign_key = format!("{} {}", table_name, prop_name);
} else { } else {
foreign_key = CROSS_MARK.into(); foreign_key = CROSS_MARK.into();
}
let name = col.name.clone();
let onchange = props.onchange.reform(move |value: SQLValueGuess| (name.clone(), value));
html! {
<tr>
<td> { &col.name } </td>
<td> { &col.sql_type } </td>
<td> {
if let Some(generator) = generator {
generator_picker(col, generator, onchange)
} else {
html!(CROSS_MARK)
}
} </td>
<td> { bool_to_mark(col.primary_key) } </td>
<td> { bool_to_mark(col.nullable) } </td>
<td> { foreign_key } </td>
</tr>
}
} }
);
html!{ let name = col.name.clone();
let onchange = props
.onchange
.reform(move |value: SQLValueGuess| (name.clone(), value));
html! {
<tr>
<td> { &col.name } </td>
<td> { &col.sql_type } </td>
<td> {
if let Some(generator) = generator {
generator_picker(col, generator, onchange)
} else {
html!(CROSS_MARK)
}
} </td>
<td> { bool_to_mark(col.primary_key) } </td>
<td> { bool_to_mark(col.nullable) } </td>
<td> { foreign_key } </td>
</tr>
}
});
html! {
<div <div
class="table-column-info flex-column inline-block" class="table-column-info flex-column inline-block"
border="solid dark100 0.2rem collapse" border="solid dark100 0.2rem collapse"

View File

@ -1,26 +1,40 @@
use std::{rc::Rc, collections::{HashSet, HashMap}, cell::Ref}; use std::{
cell::Ref,
collections::{HashMap, HashSet},
rc::Rc,
};
use anyhow::{Result, bail}; use anyhow::{bail, Result};
use chrono::{Days, Local, NaiveDateTime};
use fake::{
faker::{
address::en::{CityName, StreetName},
company::en::BsNoun,
internet::en::{DomainSuffix, FreeEmail},
lorem::en::*,
name::en::{FirstName, LastName, Name},
phone_number::en::PhoneNumber,
},
Fake,
};
use gloo::console::console_dbg; use gloo::console::console_dbg;
use rand::{seq::SliceRandom, Rng, rngs::ThreadRng}; use rand::{rngs::ThreadRng, seq::SliceRandom, Rng};
use chrono::{Local, NaiveDateTime, Days};
use fake::{faker::{lorem::en::*, name::en::{FirstName, LastName, Name}, phone_number::en::PhoneNumber, internet::en::{DomainSuffix, FreeEmail}, company::en::BsNoun, address::{en::{CityName, StreetName}}}, Fake};
use crate::magicdraw_parser::{SQLTable, SQLColumn, SQLType, SQLCheckConstraint}; use crate::magicdraw_parser::{SQLCheckConstraint, SQLColumn, SQLTable, SQLType};
const INDENT: &str = " "; const INDENT: &str = " ";
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum SQLIntValueGuess { pub enum SQLIntValueGuess {
Range(i32, i32), Range(i32, i32),
AutoIncrement AutoIncrement,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum SQLTimeValueGuess { pub enum SQLTimeValueGuess {
Now, Now,
Future, Future,
Past Past,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -58,10 +72,10 @@ pub enum SQLValueGuess {
// TODO: Check primary key constraint // TODO: Check primary key constraint
pub fn generate_fake_entries( pub fn generate_fake_entries(
tables: &[Rc<SQLTable>], tables: &[Rc<SQLTable>],
value_guessess: &Vec<Ref<HashMap<String, SQLValueGuess>>>, value_guessess: &Vec<Ref<HashMap<String, SQLValueGuess>>>,
rows_per_table: u32 rows_per_table: u32,
) -> Result<String> { ) -> Result<String> {
let mut lines = vec![]; let mut lines = vec![];
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
@ -78,11 +92,13 @@ pub fn generate_fake_entries(
let mut foreign_columns = vec![]; let mut foreign_columns = vec![];
for (i, column) in table.columns.iter().enumerate() { for (i, column) in table.columns.iter().enumerate() {
if let Some((table_name, column_name)) = &column.foreign_key { if let Some((table_name, column_name)) = &column.foreign_key {
let (table_idx, table) = tables.iter() let (table_idx, table) = tables
.iter()
.enumerate() .enumerate()
.find(|(_, table)| table.name.eq(table_name)) .find(|(_, table)| table.name.eq(table_name))
.expect("Foreign table not found"); .expect("Foreign table not found");
let (column_idx, _) = table.columns let (column_idx, _) = table
.columns
.iter() .iter()
.enumerate() .enumerate()
.find(|(_, column)| column.name.eq(column_name)) .find(|(_, column)| column.name.eq(column_name))
@ -109,7 +125,11 @@ pub fn generate_fake_entries(
.get(column.name.as_str()) .get(column.name.as_str())
.expect("Failed to get column guess"); .expect("Failed to get column guess");
for entry_idx in 0..(rows_per_table as usize) { for entry_idx in 0..(rows_per_table as usize) {
entries[entry_idx].push(generate_value(&mut rng, &value_guess, &mut auto_increment_counter)); entries[entry_idx].push(generate_value(
&mut rng,
&value_guess,
&mut auto_increment_counter,
));
} }
} }
} }
@ -120,19 +140,29 @@ pub fn generate_fake_entries(
let before_retain = entries_with_foreign_keys.len(); let before_retain = entries_with_foreign_keys.len();
entries_with_foreign_keys.retain(|(table_idx, entry_idx)| { entries_with_foreign_keys.retain(|(table_idx, entry_idx)| {
for (column_idx, foreign_table_idx, foreign_column_idx) in &all_foreign_columns[*table_idx] { for (column_idx, foreign_table_idx, foreign_column_idx) in
&all_foreign_columns[*table_idx]
{
let available_values: Vec<&str>; let available_values: Vec<&str>;
// If the foreign column, is also a foreign of the other table, ... // If the foreign column, is also a foreign of the other table, ...
// Then we need to filter out available options which have not been filled in // Then we need to filter out available options which have not been filled in
if all_foreign_columns[*foreign_table_idx].iter().find(|(idx, _, _)| idx == foreign_column_idx).is_some() { if all_foreign_columns[*foreign_table_idx]
available_values = all_entries[*foreign_table_idx].iter() .iter()
.find(|(idx, _, _)| idx == foreign_column_idx)
.is_some()
{
available_values = all_entries[*foreign_table_idx]
.iter()
.enumerate() .enumerate()
.filter(|(i, _)| entries_with_foreign_keys_copy.contains(&(*foreign_table_idx, *i))) .filter(|(i, _)| {
entries_with_foreign_keys_copy.contains(&(*foreign_table_idx, *i))
})
.map(|(_, entry)| entry[*foreign_column_idx].as_str()) .map(|(_, entry)| entry[*foreign_column_idx].as_str())
.collect(); .collect();
} else { } else {
available_values = all_entries[*foreign_table_idx].iter() available_values = all_entries[*foreign_table_idx]
.iter()
.map(|entry| entry[*foreign_column_idx].as_str()) .map(|entry| entry[*foreign_column_idx].as_str())
.collect(); .collect();
} }
@ -165,7 +195,8 @@ pub fn generate_fake_entries(
lines.push(format!("INSERT INTO {}", table.name)); lines.push(format!("INSERT INTO {}", table.name));
lines.push(format!("{}({})", INDENT, column_names.join(", "))); lines.push(format!("{}({})", INDENT, column_names.join(", ")));
lines.push("VALUES".into()); lines.push("VALUES".into());
let entries_str = entries.iter() let entries_str = entries
.iter()
.map(|entry| format!("{}({})", INDENT, entry.join(", "))) .map(|entry| format!("{}({})", INDENT, entry.join(", ")))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(",\n"); .join(",\n");
@ -183,7 +214,7 @@ fn generate_time_value(rng: &mut ThreadRng, guess: &SQLTimeValueGuess) -> NaiveD
SQLTimeValueGuess::Future => { SQLTimeValueGuess::Future => {
let days = rng.gen_range(1..=30); let days = rng.gen_range(1..=30);
now.checked_add_days(Days::new(days)).unwrap() now.checked_add_days(Days::new(days)).unwrap()
}, }
SQLTimeValueGuess::Past => { SQLTimeValueGuess::Past => {
let days = rng.gen_range(7..=365); let days = rng.gen_range(7..=365);
now.checked_sub_days(Days::new(days)).unwrap() now.checked_sub_days(Days::new(days)).unwrap()
@ -191,93 +222,77 @@ fn generate_time_value(rng: &mut ThreadRng, guess: &SQLTimeValueGuess) -> NaiveD
} }
} }
fn generate_value(rng: &mut ThreadRng, guess: &SQLValueGuess, auto_increment_counter: &mut u32) -> String { fn generate_value(
rng: &mut ThreadRng,
guess: &SQLValueGuess,
auto_increment_counter: &mut u32,
) -> String {
match guess { match guess {
SQLValueGuess::Int(int_guess) => { SQLValueGuess::Int(int_guess) => match int_guess {
match int_guess { SQLIntValueGuess::Range(min, max) => rng.gen_range((*min)..=(*max)).to_string(),
SQLIntValueGuess::Range(min, max) => { SQLIntValueGuess::AutoIncrement => {
rng.gen_range((*min)..=(*max)).to_string() let str = auto_increment_counter.to_string();
}, *auto_increment_counter += 1;
SQLIntValueGuess::AutoIncrement => { str
let str = auto_increment_counter.to_string();
*auto_increment_counter += 1;
str
},
} }
}, },
SQLValueGuess::Date(time_gues) => { SQLValueGuess::Date(time_gues) => {
let datetime = generate_time_value(rng, &time_gues); let datetime = generate_time_value(rng, &time_gues);
format!("'{}'", datetime.format("%Y-%m-%d")) format!("'{}'", datetime.format("%Y-%m-%d"))
}, }
SQLValueGuess::Time(time_gues) => { SQLValueGuess::Time(time_gues) => {
let datetime = generate_time_value(rng, &time_gues); let datetime = generate_time_value(rng, &time_gues);
format!("'{}'", datetime.format("%H:%M:%S")) format!("'{}'", datetime.format("%H:%M:%S"))
}, }
SQLValueGuess::Datetime(time_gues) => { SQLValueGuess::Datetime(time_gues) => {
let datetime = generate_time_value(rng, &time_gues); let datetime = generate_time_value(rng, &time_gues);
format!("'{}'", datetime.format("%Y-%m-%d %H:%M:%S")) format!("'{}'", datetime.format("%Y-%m-%d %H:%M:%S"))
}
SQLValueGuess::Bool(bool_guess) => match bool_guess {
SQLBoolValueGuess::True => "1".into(),
SQLBoolValueGuess::False => "0".into(),
SQLBoolValueGuess::Random => rng.gen_range(0..=1).to_string(),
}, },
SQLValueGuess::Bool(bool_guess) => { SQLValueGuess::Float(min, max) => {
match bool_guess {
SQLBoolValueGuess::True => "1".into(),
SQLBoolValueGuess::False => "0".into(),
SQLBoolValueGuess::Random => rng.gen_range(0..=1).to_string(),
}
},
SQLValueGuess::Float(min, max) => {
let value = rng.gen_range((*min)..(*max)); let value = rng.gen_range((*min)..(*max));
((value * 100.0 as f32).round() / 100.0).to_string() ((value * 100.0 as f32).round() / 100.0).to_string()
}, }
SQLValueGuess::String(max_size, string_guess) => { SQLValueGuess::String(max_size, string_guess) => {
let mut str = match string_guess { let mut str = match string_guess {
SQLStringValueGuess::LoremIpsum => { SQLStringValueGuess::LoremIpsum => {
let mut current_len = 0; let mut current_len = 0;
let mut text = vec![]; let mut text = vec![];
let words: Vec<String> = Words(3..10).fake_with_rng(rng); let words: Vec<String> = Words(3..10).fake_with_rng(rng);
for word in words { for word in words {
current_len += word.len() + 1; current_len += word.len() + 1;
text.push(word); text.push(word);
if current_len > *max_size { break; } if current_len > *max_size {
break;
}
} }
text.join(" ").to_string() text.join(" ").to_string()
}, }
SQLStringValueGuess::FirstName => { SQLStringValueGuess::FirstName => FirstName().fake_with_rng(rng),
FirstName().fake_with_rng(rng) SQLStringValueGuess::LastName => LastName().fake_with_rng(rng),
}, SQLStringValueGuess::FullName => Name().fake_with_rng(rng),
SQLStringValueGuess::LastName => { SQLStringValueGuess::PhoneNumber => PhoneNumber().fake_with_rng(rng),
LastName().fake_with_rng(rng) SQLStringValueGuess::CityName => CityName().fake_with_rng(rng),
}, SQLStringValueGuess::Address => StreetName().fake_with_rng(rng),
SQLStringValueGuess::FullName => { SQLStringValueGuess::Email => FreeEmail().fake_with_rng(rng),
Name().fake_with_rng(rng)
},
SQLStringValueGuess::PhoneNumber => {
PhoneNumber().fake_with_rng(rng)
},
SQLStringValueGuess::CityName => {
CityName().fake_with_rng(rng)
},
SQLStringValueGuess::Address => {
StreetName().fake_with_rng(rng)
},
SQLStringValueGuess::Email => {
FreeEmail().fake_with_rng(rng)
},
SQLStringValueGuess::URL => { SQLStringValueGuess::URL => {
let suffix: String = DomainSuffix().fake_with_rng(rng); let suffix: String = DomainSuffix().fake_with_rng(rng);
let noun: String = BsNoun().fake_with_rng(rng); let noun: String = BsNoun().fake_with_rng(rng);
let noun: String = noun.to_lowercase() let noun: String = noun
.to_lowercase()
.chars() .chars()
.map(|c| if c.is_whitespace() { '-' } else { c }) .map(|c| if c.is_whitespace() { '-' } else { c })
.collect(); .collect();
format!("www.{}.{}", noun, suffix) format!("www.{}.{}", noun, suffix)
}, }
SQLStringValueGuess::RandomEnum(options) => { SQLStringValueGuess::RandomEnum(options) => {
options.choose(rng).unwrap().to_string() options.choose(rng).unwrap().to_string()
},
SQLStringValueGuess::Empty => {
"".into()
} }
SQLStringValueGuess::Empty => "".into(),
}; };
str.truncate(*max_size); str.truncate(*max_size);
@ -289,74 +304,70 @@ fn generate_value(rng: &mut ThreadRng, guess: &SQLValueGuess, auto_increment_cou
fn generate_string_guess(column: &SQLColumn) -> SQLStringValueGuess { fn generate_string_guess(column: &SQLColumn) -> SQLStringValueGuess {
if let Some(constraint) = &column.check_constraint { if let Some(constraint) = &column.check_constraint {
if let SQLCheckConstraint::OneOf(options) = constraint { if let SQLCheckConstraint::OneOf(options) = constraint {
return SQLStringValueGuess::RandomEnum(options.clone()) return SQLStringValueGuess::RandomEnum(options.clone());
} else { } else {
return SQLStringValueGuess::LoremIpsum return SQLStringValueGuess::LoremIpsum;
} }
} }
let name = column.name.to_lowercase(); let name = column.name.to_lowercase();
if name.contains("first") && name.contains("name") { if name.contains("first") && name.contains("name") {
SQLStringValueGuess::FirstName SQLStringValueGuess::FirstName
} else if (name.contains("last") && name.contains("name")) || name.contains("surname") { } else if (name.contains("last") && name.contains("name")) || name.contains("surname") {
SQLStringValueGuess::LastName SQLStringValueGuess::LastName
} else if name.contains("phone") && name.contains("number") { } else if name.contains("phone") && name.contains("number") {
SQLStringValueGuess::PhoneNumber SQLStringValueGuess::PhoneNumber
} else if name.contains("city") { } else if name.contains("city") {
SQLStringValueGuess::CityName SQLStringValueGuess::CityName
} else if name.contains("address") { } else if name.contains("address") {
SQLStringValueGuess::Address SQLStringValueGuess::Address
} else if name.contains("email") { } else if name.contains("email") {
SQLStringValueGuess::Email SQLStringValueGuess::Email
} else if name.contains("homepage") || name.contains("website") || name.contains("url") { } else if name.contains("homepage") || name.contains("website") || name.contains("url") {
SQLStringValueGuess::URL SQLStringValueGuess::URL
} else { } else {
SQLStringValueGuess::LoremIpsum SQLStringValueGuess::LoremIpsum
} }
} }
pub fn generate_guess(column: &SQLColumn) -> SQLValueGuess { pub fn generate_guess(column: &SQLColumn) -> SQLValueGuess {
match column.sql_type { match column.sql_type {
SQLType::Int => { SQLType::Int => {
if column.primary_key { if column.primary_key {
SQLValueGuess::Int(SQLIntValueGuess::AutoIncrement) SQLValueGuess::Int(SQLIntValueGuess::AutoIncrement)
} else { } else {
SQLValueGuess::Int(SQLIntValueGuess::Range(0, 100)) SQLValueGuess::Int(SQLIntValueGuess::Range(0, 100))
} }
}, }
SQLType::Float | SQLType::Decimal => { SQLType::Float | SQLType::Decimal => SQLValueGuess::Float(0.0, 100.0),
SQLValueGuess::Float(0.0, 100.0) SQLType::Date => {
},
SQLType::Date => {
let name = column.name.to_lowercase(); let name = column.name.to_lowercase();
if name.contains("create") || name.contains("update") { if name.contains("create") || name.contains("update") {
SQLValueGuess::Date(SQLTimeValueGuess::Past) SQLValueGuess::Date(SQLTimeValueGuess::Past)
} else { } else {
SQLValueGuess::Date(SQLTimeValueGuess::Now) SQLValueGuess::Date(SQLTimeValueGuess::Now)
} }
}, }
SQLType::Time => { SQLType::Time => {
let name = column.name.to_lowercase(); let name = column.name.to_lowercase();
if name.contains("create") || name.contains("update") { if name.contains("create") || name.contains("update") {
SQLValueGuess::Time(SQLTimeValueGuess::Past) SQLValueGuess::Time(SQLTimeValueGuess::Past)
} else { } else {
SQLValueGuess::Time(SQLTimeValueGuess::Now) SQLValueGuess::Time(SQLTimeValueGuess::Now)
} }
}, }
SQLType::Datetime => { SQLType::Datetime => {
let name = column.name.to_lowercase(); let name = column.name.to_lowercase();
if name.contains("create") || name.contains("update") { if name.contains("create") || name.contains("update") {
SQLValueGuess::Datetime(SQLTimeValueGuess::Past) SQLValueGuess::Datetime(SQLTimeValueGuess::Past)
} else { } else {
SQLValueGuess::Datetime(SQLTimeValueGuess::Now) SQLValueGuess::Datetime(SQLTimeValueGuess::Now)
} }
}, }
SQLType::Bool => { SQLType::Bool => SQLValueGuess::Bool(SQLBoolValueGuess::Random),
SQLValueGuess::Bool(SQLBoolValueGuess::Random)
},
SQLType::Varchar(max_size) => { SQLType::Varchar(max_size) => {
SQLValueGuess::String(max_size as usize, generate_string_guess(column)) SQLValueGuess::String(max_size as usize, generate_string_guess(column))
}, }
SQLType::Char(max_size) => { SQLType::Char(max_size) => {
SQLValueGuess::String(max_size as usize, generate_string_guess(column)) SQLValueGuess::String(max_size as usize, generate_string_guess(column))
} }
@ -364,7 +375,9 @@ pub fn generate_guess(column: &SQLColumn) -> SQLValueGuess {
} }
pub fn generate_table_guessess(table: &SQLTable) -> HashMap<String, SQLValueGuess> { pub fn generate_table_guessess(table: &SQLTable) -> HashMap<String, SQLValueGuess> {
table.columns.iter() table
.columns
.iter()
.filter(|column| column.foreign_key.is_none()) .filter(|column| column.foreign_key.is_none())
.map(|column| (column.name.clone(), generate_guess(column))) .map(|column| (column.name.clone(), generate_guess(column)))
.collect() .collect()

View File

@ -1,31 +1,31 @@
use std::io::{Read, Seek}; use std::io::{Read, Seek};
use anyhow::{Context, Ok, Result};
use xml::attribute::OwnedAttribute; use xml::attribute::OwnedAttribute;
use xml::name::OwnedName; use xml::name::OwnedName;
use xml::{EventReader, reader::XmlEvent}; use xml::{reader::XmlEvent, EventReader};
use zip::ZipArchive; use zip::ZipArchive;
use anyhow::{Result, Context, Ok};
use crate::magicdraw_parser::utils::get_attribute; use crate::magicdraw_parser::utils::get_attribute;
use super::utils::{check_name, check_attribute, MyEventReader, parse_element}; use super::utils::{check_attribute, check_name, parse_element, MyEventReader};
#[derive(Debug)] #[derive(Debug)]
pub struct DDLClass { pub struct DDLClass {
pub class_id: String, pub class_id: String,
pub property_ids: Vec<String> pub property_ids: Vec<String>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct DDLScript { pub struct DDLScript {
pub script_id: String, pub script_id: String,
pub classess: Vec<DDLClass> pub classess: Vec<DDLClass>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct DDLProject { pub struct DDLProject {
pub model_id: String, pub model_id: String,
pub scripts: Vec<DDLScript> pub scripts: Vec<DDLScript>,
} }
fn get_id_from_href(attrs: &[OwnedAttribute]) -> Option<String> { fn get_id_from_href(attrs: &[OwnedAttribute]) -> Option<String> {
@ -34,16 +34,21 @@ fn get_id_from_href(attrs: &[OwnedAttribute]) -> Option<String> {
Some(parts.1.to_string()) Some(parts.1.to_string())
} }
fn parse_class<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<DDLClass> { fn parse_class<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<DDLClass> {
let mut property_ids = vec![]; let mut property_ids = vec![];
let mut class_id = None; let mut class_id = None;
fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Class") check_name(name, None, "modelElement")
&& check_attribute(&attributes, Some("xsi"), "type", "uml:Class")
} }
fn is_property_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { fn is_property_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Property") check_name(name, None, "modelElement")
&& check_attribute(&attributes, Some("xsi"), "type", "uml:Property")
} }
parse_element(parser, &mut |p, name, attrs| { parse_element(parser, &mut |p, name, attrs| {
@ -57,20 +62,30 @@ fn parse_class<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute])
Ok(DDLClass { Ok(DDLClass {
class_id: class_id.context("Missing class id")?, class_id: class_id.context("Missing class id")?,
property_ids property_ids,
}) })
} }
fn parse_script<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<DDLScript> { fn parse_script<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<DDLScript> {
let mut classess = vec![]; let mut classess = vec![];
let mut script_id = None; let mut script_id = None;
fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Component") check_name(name, None, "modelElement")
&& check_attribute(&attributes, Some("xsi"), "type", "uml:Component")
} }
fn is_class_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { fn is_class_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "objects") && check_attribute(&attributes, Some("xsi"), "type", "md.ce.rt.objects:RTClassObject") check_name(name, None, "objects")
&& check_attribute(
&attributes,
Some("xsi"),
"type",
"md.ce.rt.objects:RTClassObject",
)
} }
parse_element(parser, &mut |p, name, attrs| { parse_element(parser, &mut |p, name, attrs| {
@ -84,20 +99,30 @@ fn parse_script<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]
Ok(DDLScript { Ok(DDLScript {
script_id: script_id.context("Missing script id")?, script_id: script_id.context("Missing script id")?,
classess classess,
}) })
} }
fn parse_project<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<DDLProject> { fn parse_project<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<DDLProject> {
let mut scripts = vec![]; let mut scripts = vec![];
let mut model_id = None; let mut model_id = None;
fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Model") check_name(name, None, "modelElement")
&& check_attribute(&attributes, Some("xsi"), "type", "uml:Model")
} }
fn is_component_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { fn is_component_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "objects") && check_attribute(&attributes, Some("xsi"), "type", "md.ce.rt.objects:RTComponent") check_name(name, None, "objects")
&& check_attribute(
&attributes,
Some("xsi"),
"type",
"md.ce.rt.objects:RTComponent",
)
} }
parse_element(parser, &mut |p, name, attrs| { parse_element(parser, &mut |p, name, attrs| {
@ -111,28 +136,39 @@ fn parse_project<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute
Ok(DDLProject { Ok(DDLProject {
model_id: model_id.context("Missing model id")?, model_id: model_id.context("Missing model id")?,
scripts scripts,
}) })
} }
pub fn parse_ddl_scripts<R: Read + Seek>(project: &mut ZipArchive<R>) -> Result<Vec<DDLProject>> { pub fn parse_ddl_scripts<R: Read + Seek>(project: &mut ZipArchive<R>) -> Result<Vec<DDLProject>> {
let mut ddl_scripts = vec![]; let mut ddl_scripts = vec![];
let file = project.by_name("personal-com.nomagic.magicdraw.ce.dmn.personaldmncodeengineering")?; let file =
project.by_name("personal-com.nomagic.magicdraw.ce.dmn.personaldmncodeengineering")?;
let mut parser: MyEventReader<_> = EventReader::new(file).into(); let mut parser: MyEventReader<_> = EventReader::new(file).into();
fn is_project_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool { fn is_project_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "contents") && check_attribute(&attributes, Some("xsi"), "type", "md.ce.ddl.rt.objects:DDLProjectObject") check_name(name, None, "contents")
&& check_attribute(
&attributes,
Some("xsi"),
"type",
"md.ce.ddl.rt.objects:DDLProjectObject",
)
} }
loop { loop {
match parser.next()? { match parser.next()? {
XmlEvent::StartElement { name, attributes, .. } => { XmlEvent::StartElement {
name, attributes, ..
} => {
if is_project_element(&name, &attributes) { if is_project_element(&name, &attributes) {
ddl_scripts.push(parse_project(&mut parser, &attributes)?); ddl_scripts.push(parse_project(&mut parser, &attributes)?);
} }
}, }
XmlEvent::EndDocument => { break; }, XmlEvent::EndDocument => {
break;
}
_ => {} _ => {}
} }
} }

View File

@ -1,17 +1,28 @@
mod utils;
mod uml_model_parser;
mod ddl_parser; mod ddl_parser;
mod sql_types_parser; mod sql_types_parser;
use serde::{Serialize, Deserialize}; mod uml_model_parser;
mod utils;
use serde::{Deserialize, Serialize};
use std::{io::{Read, Seek}, collections::HashSet, fmt::Display}; use anyhow::{Context, Result};
use anyhow::{Result, Context};
use lazy_regex::regex_captures; use lazy_regex::regex_captures;
use std::{
collections::HashSet,
fmt::Display,
io::{Read, Seek},
};
use zip::ZipArchive; use zip::ZipArchive;
use crate::unwrap_opt_continue; 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}}; use self::{
ddl_parser::parse_ddl_scripts,
sql_types_parser::{parse_sql_types, SQLTypeName},
uml_model_parser::{
parse_uml_model, UMLClass, UMLForeignKeyModifier, UMLModel, UMLModifier,
UMLNullableModifier, UMLPrimaryKeyModifier, UMLTypeModifier,
},
};
#[derive(Debug, PartialEq, Deserialize, Serialize)] #[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum SQLType { pub enum SQLType {
@ -29,15 +40,15 @@ pub enum SQLType {
impl Display for SQLType { impl Display for SQLType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
SQLType::Int => write!(f, "INT"), SQLType::Int => write!(f, "INT"),
SQLType::Decimal => write!(f, "DECIMAL"), SQLType::Decimal => write!(f, "DECIMAL"),
SQLType::Date => write!(f, "DATE"), SQLType::Date => write!(f, "DATE"),
SQLType::Time => write!(f, "TIME"), SQLType::Time => write!(f, "TIME"),
SQLType::Datetime => write!(f, "DATETIME"), SQLType::Datetime => write!(f, "DATETIME"),
SQLType::Float => write!(f, "FLOAT"), SQLType::Float => write!(f, "FLOAT"),
SQLType::Bool => write!(f, "BOOL"), SQLType::Bool => write!(f, "BOOL"),
SQLType::Char(size) => write!(f, "CHAR({})", size), SQLType::Char(size) => write!(f, "CHAR({})", size),
SQLType::Varchar(size) => write!(f, "VARCHAR({})", size), SQLType::Varchar(size) => write!(f, "VARCHAR({})", size),
} }
} }
} }
@ -45,7 +56,7 @@ impl Display for SQLType {
#[derive(Debug, PartialEq, Deserialize, Serialize)] #[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum SQLCheckConstraint { pub enum SQLCheckConstraint {
OneOf(Vec<String>), OneOf(Vec<String>),
Freeform(String) Freeform(String),
} }
#[derive(Debug, PartialEq, Deserialize, Serialize)] #[derive(Debug, PartialEq, Deserialize, Serialize)]
@ -55,7 +66,7 @@ pub struct SQLColumn {
pub primary_key: bool, pub primary_key: bool,
pub nullable: bool, pub nullable: bool,
pub foreign_key: Option<(String, String)>, pub foreign_key: Option<(String, String)>,
pub check_constraint: Option<SQLCheckConstraint> pub check_constraint: Option<SQLCheckConstraint>,
} }
#[derive(Debug, PartialEq, Deserialize, Serialize)] #[derive(Debug, PartialEq, Deserialize, Serialize)]
@ -66,7 +77,7 @@ pub struct SQLTable {
#[derive(Debug, PartialEq, Deserialize, Serialize)] #[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct SQLTableCollection { pub struct SQLTableCollection {
pub tables: Vec<SQLTable> pub tables: Vec<SQLTable>,
} }
fn find_class_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UMLClass> { fn find_class_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UMLClass> {
@ -82,7 +93,11 @@ fn find_class_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UMLClass
fn is_nullabe(modifiers: &[UMLModifier], property: &str) -> bool { fn is_nullabe(modifiers: &[UMLModifier], property: &str) -> bool {
for modifier in modifiers { for modifier in modifiers {
if let UMLModifier::Nullable(UMLNullableModifier { property_id, nullable }) = modifier { if let UMLModifier::Nullable(UMLNullableModifier {
property_id,
nullable,
}) = modifier
{
if property_id.eq(property) { if property_id.eq(property) {
return *nullable; return *nullable;
} }
@ -95,7 +110,7 @@ fn is_primary_key(modifiers: &[UMLModifier], property: &str) -> bool {
for modifier in modifiers { for modifier in modifiers {
if let UMLModifier::PirmaryKey(UMLPrimaryKeyModifier { property_id }) = modifier { if let UMLModifier::PirmaryKey(UMLPrimaryKeyModifier { property_id }) = modifier {
if property_id.eq(property) { if property_id.eq(property) {
return true return true;
} }
} }
} }
@ -104,9 +119,13 @@ fn is_primary_key(modifiers: &[UMLModifier], property: &str) -> bool {
fn get_type_modifier<'a>(modifiers: &'a [UMLModifier], property: &str) -> Option<&'a str> { fn get_type_modifier<'a>(modifiers: &'a [UMLModifier], property: &str) -> Option<&'a str> {
for modifier in modifiers { for modifier in modifiers {
if let UMLModifier::Type(UMLTypeModifier { property_id, modifier }) = modifier { if let UMLModifier::Type(UMLTypeModifier {
property_id,
modifier,
}) = modifier
{
if property_id.eq(property) { if property_id.eq(property) {
return Some(modifier) return Some(modifier);
} }
} }
} }
@ -115,19 +134,27 @@ fn get_type_modifier<'a>(modifiers: &'a [UMLModifier], property: &str) -> Option
fn get_foreign_key_constraint<'a>(modifiers: &'a [UMLModifier], from_id: &str) -> Option<&'a str> { fn get_foreign_key_constraint<'a>(modifiers: &'a [UMLModifier], from_id: &str) -> Option<&'a str> {
for modifier in modifiers { for modifier in modifiers {
if let UMLModifier::ForeignKey(UMLForeignKeyModifier { from_property_id, to_property_id }) = modifier { if let UMLModifier::ForeignKey(UMLForeignKeyModifier {
from_property_id,
to_property_id,
}) = modifier
{
if from_property_id.eq(from_id) { if from_property_id.eq(from_id) {
return Some(&to_property_id) return Some(&to_property_id);
} }
} }
} }
None None
} }
fn get_foreign_key(modifiers: &[UMLModifier], classess: &[&UMLClass], property: &str) -> Result<Option<(String, String)>> { fn get_foreign_key(
modifiers: &[UMLModifier],
classess: &[&UMLClass],
property: &str,
) -> Result<Option<(String, String)>> {
let to_id = get_foreign_key_constraint(modifiers, property); let to_id = get_foreign_key_constraint(modifiers, property);
if to_id.is_none() { if to_id.is_none() {
return Ok(None) return Ok(None);
} }
let to_id = to_id.unwrap(); let to_id = to_id.unwrap();
@ -156,12 +183,14 @@ fn parse_check_constraint(str: &str) -> SQLCheckConstraint {
Some(SQLCheckConstraint::OneOf(variants)) Some(SQLCheckConstraint::OneOf(variants))
} }
try_parse_one_of(str) try_parse_one_of(str).unwrap_or(SQLCheckConstraint::Freeform(str.to_string()))
.unwrap_or(SQLCheckConstraint::Freeform(str.to_string()))
} }
// TODO: Refactor this function, less nesting would be good // TODO: Refactor this function, less nesting would be good
fn get_sql_check_constraint<'a>(models: &'a [UMLModel], property_name: &str) -> Option<SQLCheckConstraint> { fn get_sql_check_constraint<'a>(
models: &'a [UMLModel],
property_name: &str,
) -> Option<SQLCheckConstraint> {
for model in models { for model in models {
for package in &model.packages { for package in &model.packages {
for class in &package.classess { for class in &package.classess {
@ -179,14 +208,18 @@ fn get_sql_check_constraint<'a>(models: &'a [UMLModel], property_name: &str) ->
None None
} }
fn get_sql_type(modifiers: &[UMLModifier], type_name: SQLTypeName, property: &str) -> Result<SQLType> { fn get_sql_type(
modifiers: &[UMLModifier],
type_name: SQLTypeName,
property: &str,
) -> Result<SQLType> {
Ok(match type_name { Ok(match type_name {
SQLTypeName::Int => SQLType::Int, SQLTypeName::Int => SQLType::Int,
SQLTypeName::Date => SQLType::Date, SQLTypeName::Date => SQLType::Date,
SQLTypeName::Float => SQLType::Float, SQLTypeName::Float => SQLType::Float,
SQLTypeName::Bool => SQLType::Bool, SQLTypeName::Bool => SQLType::Bool,
SQLTypeName::Decimal => SQLType::Decimal, SQLTypeName::Decimal => SQLType::Decimal,
SQLTypeName::Char => { SQLTypeName::Char => {
if let Some(type_modifier) = get_type_modifier(modifiers, property) { if let Some(type_modifier) = get_type_modifier(modifiers, property) {
let (_, size) = regex_captures!(r#"^\((\d+)\)$"#, type_modifier) let (_, size) = regex_captures!(r#"^\((\d+)\)$"#, type_modifier)
.context("Type modifier doesn't match format")?; .context("Type modifier doesn't match format")?;
@ -196,8 +229,8 @@ fn get_sql_type(modifiers: &[UMLModifier], type_name: SQLTypeName, property: &st
// For now just pick a defautl arbitrarily // For now just pick a defautl arbitrarily
SQLType::Char(31) SQLType::Char(31)
} }
}, }
SQLTypeName::Varchar => { SQLTypeName::Varchar => {
if let Some(type_modifier) = get_type_modifier(modifiers, property) { if let Some(type_modifier) = get_type_modifier(modifiers, property) {
let (_, size) = regex_captures!(r#"^\((\d+)\)$"#, type_modifier) let (_, size) = regex_captures!(r#"^\((\d+)\)$"#, type_modifier)
.context("Type modifier doesn't match format")?; .context("Type modifier doesn't match format")?;
@ -207,12 +240,13 @@ fn get_sql_type(modifiers: &[UMLModifier], type_name: SQLTypeName, property: &st
// For now just pick a defautl arbitrarily // For now just pick a defautl arbitrarily
SQLType::Varchar(255) SQLType::Varchar(255)
} }
}, }
}) })
} }
fn get_used_types<'a>(models: &'a [UMLModel]) -> HashSet<&'a String> { fn get_used_types<'a>(models: &'a [UMLModel]) -> HashSet<&'a String> {
models.iter() models
.iter()
.flat_map(|model| &model.packages) .flat_map(|model| &model.packages)
.flat_map(|package| &package.classess) .flat_map(|package| &package.classess)
.flat_map(|class| &class.properties) .flat_map(|class| &class.properties)
@ -232,26 +266,43 @@ pub fn parse_project<R: Read + Seek>(project_file: R) -> Result<Vec<SQLTableColl
for ddl_script in ddl_project.scripts { for ddl_script in ddl_project.scripts {
let mut tables = vec![]; let mut tables = vec![];
let model_properties = ddl_script.classess.iter() let model_properties = ddl_script
.flat_map(|class| class.property_ids.iter().map(|prop| (&class.class_id, prop))) .classess
.iter()
.flat_map(|class| {
class
.property_ids
.iter()
.map(|prop| (&class.class_id, prop))
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut model_classess = vec![]; let mut model_classess = vec![];
for ddl_class in &ddl_script.classess { for ddl_class in &ddl_script.classess {
let model_class = find_class_by_id(&models, &ddl_class.class_id).context("UML class not found")?; let model_class = find_class_by_id(&models, &ddl_class.class_id)
.context("UML class not found")?;
model_classess.push(model_class); model_classess.push(model_class);
} }
for (ddl_class, model_class) in ddl_script.classess.iter().zip(&model_classess) { 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 name = model_class
.name
.clone()
.context("UML class name not found")?;
let mut columns = vec![]; let mut columns = vec![];
for property_id in &ddl_class.property_ids { 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 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 prop_name = unwrap_opt_continue!(&property.name).clone();
let type_href = unwrap_opt_continue!(&property.type_href); 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 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 check_constraint = get_sql_check_constraint(&models, &prop_name);
let foreign_key = get_foreign_key(&modifiers, &model_classess, property_id)?; let foreign_key = get_foreign_key(&modifiers, &model_classess, property_id)?;
@ -266,10 +317,7 @@ pub fn parse_project<R: Read + Seek>(project_file: R) -> Result<Vec<SQLTableColl
}) })
} }
tables.push(SQLTable { tables.push(SQLTable { name, columns })
name,
columns
})
} }
collections.push(SQLTableCollection { tables }) collections.push(SQLTableCollection { tables })
} }

View File

@ -1,18 +1,21 @@
use std::{io::{Read, Seek}, collections::{HashMap, HashSet}}; use std::{
collections::{HashMap, HashSet},
io::{Read, Seek},
};
use xml::{EventReader, reader::XmlEvent, attribute::OwnedAttribute, name::OwnedName}; use anyhow::{bail, Context, Result};
use xml::{attribute::OwnedAttribute, name::OwnedName, reader::XmlEvent, EventReader};
use zip::ZipArchive; use zip::ZipArchive;
use anyhow::{Result, Context, bail};
use crate::unwrap_opt_continue; use crate::unwrap_opt_continue;
use super::utils::{MyEventReader, check_name, parse_element, get_attribute, check_attribute}; use super::utils::{check_attribute, check_name, get_attribute, parse_element, MyEventReader};
#[derive(Debug)] #[derive(Debug)]
struct UsedPackage { struct UsedPackage {
share_point_id: String, share_point_id: String,
name: String, name: String,
needed_types: Vec<String> needed_types: Vec<String>,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -23,7 +26,7 @@ pub enum SQLTypeName {
Float, Float,
Bool, Bool,
Char, Char,
Varchar Varchar,
} }
fn get_used_project_name(attrs: &[OwnedAttribute]) -> Option<&str> { fn get_used_project_name(attrs: &[OwnedAttribute]) -> Option<&str> {
@ -31,14 +34,20 @@ fn get_used_project_name(attrs: &[OwnedAttribute]) -> Option<&str> {
project_uri.split("/").last() project_uri.split("/").last()
} }
fn parse_used_package<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute], needed_types: &[&str]) -> Result<UsedPackage> { fn parse_used_package<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
needed_types: &[&str],
) -> Result<UsedPackage> {
let mut share_point_id = None; let mut share_point_id = None;
let project_uri = get_attribute(&attrs, None, "usedProjectURI")?; let project_uri = get_attribute(&attrs, None, "usedProjectURI")?;
let name = project_uri.split("/").last().unwrap(); let name = project_uri.split("/").last().unwrap();
parse_element(parser, &mut |p, name, attrs| { parse_element(parser, &mut |p, name, attrs| {
if share_point_id.is_none() && check_name(&name, None, "mountPoints") { if share_point_id.is_none() && check_name(&name, None, "mountPoints") {
share_point_id = get_attribute(&attrs, None, "sharePointID").ok().map(str::to_string); share_point_id = get_attribute(&attrs, None, "sharePointID")
.ok()
.map(str::to_string);
} }
Ok(()) Ok(())
})?; })?;
@ -46,32 +55,47 @@ fn parse_used_package<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttr
Ok(UsedPackage { Ok(UsedPackage {
name: name.to_string(), name: name.to_string(),
share_point_id: share_point_id.context("Share point id not found")?, share_point_id: share_point_id.context("Share point id not found")?,
needed_types: needed_types.iter().map(|s| s.to_string()).collect() needed_types: needed_types.iter().map(|s| s.to_string()).collect(),
}) })
} }
fn list_used_packages<R: Read>(file: R, needed_types: &HashSet<&String>) -> Result<Vec<UsedPackage>> { fn list_used_packages<R: Read>(
file: R,
needed_types: &HashSet<&String>,
) -> Result<Vec<UsedPackage>> {
let mut packages = vec![]; let mut packages = vec![];
let mut needed_types_per_package = HashMap::new(); let mut needed_types_per_package = HashMap::new();
for needed_type in needed_types.iter() { for needed_type in needed_types.iter() {
let (package_name, type_id) = unwrap_opt_continue!(needed_type.split_once("#")); let (package_name, type_id) = unwrap_opt_continue!(needed_type.split_once("#"));
let ids = needed_types_per_package.entry(package_name).or_insert(vec![]); let ids = needed_types_per_package
.entry(package_name)
.or_insert(vec![]);
ids.push(type_id); ids.push(type_id);
} }
let mut parser: MyEventReader<_> = EventReader::new(file).into(); let mut parser: MyEventReader<_> = EventReader::new(file).into();
loop { loop {
match parser.next()? { match parser.next()? {
XmlEvent::StartElement { name, attributes, .. } => { XmlEvent::StartElement {
name, attributes, ..
} => {
if check_name(&name, None, "projectUsages") { if check_name(&name, None, "projectUsages") {
let project_name = unwrap_opt_continue!(get_used_project_name(&attributes)); let project_name = unwrap_opt_continue!(get_used_project_name(&attributes));
if let Some(needed_types_for_package) = needed_types_per_package.get(&project_name) { if let Some(needed_types_for_package) =
packages.push(parse_used_package(&mut parser, &attributes, needed_types_for_package)?); needed_types_per_package.get(&project_name)
{
packages.push(parse_used_package(
&mut parser,
&attributes,
needed_types_for_package,
)?);
} }
} }
}, }
XmlEvent::EndDocument => { break; }, XmlEvent::EndDocument => {
break;
}
_ => {} _ => {}
} }
} }
@ -93,15 +117,18 @@ fn parse_type_name(str: &str) -> Result<SQLTypeName> {
"Integer" | "integer" | "int" => Int, "Integer" | "integer" | "int" => Int,
"date" => Date, "date" => Date,
"Boolean" => Bool, "Boolean" => Bool,
_ => bail!("Unknown SQL type: '{}'", str) _ => bail!("Unknown SQL type: '{}'", str),
}) })
} }
fn parse_types_package<R: Read>(parser: &mut MyEventReader<R>) -> Result<Vec<(String, SQLTypeName)>> { fn parse_types_package<R: Read>(
parser: &mut MyEventReader<R>,
) -> Result<Vec<(String, SQLTypeName)>> {
let mut types = vec![]; let mut types = vec![];
fn is_primitive_type_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool { fn is_primitive_type_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool {
check_name(&name, None, "packagedElement") && check_attribute(&attrs, Some("xsi"), "type", "uml:PrimitiveType") check_name(&name, None, "packagedElement")
&& check_attribute(&attrs, Some("xsi"), "type", "uml:PrimitiveType")
} }
parse_element(parser, &mut |p, name, attrs| { parse_element(parser, &mut |p, name, attrs| {
@ -109,8 +136,8 @@ fn parse_types_package<R: Read>(parser: &mut MyEventReader<R>) -> Result<Vec<(St
let type_name = get_attribute(&attrs, None, "name")?; let type_name = get_attribute(&attrs, None, "name")?;
if !type_name.eq("StructuredExpression") { if !type_name.eq("StructuredExpression") {
types.push(( types.push((
get_attribute(&attrs, Some("xmi"), "id")?.to_string(), get_attribute(&attrs, Some("xmi"), "id")?.to_string(),
parse_type_name(type_name)? parse_type_name(type_name)?,
)); ));
} }
} }
@ -120,26 +147,37 @@ fn parse_types_package<R: Read>(parser: &mut MyEventReader<R>) -> Result<Vec<(St
Ok(types) Ok(types)
} }
fn parse_primitive_types<R: Read>(reader: R, used_packages: &[UsedPackage]) -> Result<Vec<(String, SQLTypeName)>> { fn parse_primitive_types<R: Read>(
reader: R,
used_packages: &[UsedPackage],
) -> Result<Vec<(String, SQLTypeName)>> {
let mut types = vec![]; let mut types = vec![];
let mut parser: MyEventReader<_> = EventReader::new(reader).into(); let mut parser: MyEventReader<_> = EventReader::new(reader).into();
loop { loop {
match parser.next()? { match parser.next()? {
XmlEvent::StartElement { name, attributes, .. } => { XmlEvent::StartElement {
name, attributes, ..
} => {
if check_name(&name, Some("uml"), "Package") { if check_name(&name, Some("uml"), "Package") {
if let Some(id) = get_attribute(&attributes, None, "ID").ok() { if let Some(id) = get_attribute(&attributes, None, "ID").ok() {
if let Some(package) = used_packages.iter().find(|p| p.share_point_id.eq(id)) { if let Some(package) =
used_packages.iter().find(|p| p.share_point_id.eq(id))
{
let package_types = parse_types_package(&mut parser)? let package_types = parse_types_package(&mut parser)?
.into_iter() .into_iter()
.filter(|t| package.needed_types.contains(&t.0)) .filter(|t| package.needed_types.contains(&t.0))
.map(|(id, type_name)| (format!("{}#{}", package.name, id), type_name)); .map(|(id, type_name)| {
(format!("{}#{}", package.name, id), type_name)
});
types.extend(package_types); types.extend(package_types);
} }
} }
} }
}, }
XmlEvent::EndDocument => { break; }, XmlEvent::EndDocument => {
break;
}
_ => {} _ => {}
} }
} }
@ -147,13 +185,17 @@ fn parse_primitive_types<R: Read>(reader: R, used_packages: &[UsedPackage]) -> R
Ok(types) Ok(types)
} }
pub fn parse_sql_types<R: Read + Seek>(project: &mut ZipArchive<R>, needed_types: &HashSet<&String>) -> Result<HashMap<String, SQLTypeName>> { pub fn parse_sql_types<R: Read + Seek>(
project: &mut ZipArchive<R>,
needed_types: &HashSet<&String>,
) -> Result<HashMap<String, SQLTypeName>> {
let mut type_names = HashMap::new(); let mut type_names = HashMap::new();
let meta_model_file = project.by_name("com.nomagic.ci.metamodel.project")?; let meta_model_file = project.by_name("com.nomagic.ci.metamodel.project")?;
let used_packages = list_used_packages(meta_model_file, needed_types)?; let used_packages = list_used_packages(meta_model_file, needed_types)?;
let snapshot_files = project.file_names() let snapshot_files = project
.file_names()
.filter(|f| is_umodel_snapshot_file(f)) .filter(|f| is_umodel_snapshot_file(f))
.map(|f| f.to_string()) .map(|f| f.to_string())
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@ -1,19 +1,22 @@
use std::io::{Read, Seek}; use std::io::{Read, Seek};
use xml::{EventReader, reader::XmlEvent, attribute::OwnedAttribute, name::OwnedName}; use anyhow::{Context, Result};
use xml::{attribute::OwnedAttribute, name::OwnedName, reader::XmlEvent, EventReader};
use zip::ZipArchive; use zip::ZipArchive;
use anyhow::{Result, Context};
use crate::{unwrap_err_continue, unwrap_opt_continue}; use crate::{unwrap_err_continue, unwrap_opt_continue};
use super::utils::{check_name, parse_element, get_attribute, check_attribute, MyEventReader, ParseProjectError, get_element_characters}; use super::utils::{
check_attribute, check_name, get_attribute, get_element_characters, parse_element,
MyEventReader, ParseProjectError,
};
#[derive(Debug)] #[derive(Debug)]
pub struct UMLProperty { pub struct UMLProperty {
pub id: String, pub id: String,
pub name: Option<String>, pub name: Option<String>,
pub is_id: bool, pub is_id: bool,
pub type_href: Option<String> pub type_href: Option<String>,
} }
// TODO: Make this an enum? Because from what I have seen there were only 2 cases, // TODO: Make this an enum? Because from what I have seen there were only 2 cases,
@ -33,32 +36,32 @@ pub struct UMLClass {
pub id: String, pub id: String,
pub name: Option<String>, pub name: Option<String>,
pub properties: Vec<UMLProperty>, pub properties: Vec<UMLProperty>,
pub constraints: Vec<UMLConstraint> pub constraints: Vec<UMLConstraint>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct UMLPackage { pub struct UMLPackage {
pub id: String, pub id: String,
pub name: Option<String>, pub name: Option<String>,
pub classess: Vec<UMLClass> pub classess: Vec<UMLClass>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct UMLModel { pub struct UMLModel {
pub id: String, pub id: String,
pub name: String, pub name: String,
pub packages: Vec<UMLPackage> pub packages: Vec<UMLPackage>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct UMLPrimaryKeyModifier { pub struct UMLPrimaryKeyModifier {
pub property_id: String pub property_id: String,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct UMLNullableModifier { pub struct UMLNullableModifier {
pub property_id: String, pub property_id: String,
pub nullable: bool pub nullable: bool,
} }
#[derive(Debug)] #[derive(Debug)]
@ -69,13 +72,13 @@ pub struct UMLForeignKeyModifier {
#[derive(Debug)] #[derive(Debug)]
pub struct UMLUniqueModifier { pub struct UMLUniqueModifier {
pub property_id: String pub property_id: String,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct UMLTypeModifier { pub struct UMLTypeModifier {
pub property_id: String, pub property_id: String,
pub modifier: String pub modifier: String,
} }
#[derive(Debug)] #[derive(Debug)]
@ -84,13 +87,18 @@ pub enum UMLModifier {
PirmaryKey(UMLPrimaryKeyModifier), PirmaryKey(UMLPrimaryKeyModifier),
Nullable(UMLNullableModifier), Nullable(UMLNullableModifier),
ForeignKey(UMLForeignKeyModifier), ForeignKey(UMLForeignKeyModifier),
Type(UMLTypeModifier) Type(UMLTypeModifier),
} }
fn parse_property<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<UMLProperty> { fn parse_property<R: Read>(
let id = get_attribute(attrs, Some("xmi"), "id")?.into(); parser: &mut MyEventReader<R>,
let name = get_attribute(attrs, None, "name").ok().map(str::to_string); attrs: &[OwnedAttribute],
let is_id = get_attribute(attrs, None, "isID").unwrap_or("false").eq("true"); ) -> Result<UMLProperty> {
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let name = get_attribute(attrs, None, "name").ok().map(str::to_string);
let is_id = get_attribute(attrs, None, "isID")
.unwrap_or("false")
.eq("true");
let mut type_href = None; let mut type_href = None;
parse_element(parser, &mut |p, name, attrs| { parse_element(parser, &mut |p, name, attrs| {
@ -103,22 +111,27 @@ fn parse_property<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribut
})?; })?;
Ok(UMLProperty { Ok(UMLProperty {
id, id,
name, name,
is_id, is_id,
type_href type_href,
}) })
} }
fn parse_constraint<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<Option<UMLConstraint>> { fn parse_constraint<R: Read>(
let id = get_attribute(attrs, Some("xmi"), "id")?.into(); parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<Option<UMLConstraint>> {
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let mut constrainted_element_id = None; let mut constrainted_element_id = None;
let mut language = None; let mut language = None;
let mut body = None; let mut body = None;
parse_element(parser, &mut |p, name, attrs| { parse_element(parser, &mut |p, name, attrs| {
if check_name(&name, None, "constrainedElement") && constrainted_element_id.is_none() { if check_name(&name, None, "constrainedElement") && constrainted_element_id.is_none() {
constrainted_element_id = get_attribute(&attrs, Some("xmi"), "idref").ok().map(str::to_string); constrainted_element_id = get_attribute(&attrs, Some("xmi"), "idref")
.ok()
.map(str::to_string);
} else if check_name(&name, None, "body") && body.is_none() { } else if check_name(&name, None, "body") && body.is_none() {
let contents = get_element_characters(p)?; let contents = get_element_characters(p)?;
if contents.len() > 0 { if contents.len() > 0 {
@ -137,32 +150,37 @@ fn parse_constraint<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttrib
class_id: Some(constrainted_element_id.context("Missing class id")?), class_id: Some(constrainted_element_id.context("Missing class id")?),
body: Some(format!("in {}", check_body)), body: Some(format!("in {}", check_body)),
property_id: None, property_id: None,
property_name: Some(prop_name.into()) property_name: Some(prop_name.into()),
})) }));
} }
} }
return Ok(Some(UMLConstraint { return Ok(Some(UMLConstraint {
id, id,
property_id: Some(constrainted_element_id.context("Missing property id")?), property_id: Some(constrainted_element_id.context("Missing property id")?),
body: None, body: None,
class_id: None, class_id: None,
property_name: None property_name: None,
})) }));
} }
fn parse_class<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<UMLClass> { fn parse_class<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<UMLClass> {
let mut properties = vec![]; let mut properties = vec![];
let mut consraints = vec![]; let mut consraints = vec![];
let id = get_attribute(attrs, Some("xmi"), "id")?.into(); let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let name = get_attribute(attrs, None, "name").ok().map(str::to_string); let name = get_attribute(attrs, None, "name").ok().map(str::to_string);
fn is_property_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool { fn is_property_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool {
check_name(name, None, "ownedAttribute") && check_attribute(&attrs, Some("xmi"), "type", "uml:Property") check_name(name, None, "ownedAttribute")
&& check_attribute(&attrs, Some("xmi"), "type", "uml:Property")
} }
fn is_constraint_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool { fn is_constraint_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool {
check_name(name, None, "ownedRule") && check_attribute(&attrs, Some("xmi"), "type", "uml:Constraint") check_name(name, None, "ownedRule")
&& check_attribute(&attrs, Some("xmi"), "type", "uml:Constraint")
} }
parse_element(parser, &mut |p, name, attrs| { parse_element(parser, &mut |p, name, attrs| {
@ -177,20 +195,24 @@ fn parse_class<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute])
})?; })?;
Ok(UMLClass { Ok(UMLClass {
id, id,
name, name,
properties, properties,
constraints: consraints, constraints: consraints,
}) })
} }
fn parse_package<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<UMLPackage> { fn parse_package<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<UMLPackage> {
let mut classess = vec![]; let mut classess = vec![];
let id = get_attribute(attrs, Some("xmi"), "id")?.into(); let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let name = get_attribute(attrs, None, "name").ok().map(str::to_string); let name = get_attribute(attrs, None, "name").ok().map(str::to_string);
fn is_class_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool { fn is_class_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool {
check_name(name, None, "packagedElement") && check_attribute(&attrs, Some("xmi"), "type", "uml:Class") check_name(name, None, "packagedElement")
&& check_attribute(&attrs, Some("xmi"), "type", "uml:Class")
} }
parse_element(parser, &mut |p, name, attrs| { parse_element(parser, &mut |p, name, attrs| {
@ -200,20 +222,20 @@ fn parse_package<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute
Ok(()) Ok(())
})?; })?;
Ok(UMLPackage { Ok(UMLPackage { id, name, classess })
id,
name,
classess
})
} }
fn parse_model<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<UMLModel> { fn parse_model<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<UMLModel> {
let mut packages = vec![]; let mut packages = vec![];
let id = get_attribute(attrs, Some("xmi"), "id")?.into(); let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let name = get_attribute(attrs, None, "name")?.into(); let name = get_attribute(attrs, None, "name")?.into();
fn is_package_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool { fn is_package_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool {
check_name(name, None, "packagedElement") && check_attribute(&attrs, Some("xmi"), "type", "uml:Package") check_name(name, None, "packagedElement")
&& check_attribute(&attrs, Some("xmi"), "type", "uml:Package")
} }
parse_element(parser, &mut |p, name, attrs| { parse_element(parser, &mut |p, name, attrs| {
@ -223,11 +245,7 @@ fn parse_model<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute])
Ok(()) Ok(())
})?; })?;
Ok(UMLModel { Ok(UMLModel { id, name, packages })
id,
name,
packages
})
} }
fn find_constraint_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UMLConstraint> { fn find_constraint_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UMLConstraint> {
@ -246,7 +264,9 @@ fn find_constraint_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UML
None None
} }
pub fn parse_uml_model<R: Read + Seek>(project: &mut ZipArchive<R>) -> Result<(Vec<UMLModel>, Vec<UMLModifier>)> { pub fn parse_uml_model<R: Read + Seek>(
project: &mut ZipArchive<R>,
) -> Result<(Vec<UMLModel>, Vec<UMLModifier>)> {
let mut models = vec![]; let mut models = vec![];
let mut modifiers = vec![]; let mut modifiers = vec![];
@ -255,37 +275,64 @@ pub fn parse_uml_model<R: Read + Seek>(project: &mut ZipArchive<R>) -> Result<(V
loop { loop {
match parser.next()? { match parser.next()? {
XmlEvent::StartElement { name, attributes, .. } => { XmlEvent::StartElement {
name, attributes, ..
} => {
if check_name(&name, Some("uml"), "Model") { if check_name(&name, Some("uml"), "Model") {
models.push(parse_model(&mut parser, &attributes)?); models.push(parse_model(&mut parser, &attributes)?);
} else if check_name(&name, Some("SQLProfile"), "PrimaryKey") { } else if check_name(&name, Some("SQLProfile"), "PrimaryKey") {
let constraint_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Constraint")); let constraint_id =
let constraint = unwrap_opt_continue!(find_constraint_by_id(&models, constraint_id)); unwrap_err_continue!(get_attribute(&attributes, None, "base_Constraint"));
let constraint =
unwrap_opt_continue!(find_constraint_by_id(&models, constraint_id));
let property_id = unwrap_opt_continue!(&constraint.property_id).clone(); let property_id = unwrap_opt_continue!(&constraint.property_id).clone();
modifiers.push(UMLModifier::PirmaryKey(UMLPrimaryKeyModifier { property_id })); modifiers.push(UMLModifier::PirmaryKey(UMLPrimaryKeyModifier {
property_id,
}));
} else if check_name(&name, Some("SQLProfile"), "PKMember") { } else if check_name(&name, Some("SQLProfile"), "PKMember") {
let property_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Property")).to_string(); let property_id =
modifiers.push(UMLModifier::PirmaryKey(UMLPrimaryKeyModifier { property_id })); unwrap_err_continue!(get_attribute(&attributes, None, "base_Property"))
.to_string();
modifiers.push(UMLModifier::PirmaryKey(UMLPrimaryKeyModifier {
property_id,
}));
} else if check_name(&name, Some("SQLProfile"), "Column") { } else if check_name(&name, Some("SQLProfile"), "Column") {
let property_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Property")).to_string(); let property_id =
let nullable = unwrap_err_continue!(get_attribute(&attributes, None, "nullable")).eq("true"); unwrap_err_continue!(get_attribute(&attributes, None, "base_Property"))
modifiers.push(UMLModifier::Nullable(UMLNullableModifier { property_id, nullable })); .to_string();
let nullable =
unwrap_err_continue!(get_attribute(&attributes, None, "nullable"))
.eq("true");
modifiers.push(UMLModifier::Nullable(UMLNullableModifier {
property_id,
nullable,
}));
} else if check_name(&name, Some("MagicDraw_Profile"), "typeModifier") { } else if check_name(&name, Some("MagicDraw_Profile"), "typeModifier") {
let property_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Element")).into(); let property_id =
let modifier = unwrap_err_continue!(get_attribute(&attributes, None, "typeModifier")).into(); unwrap_err_continue!(get_attribute(&attributes, None, "base_Element"))
modifiers.push(UMLModifier::Type(UMLTypeModifier { property_id, modifier })); .into();
let modifier =
unwrap_err_continue!(get_attribute(&attributes, None, "typeModifier"))
.into();
modifiers.push(UMLModifier::Type(UMLTypeModifier {
property_id,
modifier,
}));
} else if check_name(&name, Some("SQLProfile"), "FK") { } else if check_name(&name, Some("SQLProfile"), "FK") {
let from_property_id = unwrap_err_continue!(get_attribute(&attributes, None, "members")).into(); let from_property_id =
let to_property_id = unwrap_err_continue!(get_attribute(&attributes, None, "referencedMembers")).into(); unwrap_err_continue!(get_attribute(&attributes, None, "members")).into();
modifiers.push(UMLModifier::ForeignKey(UMLForeignKeyModifier { from_property_id, to_property_id })); let to_property_id =
unwrap_err_continue!(get_attribute(&attributes, None, "referencedMembers"))
.into();
modifiers.push(UMLModifier::ForeignKey(UMLForeignKeyModifier {
from_property_id,
to_property_id,
}));
} }
}, }
XmlEvent::EndDocument => { break; }, XmlEvent::EndDocument => {
break;
}
_ => {} _ => {}
} }
} }

View File

@ -1,12 +1,12 @@
use std::io::Read; use std::io::Read;
use xml::{EventReader, reader::XmlEvent, name::OwnedName, attribute::OwnedAttribute};
use anyhow::Result; use anyhow::Result;
use thiserror::Error; use thiserror::Error;
use xml::{attribute::OwnedAttribute, name::OwnedName, reader::XmlEvent, EventReader};
pub struct MyEventReader<R: Read> { pub struct MyEventReader<R: Read> {
depth: u32, depth: u32,
event_reader: EventReader<R> event_reader: EventReader<R>,
} }
impl<R: Read> MyEventReader<R> { impl<R: Read> MyEventReader<R> {
@ -27,12 +27,12 @@ impl<R: Read> MyEventReader<R> {
} }
impl<R: Read> From<EventReader<R>> for MyEventReader<R> { impl<R: Read> From<EventReader<R>> for MyEventReader<R> {
fn from(event_reader: EventReader<R>) -> Self { fn from(event_reader: EventReader<R>) -> Self {
MyEventReader { MyEventReader {
depth: 0, depth: 0,
event_reader event_reader,
} }
} }
} }
#[derive(Error, Debug, PartialEq)] #[derive(Error, Debug, PartialEq)]
@ -41,7 +41,7 @@ pub enum ParseProjectError {
AttributeNotFound(Option<String>, String), AttributeNotFound(Option<String>, String),
#[error("Unexpected end of XML document")] #[error("Unexpected end of XML document")]
EndOfDocument EndOfDocument,
} }
fn format_name_from_parts(prefix: &Option<String>, local_name: &str) -> String { fn format_name_from_parts(prefix: &Option<String>, local_name: &str) -> String {
@ -60,17 +60,27 @@ pub fn check_name(name: &OwnedName, prefix: Option<&str>, local_name: &str) -> b
name.local_name.eq(local_name) && name.prefix_ref().eq(&prefix) name.local_name.eq(local_name) && name.prefix_ref().eq(&prefix)
} }
pub fn get_attribute<'a>(attributes: &'a [OwnedAttribute], prefix: Option<&str>, name: &str) -> Result<&'a str, ParseProjectError> { pub fn get_attribute<'a>(
Ok(attributes.iter() attributes: &'a [OwnedAttribute],
prefix: Option<&str>,
name: &str,
) -> Result<&'a str, ParseProjectError> {
Ok(attributes
.iter()
.find(|attr| check_name(&attr.name, prefix, name)) .find(|attr| check_name(&attr.name, prefix, name))
.map(|attr| &attr.value[..]) .map(|attr| &attr.value[..])
.ok_or_else( .ok_or_else(|| {
|| ParseProjectError::AttributeNotFound(prefix.map(|s| s.to_owned()), name.to_owned()), ParseProjectError::AttributeNotFound(prefix.map(|s| s.to_owned()), name.to_owned())
)?) })?)
} }
#[inline(always)] #[inline(always)]
pub fn check_attribute(attributes: &[OwnedAttribute], prefix: Option<&str>, name: &str, expected_value: &str) -> bool { pub fn check_attribute(
attributes: &[OwnedAttribute],
prefix: Option<&str>,
name: &str,
expected_value: &str,
) -> bool {
if let Ok(attr) = get_attribute(attributes, prefix, name) { if let Ok(attr) = get_attribute(attributes, prefix, name) {
return attr.eq(expected_value); return attr.eq(expected_value);
} }
@ -84,10 +94,10 @@ pub fn get_element_characters<R: Read>(parser: &mut MyEventReader<R>) -> Result<
match parser.next()? { match parser.next()? {
XmlEvent::Characters(text) => { XmlEvent::Characters(text) => {
parts.push(text); parts.push(text);
}, }
XmlEvent::EndElement { name } => { XmlEvent::EndElement { name } => {
break; break;
}, }
_ => {} _ => {}
} }
} }
@ -95,23 +105,30 @@ pub fn get_element_characters<R: Read>(parser: &mut MyEventReader<R>) -> Result<
Ok(parts.join(" ")) Ok(parts.join(" "))
} }
pub fn parse_element<R: Read, F>(parser: &mut MyEventReader<R>, process_element: &mut F) -> Result<()> pub fn parse_element<R: Read, F>(
where F: FnMut(&mut MyEventReader<R>, OwnedName, Vec<OwnedAttribute>) -> Result<()> parser: &mut MyEventReader<R>,
process_element: &mut F,
) -> Result<()>
where
F: FnMut(&mut MyEventReader<R>, OwnedName, Vec<OwnedAttribute>) -> Result<()>,
{ {
let starting_depth = parser.depth(); let starting_depth = parser.depth();
loop { loop {
match parser.next()? { match parser.next()? {
XmlEvent::StartElement { name, attributes, .. } => { XmlEvent::StartElement {
name, attributes, ..
} => {
process_element(parser, name, attributes)?; process_element(parser, name, attributes)?;
if parser.depth() == starting_depth-1 { break; } if parser.depth() == starting_depth - 1 {
break;
}
} }
XmlEvent::EndElement { name } => { XmlEvent::EndElement { name } => {
if parser.depth() == starting_depth-1 { break; } if parser.depth() == starting_depth - 1 {
}, break;
XmlEvent::EndDocument => { }
Err(ParseProjectError::EndOfDocument)? }
}, XmlEvent::EndDocument => Err(ParseProjectError::EndOfDocument)?,
_ => {} _ => {}
} }
} }
@ -121,20 +138,24 @@ pub fn parse_element<R: Read, F>(parser: &mut MyEventReader<R>, process_element:
#[macro_export] #[macro_export]
macro_rules! unwrap_err_continue { macro_rules! unwrap_err_continue {
($res:expr) => { ($res:expr) => {
match $res { match $res {
Ok(val) => val, Ok(val) => val,
Err(e) => { continue; } Err(e) => {
} continue;
}; }
}
};
} }
#[macro_export] #[macro_export]
macro_rules! unwrap_opt_continue { macro_rules! unwrap_opt_continue {
($res:expr) => { ($res:expr) => {
match $res { match $res {
Some(val) => val, Some(val) => val,
None => { continue; } None => {
} continue;
}; }
}
};
} }

View File

@ -2,10 +2,10 @@ use anyhow::Result;
use app::App; use app::App;
mod magicdraw_parser;
mod app; mod app;
mod components; mod components;
mod generate_sql; mod generate_sql;
mod magicdraw_parser;
// TODO: Make this work with enumation lookup tables // TODO: Make this work with enumation lookup tables
// TODO: Dark theme switch button // TODO: Dark theme switch button