1
0

Compare commits

...

10 Commits

10 changed files with 90 additions and 66 deletions

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# MagicDraw SQL Generator
https://rokaspuzonas.github.io/magic-sql-gen/

View File

@ -1,3 +1,6 @@
#!/bin/sh #!/bin/sh
npm i
npm run uno npm run uno
trunk build --release cargo install trunk
rustup target add wasm32-unknown-unknown
TRUNK_BUILD_PUBLIC_URL="/magic-sql-gen/" trunk build --release

View File

@ -1,9 +1,8 @@
use gloo::console::{console, console_dbg};
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::cell::RefCell;
use std::collections::{self, HashMap}; use std::collections::HashMap;
use std::io::Cursor; use std::io::Cursor;
use std::rc::Rc; use std::rc::Rc;
use web_sys::{DragEvent, Event, FileList, HtmlInputElement, MouseEvent}; use web_sys::{DragEvent, Event, FileList, HtmlInputElement, MouseEvent};

View File

@ -1,4 +1,4 @@
use std::{collections::HashMap, str::FromStr}; use std::str::FromStr;
use web_sys::{Event, HtmlInputElement}; use web_sys::{Event, HtmlInputElement};
use yew::{html, AttrValue, Callback, Html, TargetCast}; use yew::{html, AttrValue, Callback, Html, TargetCast};
@ -12,9 +12,9 @@ use crate::{
fn show_dropdown_picker(selected: &str, options: &[AttrValue], onchange: Callback<String>) -> Html { fn show_dropdown_picker(selected: &str, options: &[AttrValue], onchange: Callback<String>) -> Html {
html! { html! {
<select onchange={Callback::from(move |e: Event| { <select onchange={onchange.reform(move |e: Event| {
let value = e.target_unchecked_into::<HtmlInputElement>().value(); let value = e.target_unchecked_into::<HtmlInputElement>().value();
onchange.emit(value); value
})}> })}>
{ {
options.iter().map(|value| { options.iter().map(|value| {
@ -25,12 +25,12 @@ fn show_dropdown_picker(selected: &str, options: &[AttrValue], onchange: Callbac
} }
} }
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: &Vec<(AttrValue, T)>,
onchange: Callback<T>, onchange: Callback<T>,
) -> Html { ) -> Html {
let keys = options.keys().map(AttrValue::clone).collect::<Vec<_>>(); let keys = options.iter().map(|(opt, _)| opt.clone()).collect::<Vec<_>>();
let guess_str = options let guess_str = options
.iter() .iter()
.find(|(_, v)| v.eq(&selected)) .find(|(_, v)| v.eq(&selected))
@ -38,10 +38,17 @@ fn show_enum_dropdown<T: PartialEq + Clone + 'static>(
.0 .0
.clone(); .clone();
let options = options.clone();
show_dropdown_picker( show_dropdown_picker(
&guess_str, &guess_str,
&keys, &keys,
onchange.reform(move |value_str: String| options.get(value_str.as_str()).unwrap().clone()), onchange.reform(move |value_str: String| {
let enum_value = &options.iter()
.find(|(v, _)| v.eq(&value_str))
.unwrap()
.1;
enum_value.clone()
})
) )
} }
@ -131,54 +138,54 @@ pub fn generator_picker(
onchange.reform(|(min, max)| SQLValueGuess::Float(min, max)), onchange.reform(|(min, max)| SQLValueGuess::Float(min, max)),
), ),
SQLValueGuess::Date(guess) => { SQLValueGuess::Date(guess) => {
let options = HashMap::from([ let options = vec![
("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( show_enum_dropdown(
guess, guess,
options, &options,
onchange.reform(|enum_value| SQLValueGuess::Date(enum_value)), onchange.reform(|enum_value| SQLValueGuess::Date(enum_value)),
) )
} }
SQLValueGuess::Time(guess) => { SQLValueGuess::Time(guess) => {
let options = HashMap::from([ let options = vec![
("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( show_enum_dropdown(
guess, guess,
options, &options,
onchange.reform(|enum_value| SQLValueGuess::Time(enum_value)), onchange.reform(|enum_value| SQLValueGuess::Time(enum_value)),
) )
} }
SQLValueGuess::Datetime(guess) => { SQLValueGuess::Datetime(guess) => {
let options = HashMap::from([ let options = vec![
("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( show_enum_dropdown(
guess, guess,
options, &options,
onchange.reform(|enum_value| SQLValueGuess::Datetime(enum_value)), onchange.reform(|enum_value| SQLValueGuess::Datetime(enum_value)),
) )
} }
SQLValueGuess::Bool(guess) => { SQLValueGuess::Bool(guess) => {
let options = HashMap::from([ let options = vec![
("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( show_enum_dropdown(
guess, guess,
options, &options,
onchange.reform(|enum_value| SQLValueGuess::Bool(enum_value)), onchange.reform(|enum_value| SQLValueGuess::Bool(enum_value)),
) )
} }
@ -189,23 +196,23 @@ pub fn generator_picker(
} }
} }
let options = HashMap::from([ let options = vec![
("Lorem Ipsum".into(), SQLStringValueGuess::LoremIpsum), ("Lorem Ipsum".into(), SQLStringValueGuess::LoremIpsum),
("Empty".into(), SQLStringValueGuess::Empty),
("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),
("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( show_enum_dropdown(
guess, guess,
options, &options,
onchange.reform(move |enum_value| SQLValueGuess::String(max_size, enum_value)), onchange.reform(move |enum_value| SQLValueGuess::String(max_size, enum_value)),
) )
} }

View File

@ -17,7 +17,6 @@ use fake::{
}, },
Fake, Fake,
}; };
use gloo::console::console_dbg;
use rand::{rngs::ThreadRng, seq::SliceRandom, Rng}; use rand::{rngs::ThreadRng, seq::SliceRandom, Rng};
use crate::magicdraw_parser::{SQLCheckConstraint, SQLColumn, SQLTable, SQLType}; use crate::magicdraw_parser::{SQLCheckConstraint, SQLColumn, SQLTable, SQLType};
@ -125,11 +124,8 @@ 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( let value = generate_value(&mut rng, &value_guess, &mut auto_increment_counter);
&mut rng, entries[entry_idx].push(value);
&value_guess,
&mut auto_increment_counter,
));
} }
} }
} }
@ -140,17 +136,17 @@ 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 for (column_idx, foreign_table_idx, foreign_column_idx) in &all_foreign_columns[*table_idx]
&all_foreign_columns[*table_idx]
{ {
let available_values: Vec<&str>; let mut 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] let is_foreign_column_also_foreign = all_foreign_columns[*foreign_table_idx]
.iter() .iter()
.find(|(idx, _, _)| idx == foreign_column_idx) .find(|(idx, _, _)| idx == foreign_column_idx)
.is_some() .is_some();
if is_foreign_column_also_foreign
{ {
available_values = all_entries[*foreign_table_idx] available_values = all_entries[*foreign_table_idx]
.iter() .iter()
@ -167,6 +163,14 @@ pub fn generate_fake_entries(
.collect(); .collect();
} }
let used_values = all_entries[*table_idx].iter()
.enumerate()
.filter(|(entry_idx, _)| entries_with_foreign_keys_copy.contains(&(*table_idx, *entry_idx)))
.map(|(_, entry)| entry[*column_idx].as_str())
.collect::<HashSet<_>>();
available_values.retain(|value| !used_values.contains(value));
if let Some(chosen_value) = available_values.choose(&mut rng) { if let Some(chosen_value) = available_values.choose(&mut rng) {
all_entries[*table_idx][*entry_idx][*column_idx] = chosen_value.to_string(); all_entries[*table_idx][*entry_idx][*column_idx] = chosen_value.to_string();
} else { } else {

View File

@ -61,7 +61,7 @@ fn parse_class<R: Read>(
})?; })?;
Ok(DDLClass { Ok(DDLClass {
class_id: class_id.context("Missing class id")?, class_id: class_id.context("Missing dll class id")?,
property_ids, property_ids,
}) })
} }

View File

@ -216,6 +216,8 @@ fn get_sql_type(
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::Datetime => SQLType::Datetime,
SQLTypeName::Time => SQLType::Time,
SQLTypeName::Float => SQLType::Float, SQLTypeName::Float => SQLType::Float,
SQLTypeName::Bool => SQLType::Bool, SQLTypeName::Bool => SQLType::Bool,
SQLTypeName::Decimal => SQLType::Decimal, SQLTypeName::Decimal => SQLType::Decimal,
@ -266,17 +268,6 @@ 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()
.flat_map(|class| {
class
.property_ids
.iter()
.map(|prop| (&class.class_id, prop))
})
.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) let model_class = find_class_by_id(&models, &ddl_class.class_id)
@ -302,7 +293,7 @@ pub fn parse_project<R: Read + Seek>(project_file: R) -> Result<Vec<SQLTableColl
let type_href = unwrap_opt_continue!(&property.type_href); let type_href = unwrap_opt_continue!(&property.type_href);
let type_name = sql_type_names let type_name = sql_type_names
.get(type_href) .get(type_href)
.context("Proerty type name conversion not found")?; .context("Property 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)?;

View File

@ -13,7 +13,7 @@ use super::utils::{check_attribute, check_name, get_attribute, parse_element, My
#[derive(Debug)] #[derive(Debug)]
struct UsedPackage { struct UsedPackage {
share_point_id: String, share_point_ids: Vec<String>,
name: String, name: String,
needed_types: Vec<String>, needed_types: Vec<String>,
} }
@ -23,6 +23,8 @@ pub enum SQLTypeName {
Int, Int,
Decimal, Decimal,
Date, Date,
Datetime,
Time,
Float, Float,
Bool, Bool,
Char, Char,
@ -39,22 +41,26 @@ fn parse_used_package<R: Read>(
attrs: &[OwnedAttribute], attrs: &[OwnedAttribute],
needed_types: &[&str], needed_types: &[&str],
) -> Result<UsedPackage> { ) -> Result<UsedPackage> {
let mut share_point_id = None; let mut share_point_ids = vec![];
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 check_name(&name, None, "mountPoints") {
share_point_id = get_attribute(&attrs, None, "sharePointID") if let Ok(mount_id) = get_attribute(&attrs, None, "sharePointID").map(str::to_string) {
.ok() share_point_ids.push(mount_id);
.map(str::to_string); }
} }
Ok(()) Ok(())
})?; })?;
if share_point_ids.is_empty() {
bail!("Share point mount ids not found")
}
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_ids,
needed_types: needed_types.iter().map(|s| s.to_string()).collect(), needed_types: needed_types.iter().map(|s| s.to_string()).collect(),
}) })
} }
@ -109,14 +115,16 @@ fn is_umodel_snapshot_file(filename: &str) -> bool {
fn parse_type_name(str: &str) -> Result<SQLTypeName> { fn parse_type_name(str: &str) -> Result<SQLTypeName> {
use SQLTypeName::*; use SQLTypeName::*;
Ok(match str { Ok(match &str.to_lowercase()[..] {
"decimal" => Decimal, "decimal" | "dec" => Decimal,
"char" => Char, "char" => Char,
"varchar" => Varchar, "varchar" | "string" => Varchar,
"float" => Float, "float" | "double precision" => Float, // TODO: Cheecky double precision -> float
"Integer" | "integer" | "int" => Int, "integer" | "int" => Int,
"date" => Date, "date" => Date,
"Boolean" => Bool, "datetime" => Datetime,
"time" => Time,
"boolean" => Bool,
_ => bail!("Unknown SQL type: '{}'", str), _ => bail!("Unknown SQL type: '{}'", str),
}) })
} }
@ -161,8 +169,9 @@ fn parse_primitive_types<R: Read>(
} => { } => {
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() {
let id = id.to_string();
if let Some(package) = if let Some(package) =
used_packages.iter().find(|p| p.share_point_id.eq(id)) used_packages.iter().find(|p| p.share_point_ids.contains(&id))
{ {
let package_types = parse_types_package(&mut parser)? let package_types = parse_types_package(&mut parser)?
.into_iter() .into_iter()

View File

@ -147,7 +147,7 @@ fn parse_constraint<R: Read>(
if let Some((prop_name, check_body)) = body.unwrap().split_once(" in ") { if let Some((prop_name, check_body)) = body.unwrap().split_once(" in ") {
return Ok(Some(UMLConstraint { return Ok(Some(UMLConstraint {
id, id,
class_id: Some(constrainted_element_id.context("Missing class id")?), class_id: Some(constrainted_element_id.context("Missing constraint 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()),
@ -155,9 +155,13 @@ fn parse_constraint<R: Read>(
} }
} }
if constrainted_element_id.is_none() {
return Ok(None);
}
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.unwrap()),
body: None, body: None,
class_id: None, class_id: None,
property_name: None, property_name: None,

View File

@ -1,4 +1,8 @@
body {
font-family: Arial, Helvetica, sans-serif
}
.table-column-info td, .table-column-info td,
.table-column-info th { .table-column-info th {
padding: 0.5rem; padding: 0.5rem;