diff --git a/README.md b/README.md index e69de29..636d2c7 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,39 @@ +# Ejercicio individual | (75.42) Taller de programación I + +## Alumno: Testa Santiago Tomas ( 108301 ) + +### Detalles de entrega + +El trabajo no se encuentra completo. + +Pendientes: + +1. Funcionalidad: Logica booleana en WHERE + +2. Funcionalidad: SELECT c/ ORDER BY + +3. Funcionalidad: DELETE + +4. Funcionalidad: UPDATE + +5. Testing (mas test unitarios e integración) + +6. Cargo clippy sin warnings + +7. Documentación de funciones + +Funcionalidades probadas OK + +1. SELECT * FROM + +2. SELECT columnas FROM + +3. SELECT c/ WHERE + +4. INSERT INTO + +### Detalles de implementación + +Para la lógica booleana el objetivo era crear un arbol de condiciones formado por las relaciones de precedencia. + +Dicho objetivo no pudo ser implementado. Se implementó lógica de condiciones simples ( no compuestas ) diff --git a/src/condition.rs b/src/condition.rs new file mode 100644 index 0000000..9b4be76 --- /dev/null +++ b/src/condition.rs @@ -0,0 +1,46 @@ +pub mod condition_type; +/* +pub mod complex_condition; + +use crate::libs::error; +use condition_type::BooleanOperator; +*/ + +use condition_type::ConditionOperator; + +pub struct Condition { + pub condition: ConditionOperator, + pub column: Option, + pub value: Option, +} + +pub fn build_condition(column: String, value: String, cond: ConditionOperator) -> Condition { + Condition { + condition: cond, + column: Some(column), + value: Some(value), + } +} + +pub fn operate_condition(v1: &String, v2: &String, operator: &ConditionOperator ) -> bool { + if let Ok(x1) = v1.parse::() { + if let Ok(x2) = v2.parse::() { + match operator { + ConditionOperator::Minor => return x1 < x2, + ConditionOperator::MinorEqual => return x1 <= x2, + ConditionOperator::Equal => return x1 == x2, + ConditionOperator::Higher => return x1 > x2, + ConditionOperator::HigherEqual => return x1 >= x2, + } + }; + }; + + match operator { + ConditionOperator::Minor => true, + ConditionOperator::MinorEqual => true, + ConditionOperator::Equal => v1.eq(v2), + ConditionOperator::Higher => true, + ConditionOperator::HigherEqual => true, + } +} + diff --git a/src/condition/complex_condition.rs b/src/condition/complex_condition.rs new file mode 100644 index 0000000..0f41a05 --- /dev/null +++ b/src/condition/complex_condition.rs @@ -0,0 +1,166 @@ +use crate::query::Query; +use crate::libs::error; +use super::BooleanOperator; + +pub struct ComplexCondition { + // Tree of complex conditions + pub operator: Option, + pub condition: Option, + pub left_cond: Option>, // Oldest condition ( first to be found ) + pub right_cond: Option> // Newest condition ( last to be found ) +} + +pub fn build_empty_complex_condition() -> ComplexCondition { + return ComplexCondition { + operator: None, + condition: None, + left_cond: None, + right_cond: None + } +} + +pub fn build_complex_condition(operator: BooleanOperator, left: Option>, right: Option>) -> ComplexCondition { + return ComplexCondition { + operator: Some(operator), + condition: None, + left_cond: left, + right_cond: right + } +} + +pub fn tree_check(root: ComplexCondition) -> bool { + // In Order valid check ( no None leafs ) + match &root.operator { + Some(op) => { + match op { + BooleanOperator::OR => tree_check_or_and(root), + BooleanOperator::AND => tree_check_or_and(root), + BooleanOperator::NOT => tree_check_not(root) + } + }, + None => return true + } +} + +fn tree_check_or_and(root: ComplexCondition) -> bool { + match root.left_cond { + Some(left) => { + match root.right_cond { + Some(right) => return tree_check(*left) && tree_check(*right), + None => return false + } + }, + None => return false + } +} + +fn tree_check_not(root: ComplexCondition) -> bool { + match root.left_cond { + Some(left) => { + match root.right_cond { + Some(_) => return false, + None => return tree_check(*left) + } + }, + None => return false + } +} + +pub fn add_node_to_tree(args: &Vec, start_position: &mut usize, root: ComplexCondition) -> Result { + if *start_position == args.len() || args[*start_position].eq("ORDER") { + if tree_check(root) { + Ok(root) + } else { + Err(error::WHERE_MAL_FORMATEADO) + } + } else if args[*start_position].eq("OR") { + add_node_to_tree_or(args, &mut start_position, root) + } else if args[*start_position].eq("AND") { + add_node_to_tree_and(args, &mut start_position, root) + } else if args[*start_position].eq("NOT") { + add_node_to_tree_not(args, &mut start_position, root) + } else if check_valid_args_for_basic_condition(args, *start_position) { + match root.operator { + Some(op) => { + + }, + None => return Err(error::WHERE_MAL_FORMATEADO) // C1 C2 + } + } else { + Err(error::WHERE_MAL_FORMATEADO) + } +} + +fn add_node_to_tree_or(args: &Vec, start_position: &mut usize, root: ComplexCondition) -> Result { + let new_root = build_complex_condition(BooleanOperator::OR, Some(Box::new(root)), None ); + *start_position += 1; + add_node_to_tree(args, start_position, new_root) +} + +fn add_node_to_tree_and(args: &Vec, start_position: &mut usize, root: ComplexCondition) -> Result { + match root.operator { + Some(op) => { + match op { + BooleanOperator::OR => { + match root.left_cond { + Some(x) => {}, + None => return Err(error::WHERE_MAL_FORMATEADO) + } + }, + BooleanOperator::AND => { + let new_root = build_complex_condition(BooleanOperator::AND, Some(Box::new(root)), None ); + *start_position += 1; + add_node_to_tree(args, start_position , new_root) + }, + BooleanOperator::NOT => { + let new_root = build_complex_condition(BooleanOperator::AND, Some(Box::new(root)), None ); + *start_position += 1; + add_node_to_tree(args, start_position, new_root) + }, + } + }, + None => { + let new_root = build_complex_condition(BooleanOperator::AND, Some(Box::new(root)), None ); + *start_position += 1; + add_node_to_tree(args, start_position, new_root) + } + } +} + +fn add_node_to_tree_not(args: &Vec, start_position: &mut usize, root: ComplexCondition) -> Result { + +} + +fn check_valid_args_for_basic_condition(args: &Vec, start_position: usize) -> bool { + if start_position + 3 > args.len() { + false + } else { + let first = &args[start_position]; + let second = &args[start_position + 1]; + let third = &args[start_position + 2]; + + if first.eq("AND") || first.eq("OR") || first.eq("NOT") { + false + } else if second.eq("AND") || second.eq("OR") || second.eq("NOT") { + false + } else if ! ( second.eq(">") || second.eq(">=") || second.eq("=") || second.eq("<") || second.eq("<=") ) { + false + } else if third.eq("AND") || third.eq("OR") || third.eq("NOT") { + false + } else { + true + } + } +} + +pub fn get_where_columns(query: &Query, vec: Vec, node: &ComplexCondition) -> Vec { + match node.operator { + Some(_) => {}, + None => { + match node.condition { + Some(_) => {}, + None => return vec + } + } + } +} \ No newline at end of file diff --git a/src/condition/condition_type.rs b/src/condition/condition_type.rs new file mode 100644 index 0000000..481d3ef --- /dev/null +++ b/src/condition/condition_type.rs @@ -0,0 +1,13 @@ +pub enum ConditionOperator { + Minor, // Int + MinorEqual, // Int + Equal, // Int and String + Higher, // Int + HigherEqual, // Int +} + +pub enum BooleanOperator { + AND, + OR, + NOT, +} diff --git a/src/libs.rs b/src/libs.rs new file mode 100644 index 0000000..93fd982 --- /dev/null +++ b/src/libs.rs @@ -0,0 +1,4 @@ +// Dumb file for organize everything into libs +pub mod error; +pub mod exec; +pub mod parsing; diff --git a/src/libs/error.rs b/src/libs/error.rs new file mode 100644 index 0000000..e247048 --- /dev/null +++ b/src/libs/error.rs @@ -0,0 +1,17 @@ +pub static OPERACION_INVALIDA: u32 = 1; +pub static DELETE_MAL_FORMATEADO: u32 = 2; +pub static INSERT_MAL_FORMATEADO: u32 = 3; +pub static SELECT_MAL_FORMATEADO: u32 = 4; +pub static UPDATE_MAL_FORMATEADO: u32 = 5; +pub static ARCHIVO_NO_PUDO_SER_ABIERTO: u32 = 6; +pub static ARCHIVO_VACIO: u32 = 7; +pub static ARCHIVO_NO_CONTIENE_COLUMNAS_SOLICITADAS: u32 = 8; +pub static WHERE_EN_DELETE_MAL_FORMATEADO: u32 = 9; +pub static WHERE_MAL_FORMATEADO: u32 = 10; +pub static ORDER_BY_MAL_FORMATEADO: u32 = 11; +pub static NO_WHERE: u32 = 12; + +pub fn print_err(error_code: u32) { + // TODO print error + println!("ERROR. Codigo: {}", error_code) +} diff --git a/src/libs/exec.rs b/src/libs/exec.rs new file mode 100644 index 0000000..6d382f4 --- /dev/null +++ b/src/libs/exec.rs @@ -0,0 +1,253 @@ +use std::fs::{File, OpenOptions}; +use std::io::{BufRead, BufReader, Write}; + +use crate::condition::{self, operate_condition, Condition}; +//use crate::condition::operate_condition; +use crate::query::query_type::QueryType; +use crate::query::Query; + +use super::parsing::get_file_first_line; + +pub fn exec_query(query: Query) { + match &query.operation { + Some(op) => match op { + QueryType::DELETE => exec_query_delete(query), + QueryType::INSERT => exec_query_insert(query), + QueryType::SELECT => exec_query_select(query), + QueryType::UPDATE => exec_query_update(query), + }, + None => println!("Error en el programa"), + } +} + +fn exec_query_delete(query: Query) { + // TODO +} + +fn exec_query_insert(query: Query) { + match &query.table { + Some(table) => { + let total_columns = find_total_columns(&query); + let write_columns = find_print_columns(&query); + match &mut OpenOptions::new().append(true).open(table) { + Ok(file) => { + let mut write_line = String::new(); + let mut counter = 0; + let mut writen_elements = 0; + while counter < total_columns { + if write_columns.contains(&counter) { + if let Some(x) = &query.values { + write_line.push_str(&x[writen_elements].to_string()); + writen_elements += 1; + } + } + + counter += 1; + if counter < total_columns { + write_line.push(','); + } + } + + write_line.push('\n'); + let _ = file.write(write_line.as_bytes()); + } + Err(_) => println!("ERROR en el programa"), + } + } + None => println!("ERROR en el programa"), + } +} + +fn exec_query_select(query: Query) { + match &query.order_by { + Some(_) => exec_query_select_order_by(query), + None => { + let col_index = find_filter_column(&query); + let print_columns: Vec = find_print_columns(&query); + read_and_print_file(&query, col_index, &print_columns); + } + } +} + +fn find_total_columns(query: &Query) -> usize { + match get_file_first_line(query) { + Some(line) => line_to_vec(&line).len(), + None => 0, + } +} + +fn find_print_columns(query: &Query) -> Vec { + let mut result: Vec = Vec::new(); + match &query.columns { + Some(cols) => match get_file_first_line(query) { + Some(line) => { + let table_columns: Vec = line_to_vec(&line); + for element in cols { + let mut counter = 0; + while !table_columns[counter].eq(element) && counter < table_columns.len() { + counter += 1; + } + if counter < table_columns.len() { + result.push(counter); + } + } + + result + } + None => result, + }, + None => result, + } +} + +fn find_filter_column(query: &Query) -> i32 { + let mut col_index_filter = -1; + match &query.where_condition { + Some(x) => match &x.column { + Some(column) => match &query.table { + Some(table) => match File::open(table) { + Ok(file) => { + let mut reader: BufReader = BufReader::new(file); + let mut line = String::new(); + match reader.read_line(&mut line) { + Ok(_) => { + line = line.replace('\n', ""); + let mut split = line.split(','); + let mut element_opt = split.next(); + let mut counter = 0; + + while element_opt.is_some() && col_index_filter < 0 { + if let Some(x) = element_opt { + if x.eq(column) { + col_index_filter = counter; + } + } + counter += 1; + element_opt = split.next(); + } + } + Err(_) => col_index_filter = -1, + } + } + Err(_) => return -1, + }, + None => return -1, + }, + None => { + col_index_filter = -1; + } + }, + None => col_index_filter = -1, + } + col_index_filter +} + +fn read_and_print_file(query: &Query, col_filter: i32, columns: &[usize]) { + match &query.table { + Some(table) => match File::open(table) { + Ok(f) => { + let mut reader: BufReader = BufReader::new(f); + let mut line = String::new(); + + // Print header + /* match reader.read_line(&mut line) { + Ok(_) => { + line = line.replace('\n', ""); + println!("{}",line); + line.clear(); + }, + Err(_) => println!("Error en el programa") // TODO + } + */ + // Print other lines + let mut read = true; + while read { + match reader.read_line(&mut line) { + Ok(x) => { + line = line.replace('\n', ""); + read = x != 0; + if read { + let elements = line_to_vec(&line); + match &query.where_condition { + Some(condition) => print_file_conditioned(columns, (col_filter, condition), &elements), + None => print_file_unconditional(columns, &elements), + } + line.clear(); + } + } + Err(_) => println!("Error en el programa"), + } + } + } + Err(_) => println!("Error en el programa"), + }, + None => println!("Error en el programa "), + } +} + +fn print_file_unconditional(columns: &[usize], elements: &[String]) { + // Pre: Columns vector sorted incremental && Elements of line content vector + // Post: print to stdout the correct columns + + let mut first = true; + if columns.is_empty() { // SELECT * FROM + for (counter, element) in elements.iter().enumerate() { + if first { + print!("{}", element); + first = false; + } else { + print!(",{}", element); + } + } + } else { // SELECT columns FROM + let mut counter = 0; + while counter < elements.len() { + if columns.contains(&counter) { + if first { + print!("{}", elements[counter]); + first = false; + } else { + print!(",{}", elements[counter]); + } + } + + counter += 1; + } + } + println!(); +} + +fn print_file_conditioned(columns: &[usize], filter: (i32, &Condition), elements: &[String]) { + let (col_filter,condition) = filter; + if let Some(value) = &condition.value { + if operate_condition(&elements[col_filter as usize], value, &condition.condition) { + print_file_unconditional(columns, elements) + } + } else { + print_file_unconditional(columns, elements) + } +} + +fn line_to_vec(line: &str) -> Vec { + let mut result: Vec = Vec::new(); + let mut split = line.split(','); + + let mut element_opt = split.next(); + while element_opt.is_some() { + match element_opt { + Some(x) => { + result.push(x.to_string()); + } + None => continue, + } + + element_opt = split.next(); + } + result +} + +fn exec_query_select_order_by(query: Query) {} + +fn exec_query_update(query: Query) { + // TODO +} diff --git a/src/libs/parsing.rs b/src/libs/parsing.rs new file mode 100644 index 0000000..df35d39 --- /dev/null +++ b/src/libs/parsing.rs @@ -0,0 +1,600 @@ +use std::fs::File; +use std::io::{BufRead, BufReader}; + +use crate::condition::build_condition; +use crate::condition::condition_type::ConditionOperator; +//use crate::condition::{add_node_to_tree, build_complex_condition, build_condition, build_empty_complex_condition, get_where_columns, tree_check, ComplexCondition, Condition}; +// use crate::condition::Condition; +use crate::libs::error; +use crate::query::build_empty_query; +use crate::query::query_type::QueryType; +use crate::query::{Query, DELETE_MIN_LEN, INSERT_MIN_LEN, SELECT_MIN_LEN, UPDATE_MIN_LEN}; + +use super::error::SELECT_MAL_FORMATEADO; + +pub fn build_query(text_query: &String, path: &String) -> Result { + // Full "Compilation" process of query. Tokenization, sintactic analysis, semantic and build + let args = text_to_vec(text_query); + match vec_to_query(&args, path) { + Ok(x) => validate_query(x), + Err(x) => Err(x), + } +} + +fn merge_table_and_path(path: &String, table: &str) -> String { + let mut table_full: String = path.to_string(); + table_full.push('/'); + table_full.push_str(table); + table_full.push_str(".csv"); + + table_full +} + +fn separate_args_delete(args: &[String], path: &String, result: &mut Query) -> Result { + if args.len() < DELETE_MIN_LEN { + Err(error::DELETE_MAL_FORMATEADO) + } else { + result.table = Some(merge_table_and_path(path, &args[2])); + + if args.len() != DELETE_MIN_LEN { + return Err(error::DELETE_MAL_FORMATEADO); + } else { + /* TODO: Code for complex conditions + match add_node_to_tree(args, &mut 4, build_empty_complex_condition()) { + Ok(cond) => result.where_condition = Some(cond), + Err(x) => return Err(x) + } + */ + + if args[5].eq("<") { + result.where_condition = Some(build_condition( + args[4].to_string(), + args[6].to_string(), + ConditionOperator::Minor, + )) + } else if args[5].eq("<=") { + result.where_condition = Some(build_condition( + args[4].to_string(), + args[6].to_string(), + ConditionOperator::MinorEqual, + )) + } else if args[5].eq("=") { + result.where_condition = Some(build_condition( + args[4].to_string(), + args[6].to_string(), + ConditionOperator::Equal, + )) + } else if args[5].eq(">=") { + result.where_condition = Some(build_condition( + args[4].to_string(), + args[6].to_string(), + ConditionOperator::HigherEqual, + )) + } else if args[5].eq(">") { + result.where_condition = Some(build_condition( + args[4].to_string(), + args[6].to_string(), + ConditionOperator::Higher, + )) + } else { + return Err(error::WHERE_MAL_FORMATEADO); + } + } + Ok(0) + } +} + +fn separate_args_insert(args: &[String], path: &String, result: &mut Query) -> Result { + if args.len() < INSERT_MIN_LEN { + Err(error::INSERT_MAL_FORMATEADO) + } else { + result.table = Some(merge_table_and_path(path, &args[2])); + + let mut columns: Vec = Vec::new(); + let mut counter = 3; + while counter < args.len() && !args[counter].eq("VALUES") { + columns.push(args[counter].to_string()); + counter += 1; + } + + counter += 1; + let mut values: Vec = Vec::new(); + while counter < args.len() { + values.push(args[counter].to_string()); + counter += 1; + } + + result.columns = Some(columns); + result.values = Some(values); + Ok(0) + } +} + +fn separate_args_update(args: &[String], path: &String, result: &mut Query) -> Result { + if args.len() < UPDATE_MIN_LEN { + result.table = Some(merge_table_and_path(path, &args[1])); + + let mut counter = 3; + let mut columns: Vec = Vec::new(); + let mut values: Vec = Vec::new(); + + while counter < args.len() && !args[counter].eq("WHERE") { + if counter % 3 == 0 { + columns.push(args[counter].to_string()); + } else if (counter % 3) == 2 { + values.push(args[counter].to_string()); + } + counter += 1; + } + + result.columns = Some(columns); + result.values = Some(values); + + /* TODO: Code for complex conditions + counter += 1; + match add_node_to_tree(args, &mut counter, build_empty_complex_condition()) { + Ok(cond) => result.where_condition = Some(cond), + Err(x) => return Err(x) + } + */ + Ok(0) + } else { + Err(error::UPDATE_MAL_FORMATEADO) + } +} + +fn separate_args_select(args: &[String], path: &String, result: &mut Query) -> Result { + if args.len() < SELECT_MIN_LEN { + Err(error::SELECT_MAL_FORMATEADO) + } else { + let mut columns: Vec = Vec::new(); + let mut counter = 1; + while counter < args.len() && !args[counter].eq("FROM") { + columns.push(args[counter].to_string()); + counter += 1; + } + counter += 1; + + if !&columns[0].eq("*") { + result.columns = Some(columns); + } + + result.table = Some(merge_table_and_path(path, &args[counter])); + counter += 1; + + if counter == args.len() { + Ok(0) + } else { + /* TODO: Code for complex conditions + match add_node_to_tree(args, &mut counter, build_empty_complex_condition()) { + Ok(cond) => result.where_condition = Some(cond), + Err(x) => return Err(x) + } + */ + if counter + 3 > args.len() { + return Err(SELECT_MAL_FORMATEADO); + } else if args[counter].eq("WHERE") { + counter += 1; + if args[counter + 1].eq("<") { + result.where_condition = Some(build_condition( + args[counter].to_string(), + args[counter + 2].to_string(), + ConditionOperator::Minor, + )); + counter += 3; + } else if args[counter + 1].eq("<=") { + result.where_condition = Some(build_condition( + args[counter].to_string(), + args[counter + 2].to_string(), + ConditionOperator::MinorEqual, + )); + counter += 3; + } else if args[counter + 1].eq("=") { + result.where_condition = Some(build_condition( + args[counter].to_string(), + args[counter + 2].to_string(), + ConditionOperator::Equal, + )); + counter += 3; + } else if args[counter + 1].eq(">=") { + result.where_condition = Some(build_condition( + args[counter].to_string(), + args[counter + 2].to_string(), + ConditionOperator::HigherEqual, + )); + counter += 3; + } else if args[counter + 1].eq(">") { + result.where_condition = Some(build_condition( + args[counter].to_string(), + args[counter + 2].to_string(), + ConditionOperator::Higher, + )); + counter += 3; + } else { + return Err(error::WHERE_MAL_FORMATEADO); + } + } + + if counter < args.len() { + if args[counter].eq("ORDER") && args[counter + 1].eq("BY") { + if counter + 3 == args.len() { + // ORDER BY column + result.order_by = Some((args[counter + 2].to_string(), true)); + } else if counter + 4 == args.len() { + // ORDER BY column ASC/DESC + if args[counter + 3].eq("ASC") || args[counter + 3].eq("DESC") { + result.order_by = + Some((args[counter + 2].to_string(), args[counter + 2].eq("ASC"))); + } else { + return Err(error::ORDER_BY_MAL_FORMATEADO); + } + } else { + return Err(error::ORDER_BY_MAL_FORMATEADO); + } + } else { + return Err(error::ORDER_BY_MAL_FORMATEADO); + } + } + Ok(0) + } + } +} + +fn check_operation_format(args: &Vec) -> Result { + // Check correct operation sintaxis + let operation = &args[0]; + if operation.eq("DELETE") { + check_delete_format(args) + } else if operation.eq("INSERT") { + check_insert_format(args) + } else if operation.eq("SELECT") { + check_select_format(args) + } else if operation.eq("UPDATE") { + check_update_format(args) + } else { + Err(error::OPERACION_INVALIDA) + } +} + +fn check_delete_format(args: &[String]) -> Result { + let non_valid_keywords = vec![ + "INSERT", "INTO", "VALUES", "SELECT", "ORDER", "BY", "UPDATE", "SET", + ]; + if args[1].eq("FROM") + && args[3].eq("WHERE") + && check_non_valid_keywords(args, non_valid_keywords) + { + Ok(QueryType::DELETE) + } else { + Err(error::DELETE_MAL_FORMATEADO) + } +} + +fn check_insert_format(args: &[String]) -> Result { + let non_valid_keywords = vec![ + "DELETE", "FROM", "SELECT", "ORDER", "BY", "UPDATE", "SET", "WHERE", "AND", "OR", "NOT", + ]; + if args[1].eq("INTO") && check_non_valid_keywords(args, non_valid_keywords) { + if (args.len() - 4) % 2 != 0 { + Err(2) + } else { + let mut counter = 0; + let correct_value_len = (args.len() - 4) / 2; // INSERT INTO tabla a b c VALUES x y z. Len = 10. Correct len = 3. + while counter < correct_value_len && !args[3 + counter].eq("VALUES") { + counter += 1; + } + + if counter == correct_value_len && args[3 + counter].eq("VALUES") { + Ok(QueryType::INSERT) + } else { + Err(error::INSERT_MAL_FORMATEADO) + } + } + } else { + Err(error::INSERT_MAL_FORMATEADO) + } +} + +fn check_select_format(args: &[String]) -> Result { + let non_valid_keywords = vec!["DELETE", "INSERT", "INTO", "VALUES", "UPDATE", "SET"]; + if check_non_valid_keywords(args, non_valid_keywords) { + // TODO format select + Ok(QueryType::SELECT) + } else { + Err(error::SELECT_MAL_FORMATEADO) + } +} + +fn check_update_format(args: &[String]) -> Result { + let non_valid_keywords = vec![ + "DELETE", "FROM", "INSERT", "INTO", "SELECT", "VALUES", "ORDER", "BY", + ]; + if args[2].eq("SET") + && args.contains(&"WHERE".to_string()) + && check_non_valid_keywords(args, non_valid_keywords) + { + Ok(QueryType::UPDATE) + } else { + Err(error::UPDATE_MAL_FORMATEADO) + } +} + +fn check_non_valid_keywords(args: &[String], non_valid_keywords: Vec<&str>) -> bool { + // Checks if args contains any non valid keyword + let mut result = true; + let mut counter = 0; + + while counter < args.len() && result { + if non_valid_keywords.contains(&args[counter].as_str()) { + result = false; + } else { + counter += 1; + } + } + + result +} + +fn validate_query(query: Query) -> Result { + // Pre: Sintactical query OK + // Post: Valid query for execution + match &query.table { + Some(table) => match File::open(table) { + Ok(_) => {} + Err(_) => return Err(error::ARCHIVO_NO_PUDO_SER_ABIERTO), + }, + None => return Err(error::ARCHIVO_NO_PUDO_SER_ABIERTO), + } + + match &query.operation { + Some(op) => match op { + QueryType::DELETE => validate_delete_query(query), + QueryType::INSERT => validate_insert_query(query), + QueryType::SELECT => validate_select_query(query), + QueryType::UPDATE => validate_update_query(query), + }, + None => Err(error::OPERACION_INVALIDA), + } +} + +fn validate_delete_query(query: Query) -> Result { + if query.columns.is_none() && query.values.is_none() && query.where_condition.is_some() { + match &query.table { + Some(table) => match File::open(table) { + Ok(file) => match get_columns(file) { + Some(columns) => check_columns_contains_condition(columns, query), + None => Err(error::ARCHIVO_VACIO), + }, + Err(_) => Err(error::ARCHIVO_NO_PUDO_SER_ABIERTO), + }, + None => Err(error::ARCHIVO_NO_PUDO_SER_ABIERTO), + } + } else { + Err(error::DELETE_MAL_FORMATEADO) + } +} + +fn validate_insert_query(query: Query) -> Result { + // TODO + Ok(query) +} + +fn validate_select_query(query: Query) -> Result { + // TODO + Ok(query) +} + +fn validate_update_query(query: Query) -> Result { + // TODO + Ok(query) +} + +fn get_columns(file: File) -> Option> { + let mut result: Vec = Vec::new(); + let mut reader: BufReader = BufReader::new(file); + let mut line = String::new(); + match reader.read_line(&mut line) { + Ok(_) => { + let mut split = line.split(','); + let mut element_opt = split.next(); + while element_opt.is_some() { + match element_opt { + Some(x) => result.push(x.to_string()), + None => continue, + } + element_opt = split.next(); + } + + Some(result) + } + Err(_) => None, + } +} + +fn check_columns_contains_condition(columns: Vec, query: Query) -> Result { + // TODO + match get_where_columns(&query) { + Some(column) => { + if columns.contains(&column) { + Ok(query) + } else { + Err(error::ARCHIVO_NO_CONTIENE_COLUMNAS_SOLICITADAS) + } + } + None => Err(error::NO_WHERE), + } +} + +fn get_where_columns(query: &Query) -> Option { + match &query.where_condition { + Some(cond) => match &cond.column { + Some(col) => Some(col.to_string()), + None => None, + }, + None => None, + } +} + +fn text_to_vec(text_query: &String) -> Vec { + // Text to vector. Tokenization by space, new line & coma + let mut tmp_text_query = text_query.to_string(); + tmp_text_query = tmp_text_query.replace('\n', " "); + tmp_text_query = tmp_text_query.replace(',', ""); + tmp_text_query = tmp_text_query.replace(';', ""); + let mut split = tmp_text_query.split(' '); + + let mut result: Vec = Vec::new(); + let mut element_opt = split.next(); + while element_opt.is_some() { + match element_opt { + Some(x) => { + result.push(x.to_string()); + } + None => continue, + } + + element_opt = split.next(); + } + result +} + +fn vec_to_query(args: &Vec, path: &String) -> Result { + // Vec to non validated query + let mut result: Query = build_empty_query(); + match check_operation_format(args) { + Ok(x) => match &x { + QueryType::DELETE => match separate_args_delete(args, path, &mut result) { + Ok(_) => result.operation = Some(x), + Err(x) => return Err(x), + }, + QueryType::INSERT => match separate_args_insert(args, path, &mut result) { + Ok(_) => result.operation = Some(x), + Err(x) => return Err(x), + }, + QueryType::SELECT => match separate_args_select(args, path, &mut result) { + Ok(_) => result.operation = Some(x), + Err(x) => return Err(x), + }, + QueryType::UPDATE => match separate_args_update(args, path, &mut result) { + Ok(_) => result.operation = Some(x), + Err(x) => return Err(x), + }, + }, + Err(x) => return Err(x), + } + + Ok(result) +} + +pub fn get_file_first_line(query: &Query) -> Option { + match &query.table { + Some(table) => match File::open(table) { + Ok(f) => { + let mut reader: BufReader = BufReader::new(f); + let mut line = String::new(); + + match reader.read_line(&mut line) { + Ok(_) => { + line = line.replace('\n', ""); + Some(line) + } + Err(_) => None, + } + } + Err(_) => None, + }, + None => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_text_to_vec1() { + let rt1 = text_to_vec(&String::from("SELECT * FROM table")); + assert_eq!(rt1, vec!["SELECT", "*", "FROM", "table"]); + } + + #[test] + fn test_text_to_vec2() { + let rt2 = text_to_vec(&String::from( + "SELECT id, producto, id_cliente\nFROM ordenes\nWHERE cantidad > 1", + )); + assert_eq!( + rt2, + vec![ + "SELECT", + "id", + "producto", + "id_cliente", + "FROM", + "ordenes", + "WHERE", + "cantidad", + ">", + "1" + ] + ); + } +} + +/* +fn check_where_format(args: &Vec, start_position: usize) -> bool { + let mut result = true; + + let mut counter: usize = start_position; + + let mut not_detected: bool = false; + let mut op_detected: bool = false; + + while counter < args.len() && result { + if args[counter].eq("NOT") { + if not_detected || op_detected { + result = false; // NOT NOT. Que estas haciendo ? + } else { + not_detected = true; + counter += 1; + } + } else if args[counter].eq("AND") || args[counter].eq("OR") { + if op_detected { + result = false; // AND OR , OR AND, OR OR, AND AND. Que estas haciendo ? + } else { + op_detected = true; + counter += 1; + } + } else if counter + 3 > args.len() { + result = false; + } else { + + } + } + + result +} + +fn check_table_exist(path: &String, args: &Vec, operation: &QueryType) -> Result{ + // Asume query bien formateado + let mut table= String::from(path); + table.push('/'); + match &operation { + QueryType::DELETE => table.push_str(&args[2]), + QueryType::INSERT => table.push_str(&args[2]), + QueryType::SELECT => { + // Debo encontrar cual es la tabla (asumiendo query bien formateado) + let mut counter = 2; + while ! args[counter].eq("FROM"){ + counter += 1; + } + table.push_str(&args[counter + 1]) + }, + QueryType::UPDATE => table.push_str(&args[1]), + } + + match File::open(&table) { // TODO: Path::exist()? + Ok(_) => return Ok(table), + Err(_) => return Err(error::ARCHIVO_NO_PUDO_SER_ABIERTO) + } +} +*/ diff --git a/src/main.rs b/src/main.rs index e7a11a9..97d3f98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,21 @@ +pub mod condition; +pub mod libs; +pub mod query; + +use libs::error::print_err; +use libs::exec::exec_query; +use libs::parsing::build_query; + fn main() { - println!("Hello, world!"); + let args: Vec = std::env::args().collect(); + if args.len() < 3 { + println!("Uso: cargo run -- ruta/a/tablas \"query\""); + } else { + let path: &String = &args[1]; + let text_query: &String = &args[2]; + match build_query(text_query, path) { + Ok(x) => exec_query(x), + Err(x) => print_err(x), + } + } } diff --git a/src/query.rs b/src/query.rs new file mode 100644 index 0000000..e15545f --- /dev/null +++ b/src/query.rs @@ -0,0 +1,32 @@ +pub mod query_type; + +use query_type::QueryType; + +use crate::condition::Condition; + +// use crate::condition::ComplexCondition; + +pub static DELETE_MIN_LEN: usize = 7; // DELETE FROM tabla WHERE a < b +pub static INSERT_MIN_LEN: usize = 6; // INSERT INTO tabla col VALUES val +pub static SELECT_MIN_LEN: usize = 4; // SELECT * FROM tabla +pub static UPDATE_MIN_LEN: usize = 10; // UPDATE tabla SET col = valor WHERE a < b + +pub struct Query { + pub operation: Option, // DELETE, INSERT, SELECT, UPDATE + pub table: Option, // DELETE, INSERT, SELECT, UPDATE + pub columns: Option>, // INSERT, SELECT, UPDATE + pub where_condition: Option, // DELETE (always), SELECT (sometimes), UPDATE (always) . + pub order_by: Option<(String, bool)>, // SELECT (sometimes) + pub values: Option>, // INSERT, UPDATE (in update is set) +} + +pub fn build_empty_query() -> Query { + Query { + operation: None, + table: None, + columns: None, + where_condition: None, + order_by: None, + values: None, + } +} diff --git a/src/query/query_type.rs b/src/query/query_type.rs new file mode 100644 index 0000000..8cfa19b --- /dev/null +++ b/src/query/query_type.rs @@ -0,0 +1,18 @@ +use core::fmt; +pub enum QueryType { + DELETE, + INSERT, + SELECT, + UPDATE, +} + +impl std::fmt::Display for QueryType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + QueryType::DELETE => write!(f, "DELETE"), + QueryType::INSERT => write!(f, "INSERT"), + QueryType::SELECT => write!(f, "SELECT"), + QueryType::UPDATE => write!(f, "UPDATE"), + } + } +}