use rustyline::DefaultEditor;
use std::io::{self, Error};
use std::ops::Deref;
use crate::database::{
category::DataTypeCategory,
databasetype::{DatabaseType, DatabaseTypeTrait, PostgresDatabaseType},
Database, MySqlDatabaseType, PostgresTypes,
};
pub struct MigrationAndStruct {
pub up_sql_contents: String,
pub rust_struct_contents: String,
}
pub fn column_loop(num_columns: i32, migration_name: String) -> Result<MigrationAndStruct, Error> {
let mut rl = DefaultEditor::new().unwrap_or_else(|why| {
panic!("Failed to create rustyline editor: {}", why);
});
let mut column_string = String::new();
let database = Database::get_database_from_rustyroad_toml()
.expect("Failed to get database from rustyroad.toml");
let database_type = database.database_type;
println!("Database Type: {:?}", database_type);
let mut struct_fields = String::new();
let struct_name = migration_name
.chars()
.next()
.unwrap()
.to_uppercase()
.to_string()
+ &migration_name[1..];
for _ in 0..num_columns {
let column_name = rl.readline("Enter the name of the column: ").unwrap();
let mut all_available_db_types: Vec<DataTypeCategory> =
DataTypeCategory::get_all_categories();
all_available_db_types.sort();
println!("Column Types: 1");
for (index, column_type) in all_available_db_types.iter().enumerate() {
println!("{}. {}", index + 1, column_type);
}
let column_type_input = rl
.readline("Select the category that contains your type. ")
.unwrap();
let column_type_index = column_type_input.trim().parse::<usize>().unwrap();
let data_type_category = all_available_db_types
.get(column_type_index - 1)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Invalid input"))?;
println!("You selected 1: {}", data_type_category.clone());
println!("Database Type: {:?}", database_type);
let data_types_for_category = data_type_category
.clone()
.get_data_types_from_data_type_category(database_type.clone());
let database_types = match database_type {
DatabaseType::Postgres => PostgresDatabaseType
.get_database_types(&data_types_for_category, data_type_category),
DatabaseType::Mysql => {
MySqlDatabaseType.get_database_types(&data_types_for_category, data_type_category)
}
DatabaseType::Sqlite => todo!("Implement SqliteDatabaseType.get_database_types"),
DatabaseType::Mongo => todo!("Implement MongoDatabaseType.get_database_types"),
};
let database_types_hash_map = database_types
.iter()
.map(|x| x.clone().postgres)
.collect::<Vec<_>>()
.iter()
.map(|x| x.types.clone())
.collect::<Vec<_>>();
let database_type_impl_iter_option = database_types_hash_map
.iter()
.map(|x| x.get(&data_type_category.to_string()));
let impl_iter_db_types = database_type_impl_iter_option.into_iter();
let value4 = impl_iter_db_types.map(|x| x.unwrap());
let types_for_category = value4.collect::<Vec<_>>();
let all_postgres_types = types_for_category
.iter()
.flat_map(|types_vec| types_vec.iter())
.cloned()
.collect::<Vec<PostgresTypes>>();
for (index, x) in all_postgres_types.iter().enumerate() {
println!("{}. {:?}", index + 1, x);
}
let new_type_input = rl
.readline("Enter the type of the column: ")
.unwrap_or_else(|why| {
panic!("Failed to get user input: {}", why);
});
let new_type_index = new_type_input
.trim()
.parse::<usize>()
.unwrap_or_else(|why| {
panic!("Failed to parse user input: {}", why);
});
println!("You selected 1: {}", new_type_index);
let selected_type = match database_type {
DatabaseType::Postgres => {
let postgres_types = database_types
.iter()
.map(|x| x.clone().postgres)
.collect::<Vec<_>>()
.iter()
.map(|x| x.types.clone())
.collect::<Vec<_>>();
let postgres_types_hash_map = postgres_types
.iter()
.map(|x| x.get(&data_type_category.to_string()));
let postgres_types_hash_map_iter =
postgres_types_hash_map.map(|x| x.unwrap().get(new_type_index - 1).unwrap());
let postgres_types_hash_map_iter_clone = postgres_types_hash_map_iter.cloned();
postgres_types_hash_map_iter_clone.collect::<Vec<_>>()
}
DatabaseType::Mysql => todo!("Implement MySqlDatabaseType column_type mapping"),
DatabaseType::Sqlite => todo!("Implement SqliteDatabaseType column_type mapping"),
DatabaseType::Mongo => todo!("Implement MongoDatabaseType column_type mapping"),
};
println!("Type You selected: {:?}", selected_type[0].clone());
let nullable_input = rl.readline("Is the column nullable? (y/n): ").unwrap();
let nullable = match nullable_input.trim().to_lowercase().as_str() {
"y" => "NULL",
"n" => "NOT NULL",
_ => return Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid input")),
};
println!("Enter column constraints (e.g. PRIMARY KEY, UNIQUE, NOT NULL, DEFAULT, CHECK, REFERENCES): ");
let mut column_constraints = String::new();
io::stdin()
.read_line(&mut column_constraints)
.expect("Failed to read line");
match data_type_category {
DataTypeCategory::Array => {
let array_type_index = selected_type[0].clone();
match array_type_index {
PostgresTypes::Array(thatthis) => {
let array_type = thatthis.deref();
column_string.push_str(&format!(
"{} {:?}[] {} {},",
column_name, array_type, nullable, column_constraints
));
}
_ => return Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid input")),
}
struct_fields.push_str(&format!(
" pub {}: Vec<{}>,\n",
column_name.to_lowercase(),
selected_type[0] ));
}
_ => {
column_string.push_str(&format!(
r#"{} {:?} {} {},"#,
column_name,
selected_type[0].clone(),
nullable,
column_constraints
));
struct_fields.push_str(&format!(
" pub {}: {},\n",
column_name.to_lowercase(),
selected_type[0] ));
}
}
println!("Column type selected: {:?}", selected_type);
}
let up_sql_contents = format!(
r#"
CREATE TABLE {} (
{}
);"#,
migration_name,
column_string.trim_end_matches(',')
);
let struct_contents = format!(
r#"
pub struct {} {{
{}
}}"#,
struct_name,
struct_fields.trim_end_matches(',')
);
let new_migration_struct = MigrationAndStruct {
up_sql_contents,
rust_struct_contents: struct_contents,
};
Ok(new_migration_struct)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_column_loop() {
let num_columns = 2;
let migration_name = "test_migration".to_string();
let result = column_loop(num_columns, migration_name);
assert!(result.is_ok());
}
}