use assembly_data::{ fdb::{ common::{self, Latin1String}, core, store, }, xml::{ common::{expect_decl, expect_end}, database::{ expect_column_or_end_columns, expect_columns, expect_database, expect_row_or_end_rows, expect_rows, expect_table, ValueType, }, }, }; use color_eyre::eyre::WrapErr; use std::{ collections::HashMap, fs::File, io::{BufReader, BufWriter}, path::PathBuf, time::Instant, }; use quick_xml::Reader; use structopt::StructOpt; #[derive(Debug, StructOpt)] /// Converts an XML database into a FDB file struct Options { /// The XML database file src: PathBuf, /// The FDB file dest: PathBuf, } fn main() -> color_eyre::Result<()> { color_eyre::install()?; let opts = Options::from_args(); let src_file = File::open(&opts.src) .wrap_err_with(|| format!("Failed to open input file '{}'", opts.src.display()))?; let reader = BufReader::new(src_file); let dest_file = File::create(&opts.dest) .wrap_err_with(|| format!("Failed to crate output file '{}'", opts.dest.display()))?; let mut dest_out = BufWriter::new(dest_file); println!("Copying file, this may take a few seconds..."); let start = Instant::now(); let mut dest_db = store::Database::new(); let mut xml = Reader::from_reader(reader); let xml = xml.trim_text(true); let mut buf = Vec::new(); let buf = &mut buf; expect_decl(xml, buf)?; let db_name = expect_database(xml, buf)?.unwrap(); println!("Loading database: '{}'", db_name); while let Some(table_name) = expect_table(xml, buf)? { println!("table '{}'", table_name); let mut dest_table = store::Table::new(128); expect_columns(xml, buf)?; let mut col_map = HashMap::new(); while let Some(col) = expect_column_or_end_columns(xml, buf)? { let data_type = match col.r#type { ValueType::Bit => common::ValueType::Boolean, ValueType::Float => common::ValueType::Float, ValueType::Real => common::ValueType::Float, ValueType::Int => common::ValueType::Integer, ValueType::BigInt => common::ValueType::BigInt, ValueType::SmallInt => common::ValueType::Integer, ValueType::TinyInt => common::ValueType::Integer, ValueType::Binary => common::ValueType::Text, ValueType::VarBinary => common::ValueType::Text, ValueType::Char => common::ValueType::Text, ValueType::VarChar => common::ValueType::Text, ValueType::NChar => common::ValueType::Text, ValueType::NVarChar => common::ValueType::Text, ValueType::NText => common::ValueType::VarChar, ValueType::Text => common::ValueType::VarChar, ValueType::Image => common::ValueType::VarChar, ValueType::DateTime => common::ValueType::BigInt, }; if col_map.is_empty() { // first col if data_type == common::ValueType::Float { let id_col_name = format!("{}ID", table_name); dest_table.push_column( Latin1String::encode(&id_col_name), common::ValueType::Integer, ); col_map.insert(id_col_name, col_map.len()); } } dest_table.push_column(Latin1String::encode(&col.name), data_type); col_map.insert(col.name, col_map.len()); } expect_rows(xml, buf)?; let col_count = dest_table.columns().len(); let mut auto_inc = 0; while let Some(row) = expect_row_or_end_rows(xml, buf, true)? { let mut fields = vec![core::Field::Nothing; col_count]; let mut pk = None; for (key, src_value) in row { let col_index = *col_map.get(&key).unwrap(); let value_type = dest_table.columns().get(col_index).unwrap().value_type(); let dest_value = match value_type { common::ValueType::Nothing => core::Field::Nothing, common::ValueType::Integer => core::Field::Integer(src_value.parse().unwrap()), common::ValueType::Float => core::Field::Float(src_value.parse().unwrap()), common::ValueType::Text => core::Field::Text(src_value), common::ValueType::Boolean => core::Field::Boolean(&src_value != "0"), common::ValueType::BigInt => core::Field::BigInt(src_value.parse().unwrap()), common::ValueType::VarChar => core::Field::VarChar(src_value), }; if col_index == 0 { match &dest_value { core::Field::Integer(i) => { pk = Some((*i % 128) as usize); } core::Field::BigInt(i) => { pk = Some((*i % 128) as usize); } core::Field::Text(text) => { pk = Some((hsieh_hash::digest(text.as_bytes()) % 128) as usize); } _ => panic!("Can't use {:?} as PK", &dest_value), } } fields[col_index] = dest_value; } let pk = if let Some(pk) = pk { pk } else { auto_inc += 1; fields[0] = core::Field::Integer(auto_inc); (auto_inc as usize) % 128 }; dest_table.push_row(pk, &fields); } expect_end(xml, buf, "table")?; dest_db.push_table(Latin1String::encode(&table_name), dest_table); } dest_db .write(&mut dest_out) .wrap_err("Failed to write copied database")?; let duration = start.elapsed(); println!( "Finished in {}.{}s", duration.as_secs(), duration.subsec_millis() ); Ok(()) }