diff --git a/README.md b/README.md index e69de29..da1865b 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,37 @@ +# Ejercicio individual | (75.42) Taller de programación I + +## Alumno: Testa Santiago Tomas ( 108301 ) + +### Detalles de entrega + +Pendientes: + +1. Testing (mas test unitarios e integración) + +2. Documentación de funciones + +3. Refactor de funciones para que tengan menos de 30 lineas. Por una cuestión de tiempos se deja para el final. + +6. Terminar trabajo sobre el parseo ( TODOs & funcion para imprimir errores ). Se decide para poder avanzar con las funcionalidades implementar las mismas tal que, en caso de haber un error, simplemente no ejecutarán nada. + +Funcionalidades probadas OK + +1. SELECT + +2. INSERT INTO + +3. UPDATE + +4. DELETE FROM + +### Detalles de implementación + +Para la logica del programa se decidio dividir en dos etapas: parseo y ejecucción. El punto del parseo es que se encuentre cualquier error previo a ejecucción del query, tal que no sea posible dañar la base de datos por error humano. De encontrarse un error se debe imprimir + +Logica booleana: Se implemento por medio de un vector de vectores. Por cada OR se pushea un nuevo vector, donde en este vector se pushearán las expresiones simples y las negaciones ( los ANDs no son pusheados, ya que sería redundante ). + +Esta implementación tiene como problema que un query con el condicional WHERE A AND AND AND {...} B resulta en WHERE A AND B. Como solución simple se planteó un chequeo en parseo que si se encuentra un condicional AND, se chequeará que el largo sea tal que el condicional sea válido, y que el proximo elemento no sea AND u OR. + +Para la lógica booleana tambien se considero un arbol binario de expresiones booleanas (https://en.wikipedia.org/wiki/Binary_expression_tree). No se logró implementar debido a problemas con el manejo de memoria en Rust, aunque en mi opinión creo que es la mejor implementación posible. + +Para en ORDER BY no cargar las tablas completas en memoria se eligió "Full external sort" (https://cs186berkeley.net/notes/note8/) diff --git a/src/condition.rs b/src/condition.rs new file mode 100644 index 0000000..b27a6bf --- /dev/null +++ b/src/condition.rs @@ -0,0 +1,106 @@ +pub mod condition_type; +// pub mod complex_condition; +// use condition_type::BooleanOperator; +// use crate::libs::error; + +use condition_type::ConditionOperator; + +pub struct Condition { + pub condition: ConditionOperator, + pub column: Option, + pub value: Option, +} + +/// Constructor for not condition +pub fn build_not_condition() -> Condition { + Condition { + condition: ConditionOperator::Equal, + column: None, + value: None, + } +} + +/// Constructor for general condition (simple expression, non boolean) +pub fn build_condition(column: usize, value: String, cond: ConditionOperator) -> Condition { + Condition { + condition: cond, + column: Some(column), + value: Some(value), + } +} + +/// Returns string (or int if it's parseable as int) comparison +/// +/// Pre: Strings and operator +/// Post: True if condition applies +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 => v1 < v2, + ConditionOperator::MinorEqual => v1 <= v2, + ConditionOperator::Equal => v1.eq(v2), + ConditionOperator::Higher => v1 > v2, + ConditionOperator::HigherEqual => v1 >= v2, + } +} + +/// Returns the result of applying the condition to the lineW +/// +/// Pre: Line to vec and condition (bool vector). +/// Post: Bool if condition applies or not +pub fn operate_full_condition(elements: &[String], condition: &[Vec]) -> bool { + let mut or_valid = false; + let mut or_counter = 0; + + let mut and_valid; + let mut and_counter; + let mut not_detected; + + while or_counter < condition.len() && !or_valid { + and_valid = true; + and_counter = 0; + not_detected = false; + while and_counter < condition[or_counter].len() && and_valid { + match &condition[or_counter][and_counter].column { + None => not_detected = !not_detected, + Some(column) => match &condition[or_counter][and_counter].value { + None => return false, + Some(value) => { + if not_detected { + and_valid = !operate_condition( + &elements[*column], + value, + &condition[or_counter][and_counter].condition, + ) + } else { + and_valid = operate_condition( + &elements[*column], + value, + &condition[or_counter][and_counter].condition, + ); + } + } + }, + } + + and_counter += 1; + } + + or_valid = and_valid; + + or_counter += 1; + } + + or_valid +} diff --git a/src/condition/complex_condition.rs b/src/condition/complex_condition.rs new file mode 100644 index 0000000..5e5d9fd --- /dev/null +++ b/src/condition/complex_condition.rs @@ -0,0 +1,133 @@ +use std::borrow::BorrowMut; + +use crate::{libs::error::WHERE_MAL_FORMATEADO, query::Query}; +use super::{condition_type::{self, ConditionOperator}, BooleanOperator, Condition}; + +pub struct ComplexCondition { + // Tree of complex conditions + pub operator: BooleanOperator, + pub left_cond: Option>, // Oldest condition ( first to be found ) + pub left_simple: Option, + pub right_cond: Option>, // Newest condition ( last to be found ) + pub right_simple: Option, +} + +pub fn build_simple_condition(left: Option) -> ComplexCondition { + ComplexCondition { + operator: BooleanOperator::I, + left_simple: left, + + left_cond: None, + right_simple: None, + right_cond: None, + } +} + +pub fn build_complex_condition(op: BooleanOperator, left: Option>, right: Option>) -> ComplexCondition { + ComplexCondition { + operator: op, + left_cond: left, + right_cond: right, + + left_simple: None, + right_simple: None + } +} + +pub fn tree_check(root: &ComplexCondition) -> bool { + // In Order valid check ( no None leafs ) + match &root.operator { + BooleanOperator::OR => tree_check_or_and(&root), + BooleanOperator::AND => tree_check_or_and(&root), + BooleanOperator::NOT => tree_check_not_i(&root), + BooleanOperator::I => tree_check_not_i(&root) + } +} + +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_i(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(mut new_node: ComplexCondition, root: &mut ComplexCondition) -> Result { + // Pre: Previous root and new node + // Post: If success new root, else Error + if check_precedence(&new_node.operator, &root.operator) { // IM new root + new_node.left_cond = Some(Box::new(*root)); + Ok(new_node) + } else if root.left_cond.is_none() { // IM RIGHT SON + Err(WHERE_MAL_FORMATEADO) + } else { + match root.right_cond.as_deref() { + Some(mut x) => add_node_to_tree(new_node, x.borrow_mut()), + None => { + root.right_cond = Some(Box::new(new_node)); + return Ok(*root) + } + } + } +} + +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 check_precedence(op1: &BooleanOperator, op2: &BooleanOperator) -> bool { + // Pre: Operators + // Post: True if it's higher on the tree, false if not + match op1 { + BooleanOperator::OR => true, + BooleanOperator::AND => { + if *op2 == BooleanOperator::OR { + false + } else { + true + } + }, + BooleanOperator::NOT => { + if *op2 == BooleanOperator::OR || *op2 == BooleanOperator::AND { + false + } else { + true + } + }, + BooleanOperator::I => false, + } +} \ 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..e134ecb --- /dev/null +++ b/src/condition/condition_type.rs @@ -0,0 +1,23 @@ +pub enum ConditionOperator { + Minor, + MinorEqual, + Equal, + Higher, + HigherEqual, +} + +pub enum BooleanOperator { + AND, + OR, + NOT, // NOT. Left child + I, // identity. +} + +/* +impl PartialEq for BooleanOperator { + fn eq(&self, other: &Self) -> bool { + self == other + } +} +impl Eq for BooleanOperator {} +*/ 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..80f2924 --- /dev/null +++ b/src/libs/error.rs @@ -0,0 +1,124 @@ +pub static OPERACION_INVALIDA: u32 = 101; +pub static DELETE_MAL_FORMATEADO: u32 = 102; +pub static INSERT_MAL_FORMATEADO: u32 = 103; +pub static SELECT_MAL_FORMATEADO: u32 = 104; +pub static UPDATE_MAL_FORMATEADO: u32 = 105; +pub static WHERE_MAL_FORMATEADO: u32 = 110; +pub static ORDER_BY_MAL_FORMATEADO: u32 = 111; +pub static NO_WHERE: u32 = 112; +pub static FALTA_ARCHIVO: u32 = 200; +pub static ARCHIVO_NO_PUDO_SER_ABIERTO: u32 = 201; +pub static ARCHIVO_VACIO: u32 = 202; +pub static ARCHIVO_NO_CONTIENE_COLUMNAS_SOLICITADAS: u32 = 301; +pub static ERROR_RENOMBRADO_ARCHIVO_TEMPORAL: u32 = 401; +pub static ERROR_INTERCAMBIANDO_TEMPORAL_POR_OFICIAL: u32 = 402; +pub static ERROR_OPERACION_INVALIDA_EN_EJECUCCION: u32 = 403; + +pub static CODE_INVALID_TABLE: &str = "INVALID_TABLE"; +pub static CODE_INVALID_COLUMN: &str = "INVALID_TABLE"; +pub static CODE_INVALID_SYNTAX: &str = "INVALID_SYNTAX"; +pub static CODE_ERROR: &str = "ERROR"; + +pub static TEXTO_OP_INV: &str = "OPERACION INVALIDA"; +pub static TEXTO_DELETE_MF: &str = "DELETE MAL FORMATEADO"; +pub static TEXTO_INSERT_MF: &str = "INSERT MAL FORMATEADO"; +pub static TEXTO_SELECT_MF: &str = "SELECT MAL FORMATEADO"; +pub static TEXTO_UPDATE_MF: &str = "UPDATE MAL FORMATEADO"; +pub static TEXTO_WHERE_MF: &str = "WHERE MAL FORMATEADO"; +pub static TEXTO_ORDERBY_MF: &str = "ORDER BY MAL FORMATEADO"; +pub static TEXTO_NO_WHERE: &str = "OPERACION REQUIERE WHERE"; +pub static TEXTO_FALTA_ARCHIVO: &str = "FALTA ARCHIVO EN QUERY"; +pub static TEXTO_ARCHIVO_NOPEN: &str = "ARCHIVO NO PUDO SER ABIERTO"; +pub static TEXTO_ARCHIVO_VACIO: &str = "ARCHIVO VACIO"; +pub static TEXTO_ARCHIVO_NO_CONTIENE_COLUMNAS: &str = + "EL ARCHIVO NO CONTIENE TODAS LAS COLUMNAS SOLICITADAS"; +pub static TEXTO_ERROR_RENOMBRADO_TEMP: &str = "ERROR RENOMBRADO ARCHIVO TEMPORAL"; +pub static TEXTO_ERROR_INTERCAMBIANDO_TEMP: &str = "ERROR INTERCAMBIANDO TEMPORAL POR OFICIAL"; +pub static TEXTO_ERROR_OPERACION_INVALIDA_EN_EJECUCCION: &str = "OPERACION INVALIDA EN EJECUCCION"; +pub static TEXTO_ERROR_DESCONOCIDO: &str = "ERROR DESCONOCIDO"; + +pub fn print_err(error_code: u32) { + println!( + "{}: {}", + get_text_code(&error_code), + get_description(&error_code) + ); +} + +fn get_text_code(error_code: &u32) -> &str { + if error_code / 100 == 1 { + CODE_INVALID_SYNTAX + } else if error_code / 100 == 2 { + CODE_INVALID_TABLE + } else if error_code / 100 == 3 { + CODE_INVALID_COLUMN + } else { + CODE_ERROR + } +} + +fn get_description(error_code: &u32) -> &str { + if error_code / 100 == 1 { + get_description_syntax(error_code) + } else if error_code / 100 == 2 { + get_description_table(error_code) + } else if error_code / 100 == 3 { + get_description_column(error_code) + } else { + get_description_error(error_code) + } +} + +fn get_description_syntax(error_code: &u32) -> &str { + if *error_code == OPERACION_INVALIDA { + TEXTO_OP_INV + } else if *error_code == DELETE_MAL_FORMATEADO { + TEXTO_DELETE_MF + } else if *error_code == INSERT_MAL_FORMATEADO { + TEXTO_INSERT_MF + } else if *error_code == SELECT_MAL_FORMATEADO { + TEXTO_SELECT_MF + } else if *error_code == UPDATE_MAL_FORMATEADO { + TEXTO_UPDATE_MF + } else if *error_code == WHERE_MAL_FORMATEADO { + TEXTO_WHERE_MF + } else if *error_code == ORDER_BY_MAL_FORMATEADO { + TEXTO_ORDERBY_MF + } else if *error_code == NO_WHERE { + TEXTO_NO_WHERE + } else { + TEXTO_ERROR_DESCONOCIDO + } +} + +fn get_description_table(error_code: &u32) -> &str { + if *error_code == FALTA_ARCHIVO { + TEXTO_FALTA_ARCHIVO + } else if *error_code == ARCHIVO_NO_PUDO_SER_ABIERTO { + TEXTO_ARCHIVO_NOPEN + } else if *error_code == ARCHIVO_VACIO { + TEXTO_ARCHIVO_VACIO + } else { + TEXTO_ERROR_DESCONOCIDO + } +} + +fn get_description_column(error_code: &u32) -> &str { + if *error_code == ARCHIVO_NO_CONTIENE_COLUMNAS_SOLICITADAS { + TEXTO_ARCHIVO_NO_CONTIENE_COLUMNAS + } else { + TEXTO_ERROR_DESCONOCIDO + } +} + +fn get_description_error(error_code: &u32) -> &str { + if *error_code == ERROR_INTERCAMBIANDO_TEMPORAL_POR_OFICIAL { + TEXTO_ERROR_INTERCAMBIANDO_TEMP + } else if *error_code == ERROR_RENOMBRADO_ARCHIVO_TEMPORAL { + TEXTO_ERROR_RENOMBRADO_TEMP + } else if *error_code == ERROR_OPERACION_INVALIDA_EN_EJECUCCION { + TEXTO_ERROR_OPERACION_INVALIDA_EN_EJECUCCION + } else { + TEXTO_ERROR_DESCONOCIDO + } +} diff --git a/src/libs/exec.rs b/src/libs/exec.rs new file mode 100644 index 0000000..c15e2bc --- /dev/null +++ b/src/libs/exec.rs @@ -0,0 +1,1238 @@ +use std::fs::{remove_file, rename, File, OpenOptions}; +use std::io::{BufRead, BufReader, Write}; + +use crate::condition::{self, operate_condition, operate_full_condition, Condition}; +//use crate::condition::operate_condition; +use crate::query::query_type::QueryType; +use crate::query::Query; + +use super::error; +use super::parsing::{self, get_file_first_line, text_to_vec}; + +static FILE_SORT_BUFFER: usize = 3; + +/// Main execution function, execs query +/// By definition, if something is wrong with the query, then it should do nothing +pub fn exec_query(query: Query) { + if let Some(op) = &query.operation { + 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), + } + } +} + +/// Exec query: Special case for delete +fn exec_query_delete(query: Query) { + if let Some(cond) = &query.where_condition { + if let Some(table) = &query.table { + // Check conditions for query. if not, don't do anything + let tmp_file_name = get_tmp_file_name(table); + let mut valid_operation = true; + match File::open(table) { + Ok(file) => match File::create(&tmp_file_name) { + Ok(mut tmp_file) => { + let mut reader: BufReader = BufReader::new(file); + let mut line = String::new(); + + let mut read = true; + while read { + if let Ok(x) = reader.read_line(&mut line) { + line = line.replace('\n', ""); + read = x != 0; + if read { + let elements = line_to_vec(&line); + if !operate_full_condition(&elements, cond) { + line.push('\n'); + if let Err(_error) = tmp_file.write(line.as_bytes()) { + read = false; + valid_operation = false; + } + } + } + line.clear(); + } else { + read = false; + valid_operation = false; + } + } + } + Err(_) => valid_operation = false, + }, + Err(_) => valid_operation = false, + } + + if remove_old_file(table, &tmp_file_name, valid_operation).is_err() { + println!("ERROR: limpieza de archivos temporales"); + } + } + } +} + +/// Exec query: Special case for insert +fn exec_query_insert(query: Query) { + if let Some(table) = &query.table { + if let Some(write_columns) = &query.columns { + let total_columns = find_total_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: Manipulación de archivo temporal en INSERT"), + } + } + } +} + +/// Exec query: Special case for select +fn exec_query_select(query: Query) { + print_header(&query); + match &query.order_by { + Some((column, asc)) => { + if let Some(col_index) = find_column(&query, column) { + if let Ok(files) = read_into_sorted_files(&query, col_index, asc) { + print_sorted_files(&files, &col_index, asc); + file_cleanup(&files); + } + } + } + None => { + read_and_print_file(&query); + } + } +} + +/// Cleans tmp files +/// Pre: filenames +/// Post: Cleans files +fn file_cleanup(files: &Vec) { + for file in files { + if remove_file(file).is_err() { + println!("ERROR: Limpieza de archivos temporales"); + } + } +} + +/// Pre: Name of tmp_files, column for ordering and selector of asc/desc +/// Post: Prints from tmp_files in order +fn print_sorted_files(files: &[String], col_index: &usize, asc: &bool) { + if let Some(mut readers) = create_readers(files) { + if let Some(mut lines_buffer) = readers_read_first_line(&mut readers) { + // While there are lines to be printed + while let Some(elements) = + get_next_line(&mut lines_buffer, &mut readers, *col_index, asc) + { + let mut counter = 0; + for element in elements { + if !element.eq("") { + if counter == 0 { + print!("{}", element); + } else { + print!(",{}", element); + } + counter += 1; + } + } + println!(); + } + } + } +} + +/// Gets next sorted line from mem's buffer and replace it with the next line on that file +/// Pre: Lines buffer, readers, col index for sorting and ascending/descending bool +/// Post: Next line, if not fully read +fn get_next_line( + lines_buffer: &mut Vec>, + readers: &mut [BufReader], + col_index: usize, + asc: &bool, +) -> Option> { + let mut line = String::new(); + match find_next_line(lines_buffer, col_index, asc) { + Some(reader_index) => match &readers[reader_index].read_line(&mut line) { + Ok(x) => { + if *x > 0 { + let _ = line.replace('\n', ""); + lines_buffer.push(text_to_vec(&line, true)); + } else { + let empty_line_buf: Vec = Vec::new(); + lines_buffer.push(empty_line_buf); + } + Some(lines_buffer.swap_remove(reader_index)) + } + Err(_) => { + let empty_line_buf: Vec = Vec::new(); + lines_buffer.push(empty_line_buf); + Some(lines_buffer.swap_remove(reader_index)) + } + }, + None => None, + } +} + +/// Find next sorted line's index to be printed +/// Pre: Lines buffer, col index and ascending/descending +/// Post: Index of next line, if any +fn find_next_line(lines_buffer: &mut [Vec], col_index: usize, asc: &bool) -> Option { + // Find first line + let mut candidate = 0; + while candidate < lines_buffer.len() && lines_buffer[candidate].is_empty() { + candidate += 1; + } + + if candidate == lines_buffer.len() { + None + } else { + // Find lower line + let mut counter = candidate + 1; + while counter < lines_buffer.len() { + if !lines_buffer[counter].is_empty() { + if *asc { + if operate_condition( + &lines_buffer[counter][col_index], + &lines_buffer[candidate][col_index], + &condition::condition_type::ConditionOperator::Minor, + ) { + candidate = counter; + } + } else if operate_condition( + &lines_buffer[counter][col_index], + &lines_buffer[candidate][col_index], + &condition::condition_type::ConditionOperator::Higher, + ) { + candidate = counter; + } + } + counter += 1; + } + + Some(candidate) + } +} + +/// Read first line from all files +/// Pre: Lines buffer, col index and ascending/descending +/// Post: Index of next line, if any +fn readers_read_first_line(readers: &mut Vec>) -> Option>> { + let mut result: Vec> = Vec::new(); + let mut line = String::new(); + + for reader in readers { + match reader.read_line(&mut line) { + Ok(_) => { + let _ = line.replace('\n', ""); + result.push(text_to_vec(&line, true)); + line.clear(); + } + Err(_) => return None, + } + } + Some(result) +} + +/// Create readers for reading files +/// Pre: Filenames +/// Post: File's readers +fn create_readers(files: &[String]) -> Option>> { + let mut valid = true; + let mut readers: Vec> = Vec::new(); + + let mut counter = 0; + while counter < files.len() && valid { + match File::open(&files[counter]) { + Err(_) => valid = false, + Ok(file) => { + readers.push(BufReader::new(file)); + counter += 1; + } + } + } + + if valid { + Some(readers) + } else { + None + } +} + +/// Exec query: Special case for update +fn exec_query_update(query: Query) { + if let Some(cond) = &query.where_condition { + if let Some(columns) = &query.columns { + if let Some(values) = &query.values { + if let Some(table) = &query.table { + let tmp_file = get_tmp_file_name(table); + let mut valid_operation = true; + match File::open(table) { + Ok(file) => match File::create(&tmp_file) { + Ok(mut tmp_file) => { + let mut reader: BufReader = BufReader::new(file); + let mut line = String::new(); + + let mut read = true; + while read { + if let Ok(x) = reader.read_line(&mut line) { + read = x != 0; + if read { + line = line.replace('\n', ""); + let elements = line_to_vec(&line); + if !operate_full_condition(&elements, cond) { + // Line not updated + line.push('\n'); + if let Err(_error) = tmp_file.write(line.as_bytes()) + { + read = false; + valid_operation = false; + } + } else { + // Update Line + if let Err(_error) = tmp_file.write( + update_line(&elements, columns, values) + .as_bytes(), + ) { + read = false; + valid_operation = false; + } + } + } + line.clear(); + } else { + read = false; + valid_operation = false; + } + } + } + Err(_) => valid_operation = false, + }, + Err(_) => valid_operation = false, + } + + if remove_old_file(table, &tmp_file, valid_operation).is_err() { + println!("ERROR: Manipulación de archivo temporal en UPDATE"); + } + } + } + } + } +} + +/// Exec query: Special case for select +fn update_line(elements: &[String], columns: &[usize], values: &[String]) -> String { + let mut result = String::new(); + let mut writen_counter = 0; + for (counter, element) in elements.iter().enumerate() { + if counter > 0 { + result.push(','); + } + if columns.contains(&counter) { + result.push_str(&values[writen_counter].to_string()); + writen_counter += 1; + } else { + result.push_str(&element.to_string()); + } + } + result +} + +/// Aux Function. Remove old tmp file +fn remove_old_file(file: &String, tmp_file: &String, valid_operation: bool) -> Result { + if valid_operation { + if remove_file(file).is_ok() { + if rename(tmp_file, file).is_err() { + return Err(error::ERROR_RENOMBRADO_ARCHIVO_TEMPORAL); + } + Ok(0) + } else { + let _ = remove_file(tmp_file); + Err(error::ERROR_INTERCAMBIANDO_TEMPORAL_POR_OFICIAL) + } + } else { + let _ = remove_file(tmp_file); + Err(error::ERROR_OPERACION_INVALIDA_EN_EJECUCCION) + } +} + +/// Returns tmp filename from official file +/// Pre: Path and name to file. ruta/a/tablas/tabla.csv +/// Post: same file but starting with period, as long as it's a hidden file +fn get_tmp_file_name(table: &str) -> String { + let mut output = String::new(); + let last_item_index = table.split('/').enumerate().count() - 1; + for (counter, element) in table.split('/').enumerate() { + if counter == last_item_index { + output.push('.'); + } + + output.push_str(element); + + if counter < last_item_index { + output.push('/'); + } + } + output +} + +/// Aux function. Total number of columns in table +fn find_total_columns(query: &Query) -> usize { + match get_file_first_line(query) { + Some(line) => line_to_vec(&line).len(), + None => 0, + } +} + +/// Find column index given it's name +/// +/// Pre: Valid query and column for text +/// Post: Index if any +fn find_column(query: &Query, column: &String) -> Option { + match get_file_first_line(query) { + Some(line) => { + let args = parsing::text_to_vec(&line, true); + let mut counter = 0; + while counter < args.len() && !args[counter].eq(column) { + counter += 1 + } + + if counter == args.len() { + None + } else { + match &query.columns { + None => Some(counter), + Some(cols) => { + if cols.contains(&counter) { + Some(counter) + } else { + None + } + } + } + } + } + None => None, + } +} + +/// Print header of function. +fn print_header(query: &Query) { + if let Some(table) = &query.table { + if let Ok(file) = File::open(table) { + let mut reader: BufReader = BufReader::new(file); + let mut line = String::new(); + + if let Ok(x) = reader.read_line(&mut line) { + if x > 0 { + match &query.columns { + Some(columns) => { + line = line.replace('\n', ""); + let args = text_to_vec(&line, true); + let mut first = true; + for (counter, arg) in args.into_iter().enumerate() { + if columns.contains(&counter) { + if first { + print!("{}", arg); + first = false; + } else { + print!(",{}", arg); + } + } + } + println!(); + } + None => print!("{}", line), + } + } + } + } + } +} + +/// Function for printing file ( special select case when no sort by ) +fn read_and_print_file(query: &Query) { + if let Some(table) = &query.table { + if let Ok(f) = File::open(table) { + let mut reader: BufReader = BufReader::new(f); + let mut line = String::new(); + + let mut read = reader.read_line(&mut line).is_ok(); + line.clear(); + while read { + if let Ok(x) = reader.read_line(&mut line) { + line = line.replace('\n', ""); + read = x != 0; + if read { + let elements = line_to_vec(&line); + match &query.where_condition { + Some(condition) => { + print_file_conditioned(&query.columns, condition, &elements) + } + None => print_file_unconditional(&query.columns, &elements), + } + line.clear(); + } + } + } + } + } +} + +/// Exec query line by line, sorting the output in buffer memory and printing to tmp files +/// PD: Similar to Cassandra Write Path :D +/// +/// Pre: Query, the col index for sorting and bool of ascending/descending +/// Post: Vec of tmp_files +fn read_into_sorted_files(query: &Query, col_index: usize, asc: &bool) -> Result, u32> { + match &query.table { + None => Err(3), + Some(table) => { + let tmp_file_name = get_tmp_file_name(table); + match File::open(table) { + Err(_) => Err(3), + Ok(table_file) => { + let mut tmp_filenames: Vec = Vec::new(); + let mut lines_buffer: Vec> = Vec::new(); // list maybe better ? + + let mut reader: BufReader = BufReader::new(table_file); + let mut line = String::new(); + + let mut read: bool = true; + let mut valid_operation: bool = true; + if reader.read_line(&mut line).is_ok() { + line.clear(); + while read { + match reader.read_line(&mut line) { + Err(_) => { + read = false; + valid_operation = false; + } + Ok(x) => { + line = line.replace('\n', ""); + read = x != 0; + if read { + let elements = text_to_vec(&line, true); + match &query.where_condition { + Some(cond) => insert_conditioned( + elements, + cond, + &query.columns, + &mut lines_buffer, + &col_index, + asc, + ), + None => insert_unconditioned( + elements, + &query.columns, + &mut lines_buffer, + &col_index, + asc, + ), + } + line.clear(); + } + + if lines_buffer.len() == FILE_SORT_BUFFER { + write_to_tmp_file( + &tmp_file_name, + &mut tmp_filenames, + &lines_buffer, + &mut read, + &mut valid_operation, + ); + lines_buffer.clear(); + } + } + } + } + + if valid_operation { + if !lines_buffer.is_empty() { + write_to_tmp_file( + &tmp_file_name, + &mut tmp_filenames, + &lines_buffer, + &mut read, + &mut valid_operation, + ); + } + Ok(tmp_filenames) + } else { + Err(3) + } + } else { + Err(error::ARCHIVO_VACIO) + } + } + } + } + } +} + +/// Write the buffer to file +fn write_to_tmp_file( + tmp_file_name: &String, + tmp_filenames: &mut Vec, + lines_buffer: &Vec>, + read: &mut bool, + valid_operation: &mut bool, +) { + let mut new_tmp_file_name = String::from(tmp_file_name); + new_tmp_file_name.push_str(format!(".{}", &tmp_filenames.len()).as_str()); + + match File::create(&new_tmp_file_name) { + Ok(mut tmp_f) => { + for elements in lines_buffer { + let mut first = true; + for element in elements { + if first { + let _ = tmp_f.write(element.as_bytes()); + first = false; + } else { + let _ = tmp_f.write(format!(",{}", element).as_bytes()); + } + } + let _ = tmp_f.write("\n".as_bytes()); + } + tmp_filenames.push(new_tmp_file_name); + } + Err(_) => { + *read = false; + *valid_operation = false; + } + } +} + +/// Insert line to buffer in sorted position (special case: no boolean condition) +fn insert_unconditioned( + elements: Vec, + columns_opt: &Option>, + lines_buffer: &mut Vec>, + col_index: &usize, + asc: &bool, +) { + let position: usize = if lines_buffer.is_empty() { + 0 + } else { + find_insert_position( + &elements, + lines_buffer, + 0, + lines_buffer.len() - 1, + *col_index, + asc, + ) + }; + + match columns_opt { + Some(columns) => { + // SELECT columns FROM + let mut vector: Vec = Vec::new(); + + let mut counter = 0; + while counter < elements.len() { + if columns.contains(&counter) { + vector.push(elements[counter].to_string()); + } + + counter += 1; + } + lines_buffer.insert(position, vector); + } + None => lines_buffer.insert(position, elements), + } +} + +/// Insert line to buffer in sorted position (special case: there's a boolean condition) +fn insert_conditioned( + elements: Vec, + condition: &[Vec], + columns_opt: &Option>, + lines_buffer: &mut Vec>, + col_index: &usize, + asc: &bool, +) { + if operate_full_condition(&elements, condition) { + insert_unconditioned(elements, columns_opt, lines_buffer, col_index, asc); + } +} + +/// Binary search of elements position. +fn find_insert_position( + elements: &Vec, + lines_buffer: &Vec>, + min_pos: usize, + max_pos: usize, + col_index: usize, + asc: &bool, +) -> usize { + let less_than_minor = operate_condition( + &elements[col_index], + &lines_buffer[min_pos][col_index], + &condition::condition_type::ConditionOperator::Minor, + ); + let less_than_med = operate_condition( + &elements[col_index], + &lines_buffer[(min_pos + max_pos) / 2][col_index], + &condition::condition_type::ConditionOperator::Minor, + ); + + if min_pos == max_pos { + if less_than_minor { + if *asc { + min_pos + } else { + min_pos + 1 + } + } else if *asc { + min_pos + 1 + } else { + min_pos + } + } else if min_pos + 1 == max_pos { + if less_than_minor { + if *asc { + find_insert_position(elements, lines_buffer, min_pos, min_pos, col_index, asc) + } else { + find_insert_position(elements, lines_buffer, max_pos, max_pos, col_index, asc) + } + } else if *asc { + find_insert_position(elements, lines_buffer, max_pos, max_pos, col_index, asc) + } else { + find_insert_position(elements, lines_buffer, min_pos, min_pos, col_index, asc) + } + } else { + let med = (min_pos + max_pos) / 2; + if *asc { + if less_than_med { + find_insert_position(elements, lines_buffer, min_pos, med, col_index, asc) + } else { + find_insert_position(elements, lines_buffer, med, max_pos, col_index, asc) + } + } else if less_than_med { + find_insert_position(elements, lines_buffer, med, max_pos, col_index, asc) + } else { + find_insert_position(elements, lines_buffer, min_pos, med, col_index, asc) + } + } +} + +/// Print line to stdout ( special case, no SORT BY condition ) +/// Pre: Columns vector sorted && Elements of line content vector +/// Post: print to stdout the correct columns +fn print_file_unconditional(columns_opt: &Option>, elements: &[String]) { + match columns_opt { + Some(columns) => { + // SELECT columns FROM + let mut counter = 0; + while counter < elements.len() { + if columns.contains(&counter) { + if counter == 0 { + print!("{}", elements[counter]); + } else { + print!(",{}", elements[counter]); + } + } + + counter += 1; + } + } + None => { + for (counter, element) in elements.iter().enumerate() { + if counter == 0 { + print!("{}", element); + } else { + print!(",{}", element); + } + } + } + } + + println!(); +} + +/// Print line to stdout with a boolean condition( special case, no SORT BY condition ) +fn print_file_conditioned( + columns_opt: &Option>, + condition: &[Vec], + elements: &[String], +) { + if operate_full_condition(elements, condition) { + print_file_unconditional(columns_opt, elements) + } +} + +/// Aux function. Tokenizer for special cases +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 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bsearch1() { + let elements = vec![ + String::from("4"), + String::from("jorge"), + String::from("martell"), + ]; + let lines_buffer = vec![ + vec![ + String::from("1"), + String::from("diego"), + String::from("mayor"), + ], + vec![ + String::from("3"), + String::from("pamela"), + String::from("ureta"), + ], + vec![ + String::from("5"), + String::from("lucas"), + String::from("catini"), + ], + ]; + let rt1 = find_insert_position( + &elements, + &lines_buffer, + 0, + lines_buffer.len() - 1, + 0, + &true, + ); + assert_eq!(rt1, 2); + } + + #[test] + fn test_bsearch2() { + let elements = vec![ + String::from("1"), + String::from("Jorge"), + String::from("martell"), + ]; + let lines_buffer = vec![ + vec![ + String::from("4"), + String::from("María"), + String::from("Rodríguez"), + ], + vec![ + String::from("10"), + String::from("Manuel"), + String::from("Allen"), + ], + vec![ + String::from("6"), + String::from("Laura"), + String::from("Fernández"), + ], + vec![ + String::from("5"), + String::from("José"), + String::from("López"), + ], + vec![ + String::from("7"), + String::from("Diego"), + String::from("Torres"), + ], + vec![ + String::from("3"), + String::from("Carlos"), + String::from("Gómez"), + ], + vec![ + String::from("2"), + String::from("Ana"), + String::from("López"), + ], + ]; + let rt2 = find_insert_position( + &elements, + &lines_buffer, + 0, + lines_buffer.len() - 1, + 1, + &false, + ); + assert_eq!(rt2, 4); + } +} + +/* +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 save_file_unconditional(columns_opt: &Option>, elements: &[String], file: &mut File) { + // Pre: Columns vector sorted incremental && Elements of line content vector + // Post: print to stdout the correct columns + + match columns_opt { + Some(columns) => { + // SELECT columns FROM + let mut counter = 0; + while counter < elements.len() { + if columns.contains(&counter) { + if counter == 0 { + file.write(elements[counter].as_bytes()); + } else { + file.write(format!(",{}", elements[counter]).as_bytes()); + } + } + + counter += 1; + } + } + None => { + for (counter, element) in elements.iter().enumerate() { + if counter == 0 { + file.write(element.as_bytes()); + } else { + file.write(format!(",{}", element).as_bytes()); + } + } + } + } + + println!(); +} + +fn save_file_conditioned( + columns_opt: &Option>, + filter: (i32, &Condition), + elements: &[String], + file: &mut File, +) { + let (col_filter, condition) = filter; + if let Some(value) = &condition.value { + if operate_condition(&elements[col_filter as usize], value, &condition.condition) { + save_file_unconditional(columns_opt, elements, file) + } + } else { + save_file_unconditional(columns_opt, elements, file) + } +} + +fn read_and_save_file(query: &Query, col_filter: i32) -> Result { + // Pre: query, filter and + // Post: + if let Some(table) = &query.table { + let tmp_file_name = get_tmp_file_name(table); + if let Ok(f) = File::open(table) { + match File::create(tmp_file_name) { + Ok(mut tmp_f) => { + let mut line = String::new(); + let mut reader: BufReader = BufReader::new(f); + + let mut read = true; + while read { + if let Ok(x) = reader.read_line(&mut line) { + line = line.replace('\n', ""); + read = x != 0; + if read { + let elements = line_to_vec(&line); + match &query.where_condition { + Some(condition) => save_file_conditioned( + &query.columns, + (col_filter, condition), + &elements, + &mut tmp_f, + ), + None => save_file_unconditional( + &query.columns, + &elements, + &mut tmp_f, + ), + } + line.clear(); + } + } + } + return Ok(0); + } + Err(_) => Err(1), + } + } else { + return Err(1); + } + } else { + return Err(1); + } +} + +fn sort_and_print_file( + column_number: usize, + query: &Query, + asc: &bool, +) -> Result, u32> { + // Pre: Column number for sorting + // Post: Separates into files, sort them and print them in correct order. Result name of files for cleanup + match &query.table { + None => Err(3), + Some(table) => { + let tmp_unsorted_filename = get_tmp_file_name(table); + match File::open(tmp_unsorted_filename) { + Err(_) => Err(3), + Ok(unsorted_file) => { + let mut reader: BufReader = BufReader::new(unsorted_file); + let mut line = String::new(); + let mut actually_sorting_elements: Vec> = Vec::new(); + + let mut sorted_files: Vec = Vec::new(); + let mut sorted_filenames: Vec = Vec::new(); + let mut counter = 0; + + let mut read = true; + let mut valid_operation = true; + while read { + if let Ok(x) = reader.read_line(&mut line) { + line = line.replace('\n', ""); + read = x != 0; + + let element = text_to_vec(&line, true); + if counter < FILE_SORT_BUFFER { + actually_sorting_elements.insert( + find_sorted_position( + &actually_sorting_elements, + &element, + column_number, + asc, + ), + element, + ); + } else { + let mut new_file = String::from(format!(".{}", sorted_files.len())); + new_file.push_str(&tmp_unsorted_filename); + match File::create(new_file) { + Ok(f) => { + for element in actually_sorting_elements { + let mut write_line = String::new(); + for (counter, word) in element.iter().enumerate() { + if counter == 0 { + f.write(word.as_bytes()); + } else { + f.write(format!(",{}", &word)); + } + } + } + sorted_files.push(f); + sorted_filenames.push(new_file); + actually_sorting_elements.clear(); + } + Err(_) => { + read = false; + valid_operation = false; + } + } + } + } else { + valid_operation = false; + read = false; + } + } + + if valid_operation { + Ok(sorted_filenames) + } else { + Err(3) + } + } + } + } + } +} + + +fn file_cleanup(files: Vec) { + for file in files { + let _ = remove_file(file); + } +} + +fn find_sorted_position( + sorted_vec: &Vec>, + new_element: &Vec, + column_number: usize, + asc: &bool, +) -> usize { + // Insert into shifts everything to right. + 0 +} + +fn insert_conditioned(elements: &[String], columns_opt: &Option>, lines_buffer: &mut Vec>, col_index: &usize, filter: &Condition) { +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_opt, elements) + } + } else { + print_file_unconditional(columns_opt, elements) + } +} + +fn exec_query_select(query: Query) { + match &query.order_by { + Some((column, asc)) => { + let col_index: i32 = find_filter_column(&query); + if read_and_save_file(&query, col_index).is_ok() { + if let Some(column_number) = find_column(&query, column) { + if let Ok(tmp_files) = sort_and_print_file(column_number, &query, asc) { + file_cleanup(tmp_files); + } + } + } + } + None => { + let col_index: i32 = find_filter_column(&query); + read_and_print_file(&query, col_index); + } + } +} + +fn get_next_line(lines_buffer: &mut Vec>, readers: &mut Vec>, col_index: usize, asc: &bool) -> Option> { + // Post: Next line in order, if not fully read + let mut line = String::new(); + match find_available_reader(&readers) { + None => None, + Some(mut candidate) => { + let mut counter = candidate + 1; + while counter < readers.len() { + if readers[counter].0 < FILE_SORT_BUFFER && counter != candidate { + if *asc { + if lines_buffer[counter][col_index] < lines_buffer[candidate][col_index] { + candidate = counter; + } + } else { + if lines_buffer[counter][col_index] > lines_buffer[candidate][col_index] { + candidate = counter; + } + } + } + + counter += 1; + } + + if counter == readers.len() { + None + } else { + let new_line: Vec; + match readers[candidate].1.read_line(&mut line){ + Err(_) => new_line = Vec::new(), + Ok(read_result) => { + if read_result == 0 { + new_line = Vec::new(); // If this happen, then never read again from here + readers[candidate].0 = FILE_SORT_BUFFER; + } else { + lines_buffer.push(text_to_vec(&line, true)); + let len = lines_buffer.len(); + lines_buffer.swap(candidate,len - 1); + match lines_buffer.pop() { + Some(x) => { + new_line = x; + readers[candidate].0 += 1; + }, + None => { + new_line = Vec::new(); // If this happen, then never read again from here + readers[candidate].0 = FILE_SORT_BUFFER; + } + } + } + } + } + + Some(new_line) + } + } + } +} + +fn find_available_reader(readers: &Vec>) -> Option { + // Pre: Readers + // Post: First available reader position + let mut counter = 0; + while counter < readers.len() && ! &readers[counter].0 < FILE_SORT_BUFFER { + counter += 1; + } + + if counter == readers.len() { + None + } else { + Some(counter) + } +} +*/ diff --git a/src/libs/parsing.rs b/src/libs/parsing.rs new file mode 100644 index 0000000..00d73b6 --- /dev/null +++ b/src/libs/parsing.rs @@ -0,0 +1,755 @@ +use std::fs::File; +use std::io::{BufRead, BufReader}; + +use crate::condition::{build_condition, build_not_condition, Condition}; +//use crate::condition::complex_condition::{add_node_to_tree, build_complex_condition, build_simple_condition, ComplexCondition, tree_check, check_precedence}; +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::{ + DELETE_MAL_FORMATEADO, ORDER_BY_MAL_FORMATEADO, SELECT_MAL_FORMATEADO, WHERE_MAL_FORMATEADO, +}; + +/// Build query for execution +pub fn build_query(text_query: &String, path: &String) -> Result { + let args = text_to_vec(text_query, false); + match vec_to_query(&args, path) { + Ok(x) => validate_query(x), + Err(x) => Err(x), + } +} + +/// Merge table and path. Aux funcion +/// +/// ``` +/// let result = merge_table_and_path(String::from("tablas"), "clientes.csv"); +/// assert!(result,String::from("tablas/clientes.csv")); +/// ``` +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 +} + +/// Parse args from vec to query (special case for delete) +fn parse_args_delete(args: &[String], path: &String, result: &mut Query) -> Result { + if args.len() < DELETE_MIN_LEN { + Err(error::DELETE_MAL_FORMATEADO) + } else { + match File::open(merge_table_and_path(path, &args[2])) { + Ok(_) => result.table = Some(merge_table_and_path(path, &args[2])), + Err(_) => return Err(error::ARCHIVO_NO_PUDO_SER_ABIERTO), + } + + if args.len() != DELETE_MIN_LEN { + return Err(error::DELETE_MAL_FORMATEADO); + } else if args[3].eq("WHERE") { + let mut counter: usize = 4; + match parse_where_condition(result, args, &mut counter) { + Ok(cond) => result.where_condition = Some(cond), + Err(x) => return Err(x), + } + } else { + return Err(DELETE_MAL_FORMATEADO); + } + + Ok(0) + } +} + +/// Parse args from vec to query (special case for insert) +fn parse_args_insert(args: &[String], path: &String, result: &mut Query) -> Result { + if args.len() < INSERT_MIN_LEN { + Err(error::INSERT_MAL_FORMATEADO) + } else { + match File::open(merge_table_and_path(path, &args[2])) { + Ok(_) => result.table = Some(merge_table_and_path(path, &args[2])), + Err(_) => return Err(error::ARCHIVO_NO_PUDO_SER_ABIERTO), + } + + let mut string_columns: Vec = Vec::new(); + let mut counter = 3; + while counter < args.len() && !args[counter].eq("VALUES") { + string_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; + } + + match get_columns_position(result, &string_columns) { + Ok(x) => result.columns = Some(x), + Err(x) => return Err(x), + } + result.values = Some(values); + + Ok(0) + } +} + +/// Parse args from vec to query (special case for update) +fn parse_args_update(args: &[String], path: &String, result: &mut Query) -> Result { + if args.len() < UPDATE_MIN_LEN { + Err(error::UPDATE_MAL_FORMATEADO) + } else { + match File::open(merge_table_and_path(path, &args[1])) { + Ok(_) => result.table = Some(merge_table_and_path(path, &args[1])), + Err(_) => return Err(error::ARCHIVO_NO_PUDO_SER_ABIERTO), + } + + let mut counter: usize = 3; + let mut string_columns: Vec = Vec::new(); + let mut values: Vec = Vec::new(); + + while counter < args.len() && !args[counter].eq("WHERE") { + if counter % 3 == 0 { + string_columns.push(args[counter].to_string()); + } else if (counter % 3) == 2 { + values.push(args[counter].to_string()); + } + counter += 1; + } + + match get_columns_position(result, &string_columns) { + Ok(x) => result.columns = Some(x), + Err(x) => return Err(x), + } + result.values = Some(values); + + match parse_where_condition(result, args, &mut counter) { + Ok(cond) => result.where_condition = Some(cond), + Err(x) => return Err(x), + } + + Ok(0) + } +} + +/// Parse args from vec to query (special case for select) +fn parse_args_select(args: &[String], path: &String, result: &mut Query) -> Result { + if args.len() < SELECT_MIN_LEN { + Err(error::SELECT_MAL_FORMATEADO) + } else { + let mut string_columns: Vec = Vec::new(); + let mut counter = 1; + while counter < args.len() && !args[counter].eq("FROM") { + string_columns.push(args[counter].to_string()); + counter += 1; + } + counter += 1; + + match File::open(merge_table_and_path(path, &args[counter])) { + Ok(_) => result.table = Some(merge_table_and_path(path, &args[counter])), + Err(_) => return Err(error::ARCHIVO_NO_PUDO_SER_ABIERTO), + } + + if !&string_columns[0].eq("*") { + match get_columns_position(result, &string_columns) { + Ok(x) => result.columns = Some(x), + Err(x) => return Err(x), + } + } + + counter += 1; + + if counter == args.len() { + return Ok(0); + } else if args[counter].eq("WHERE") { + counter += 1; + match parse_where_condition(result, args, &mut counter) { + Ok(cond) => result.where_condition = Some(cond), + Err(x) => return Err(x), + } + } + + if counter == args.len() { + Ok(0) + } else if args[counter].eq("ORDER") { + match parse_order_by(args, &mut counter) { + Ok(cond) => { + result.order_by = Some(cond); + Ok(0) + } + Err(x) => Err(x), + } + } else { + Err(SELECT_MAL_FORMATEADO) + } + } +} + +/// Aux Function. parse from string columns to the index of the columns in the table +/// All columns should be in the table +fn get_columns_position(query: &Query, string_cols: &[String]) -> Result, u32> { + match get_file_first_line(query) { + Some(line) => { + let mut result: Vec = Vec::new(); + let columns = text_to_vec(&line, true); + + let mut counter = 0; + while counter < string_cols.len() { + if columns.contains(&string_cols[counter]) { + match find_column_position(&string_cols[counter], &columns) { + Ok(x) => { + result.push(x); + counter += 1; + } + Err(x) => return Err(x), + } + } else { + return Err(error::ARCHIVO_NO_CONTIENE_COLUMNAS_SOLICITADAS); + } + } + Ok(result) + } + None => Err(error::ARCHIVO_NO_PUDO_SER_ABIERTO), + } +} + +/// Parse order by +/// +/// Pre: Counter == 3 or counter == 4 +fn parse_order_by(args: &[String], counter: &mut usize) -> Result<(String, bool), u32> { + if *counter + 3 == args.len() || *counter + 4 == args.len() { + if args[*counter].eq("ORDER") && args[*counter + 1].eq("BY") { + let column = args[*counter + 2].to_string(); + let mut asc = true; + if *counter + 4 == args.len() { + if args[*counter + 3].eq("DESC") { + asc = false; + } else if !args[*counter + 3].eq("ASC") { + return Err(ORDER_BY_MAL_FORMATEADO); + } + } + Ok((column, asc)) + } else { + Err(ORDER_BY_MAL_FORMATEADO) + } + } else { + Err(ORDER_BY_MAL_FORMATEADO) + } +} + +/// Parse boolean where condition, advancing the counter +/// +/// Pre: Query args and counter in next position to WHERE +/// Post: Complex condition for query and counter at the next position of the last element of condition +/// +/// EXAMPLE: +/// Pre: parse_where_condition(query, vec!["SELECT","*","FROM","tabla","WHERE","id","<","5","ORDER","BY","id"],5); +/// Post: Result, counter = 8 +fn parse_where_condition( + query: &Query, + args: &[String], + counter: &mut usize, +) -> Result>, u32> { + if args[*counter].eq("AND") || args[*counter].eq("OR") { + Err(WHERE_MAL_FORMATEADO) + } else { + // Parse into boolean vector + let mut result: Vec> = Vec::new(); + result.push(Vec::new()); + while *counter < args.len() && !args[*counter].eq("ORDER") { + match parse_next_condition(query, args, counter, &mut result) { + Ok(_) => continue, + Err(x) => return Err(x), + } + } + + // Check valid result + if check_valid_bool(&result) { + Ok(result) + } else { + Err(WHERE_MAL_FORMATEADO) + } + } +} + +/// Pre: Query, args, position counter and wip vector +/// Post: Parses the next simple condition into boolean vector +fn parse_next_condition( + query: &Query, + args: &[String], + counter: &mut usize, + vec: &mut Vec>, +) -> Result { + if *counter + 2 >= args.len() { + Err(WHERE_MAL_FORMATEADO) + } else { + if args[*counter + 1].eq("AND") || args[*counter + 1].eq("OR") { + return Err(WHERE_MAL_FORMATEADO); + } else if args[*counter].eq("OR") { + vec.push(Vec::new()); + *counter += 1; + } else if args[*counter].eq("AND") { + *counter += 1; + } else if args[*counter].eq("NOT") { + let position = vec.len() - 1; + vec[position].push(build_not_condition()); // String vacio + *counter += 1; + } else { + match get_columns_position(query, &[args[*counter].to_string()]) { + Err(x) => return Err(x), + Ok(x) => { + let simple_condition: Condition = if args[*counter + 1].eq("<") { + build_condition( + x[0], + args[*counter + 2].to_string(), + ConditionOperator::Minor, + ) + } else if args[*counter + 1].eq("<=") { + build_condition( + x[0], + args[*counter + 2].to_string(), + ConditionOperator::MinorEqual, + ) + } else if args[*counter + 1].eq("=") { + build_condition( + x[0], + args[*counter + 2].to_string(), + ConditionOperator::Equal, + ) + } else if args[*counter + 1].eq(">=") { + build_condition( + x[0], + args[*counter + 2].to_string(), + ConditionOperator::HigherEqual, + ) + } else if args[*counter + 1].eq(">") { + build_condition( + x[0], + args[*counter + 2].to_string(), + ConditionOperator::Higher, + ) + } else { + return Err(WHERE_MAL_FORMATEADO); + }; + let position = vec.len() - 1; + vec[position].push(simple_condition); + *counter += 3; + } + } + } + Ok(0) + } +} + +// Pre: Boolean vector (parsed boolean condition) +// Post: Bool for valid or invalid result +fn check_valid_bool(boolean_expresion: &[Vec]) -> bool { + let mut valid = true; + let mut counter = 0; + let mut sub_counter; + let mut open_not = false; + while counter < boolean_expresion.len() && valid { + valid = boolean_expresion[counter].is_empty(); + + sub_counter = 0; + while sub_counter < boolean_expresion[counter].len() && valid { + open_not = !(open_not && boolean_expresion[counter][sub_counter].value.is_some()); + sub_counter += 1; + } + + valid = !open_not; + counter += 1; + } + + valid +} + +/// find column position +fn find_column_position(column_name: &String, columns: &[String]) -> Result { + let mut counter = 0; + while counter < columns.len() { + if column_name.eq(&columns[counter]) { + return Ok(counter); + } else { + counter += 1; + } + } + Err(error::ARCHIVO_NO_CONTIENE_COLUMNAS_SOLICITADAS) +} + +/// Check correct operation sintaxis +fn check_operation_format(args: &[String]) -> Result { + 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) + } +} + +/// Check correct operation sintaxis (special case for delete) +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) + } +} + +/// Check correct operation sintaxis (special case for insert) +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) + } +} + +/// Check correct operation sintaxis (special case for select) +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) + } +} + +/// Check correct operation sintaxis (special case for update) +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) + } +} + +/// Check if args for query contains non valid keywords +fn check_non_valid_keywords(args: &[String], non_valid_keywords: Vec<&str>) -> bool { + 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 +} + +/// Pre: Sintactical query OK +/// Post: Valid query for execution +fn validate_query(query: Query) -> Result { + 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), + } +} + +/// Validate query: Special case for delete +fn validate_delete_query(query: Query) -> Result { + if query.columns.is_none() && query.values.is_none() && query.where_condition.is_some() { + validate_table(query) + } else { + Err(error::DELETE_MAL_FORMATEADO) + } +} + +/// Validate query: Special case for insert +fn validate_insert_query(query: Query) -> Result { + if query.values.is_some() && query.columns.is_some() && query.where_condition.is_none() { + validate_table(query) + } else { + Err(error::INSERT_MAL_FORMATEADO) + } +} + +/// Validate query: Special case for select +fn validate_select_query(query: Query) -> Result { + if query.values.is_none() { + validate_table(query) + } else { + Err(error::SELECT_MAL_FORMATEADO) + } +} + +/// Validate query: Special case for update +fn validate_update_query(query: Query) -> Result { + if query.values.is_some() && query.columns.is_some() && query.where_condition.is_some() { + validate_table(query) + } else { + Err(error::UPDATE_MAL_FORMATEADO) + } +} + +/// Validate if table is valid +fn validate_table(query: Query) -> Result { + match &query.table { + Some(table) => match File::open(table) { + Ok(file) => match get_columns(file) { + Some(_) => Ok(query), + None => Err(error::ARCHIVO_VACIO), + }, + Err(_) => Err(error::ARCHIVO_NO_PUDO_SER_ABIERTO), + }, + None => Err(error::FALTA_ARCHIVO), + } +} + +/// AUX: Get columns from file +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, + } +} + +/// AUX: Get first line from file +/// Pre: query +/// Post: File first line, if any +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, + } +} + +/// Tokenization of text query by space, new line & coma +pub fn text_to_vec(text_query: &String, coma: bool) -> Vec { + let mut tmp_text_query = text_query.to_string(); + tmp_text_query = tmp_text_query.replace('\n', " "); + if !coma { + tmp_text_query = tmp_text_query.replace(',', ""); + } else { + 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 +} + +/// Parses vector of tokens into query +/// Pre: Vec of tokens from query & path to tables +/// Post: Vec to non validated query +fn vec_to_query(args: &[String], path: &String) -> Result { + let mut result: Query = build_empty_query(); + match check_operation_format(args) { + Ok(x) => match &x { + QueryType::DELETE => match parse_args_delete(args, path, &mut result) { + Ok(_) => result.operation = Some(x), + Err(x) => return Err(x), + }, + QueryType::INSERT => match parse_args_insert(args, path, &mut result) { + Ok(_) => result.operation = Some(x), + Err(x) => return Err(x), + }, + QueryType::SELECT => match parse_args_select(args, path, &mut result) { + Ok(_) => result.operation = Some(x), + Err(x) => return Err(x), + }, + QueryType::UPDATE => match parse_args_update(args, path, &mut result) { + Ok(_) => result.operation = Some(x), + Err(x) => return Err(x), + }, + }, + Err(x) => return Err(x), + } + + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_text_to_vec1() { + let rt1 = text_to_vec(&String::from("SELECT * FROM table"), false); + 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"), + false, + ); + assert_eq!( + rt2, + vec![ + "SELECT", + "id", + "producto", + "id_cliente", + "FROM", + "ordenes", + "WHERE", + "cantidad", + ">", + "1" + ] + ); + } +} + +/* +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) => cond.column.as_ref().map(|col| col.to_string()), + None => None, + } +} + +fn get_condition_node(query: &Query, args: &[String], counter: &mut usize) -> Result { + // Pre: Query args and counter of args + // Post: New node && increments counter + let new_node: ComplexCondition; + if args[*counter].eq("AND") { + new_node = build_complex_condition(BooleanOperator::AND, None, None); + *counter += 1; + } else if args[*counter].eq("OR") { + new_node = build_complex_condition(BooleanOperator::OR, None, None); + *counter += 1; + } else if args[*counter].eq("NOT") { + new_node = build_complex_condition(BooleanOperator::NOT, None, None); + *counter += 1; + } else if *counter + 3 < args.len() { + match get_columns_position(query, &[args[*counter].to_string()]) { + Err(x) => return Err(x), + Ok(x) => { + let simple_condition: Condition = if args[*counter + 2].eq("<") { + build_condition(x[0], args[*counter + 2].to_string(), ConditionOperator::Minor) + } else if args[*counter + 2].eq("<=") { + build_condition(x[0], args[*counter + 2].to_string(), ConditionOperator::MinorEqual) + } else if args[*counter + 2].eq("=") { + build_condition(x[0], args[*counter + 2].to_string(), ConditionOperator::Equal) + } else if args[*counter + 2].eq(">=") { + build_condition(x[0], args[*counter + 2].to_string(), ConditionOperator::HigherEqual) + } else if args[*counter + 2].eq(">") { + build_condition(x[0], args[*counter + 2].to_string(), ConditionOperator::Higher) + } else { + return Err(WHERE_MAL_FORMATEADO) + }; + new_node = build_simple_condition(Some(simple_condition)); + *counter += 3; + } + } + } else { + return Err(WHERE_MAL_FORMATEADO) + } + Ok(new_node) +} +*/ 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..2132594 --- /dev/null +++ b/src/query.rs @@ -0,0 +1,31 @@ +pub mod query_type; + +use query_type::QueryType; + +use crate::condition::Condition; + +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 + +/// Represents a SQL query +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"), + } + } +}