From 1e12a1e4fda8d18414317290d1989de07f27e52a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Douce?= <33525454+germandouce@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:33:36 -0300 Subject: [PATCH 01/54] Initial commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..69e2a97 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# tp-individual-taller-95.08 +repo xa el tp individual From a3e45ce0c2ab5292d0d550bebd3a98e8627b871c Mon Sep 17 00:00:00 2001 From: German Douce Date: Tue, 27 Aug 2024 14:42:18 -0300 Subject: [PATCH 02/54] second commit. Agrego comandos xa compilar al README.md --- .gitignore | 1 + Cargo.lock | 7 +++++++ Cargo.toml | 6 ++++++ README.md | 12 ++++++++++++ src/main.rs | 3 +++ 5 files changed, 29 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..baa1c2e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "tp-individual-taller-9508" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a51d521 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "tp-individual-taller-9508" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/README.md b/README.md index 69e2a97..36ef463 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ # tp-individual-taller-95.08 repo xa el tp individual + +Crear un nuevo proyecto: cargo new +I Crear un nuevo proyecto en un directorio existente: cargo init +(basta con ejecutarlo estando parado en ese directorio) +I Compilar el proyecto: cargo build +I Compilar el proyecto en modo release: cargo build –release +I Ejecutar el proyecto: cargo run +I Ejecutar los tests: cargo test +I Generar la documentación HTML: cargo doc +I Analizar el proyecto, sin compilar: cargo check +I Formatear el código: cargo fmt +I linter: cargo clippy diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 5bf3ea0e0a61ea3dc4262e261398445ab652d93e Mon Sep 17 00:00:00 2001 From: German Douce Date: Tue, 27 Aug 2024 14:43:55 -0300 Subject: [PATCH 03/54] formateo el README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 36ef463..4a2d124 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,22 @@ repo xa el tp individual Crear un nuevo proyecto: cargo new + I Crear un nuevo proyecto en un directorio existente: cargo init (basta con ejecutarlo estando parado en ese directorio) + I Compilar el proyecto: cargo build + I Compilar el proyecto en modo release: cargo build –release + I Ejecutar el proyecto: cargo run + I Ejecutar los tests: cargo test + I Generar la documentación HTML: cargo doc + I Analizar el proyecto, sin compilar: cargo check + I Formatear el código: cargo fmt + I linter: cargo clippy From 7b6ea4fbbf4fef944caa32b3a8f331636fde7c56 Mon Sep 17 00:00:00 2001 From: German Douce Date: Tue, 27 Aug 2024 16:05:12 -0300 Subject: [PATCH 04/54] leo args por consola y sepoato path a la carpeta de la consulta --- src/main.rs | 18 ++++++++++++++++-- tablas/clientes.csv | 7 +++++++ tablas/ordenes.csv | 11 +++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tablas/clientes.csv create mode 100644 tablas/ordenes.csv diff --git a/src/main.rs b/src/main.rs index e7a11a9..8b75163 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,17 @@ +use std::env; + fn main() { - println!("Hello, world!"); -} + //leo argumentos + let args: Vec = env::args().collect(); + + if args.len() < 3 { + println!("[ERROR]: Por favor, proporcione una ruta a la carpeta de tablas y una consulta SQL."); + return; + } + + let ruta_carpeta_tablas = &args[1]; + let consulta = &args[2]; + + println!("Ruta a las tablas: {}", ruta_carpeta_tablas); + println!("Consulta sql: {}", consulta) +} \ No newline at end of file diff --git a/tablas/clientes.csv b/tablas/clientes.csv new file mode 100644 index 0000000..1ccbf63 --- /dev/null +++ b/tablas/clientes.csv @@ -0,0 +1,7 @@ +id,nombre,apellido,email +1,Juan,Pérez,juan.perez@email.com +2,Ana,López,ana.lopez@email.com +3,Carlos,Gómez,carlos.gomez@email.com +4,María,Rodríguez,maria.rodriguez@email.com +5,José,López,jose.lopez@email.com +6,Laura,Fernández,laura.fernandez@email.com \ No newline at end of file diff --git a/tablas/ordenes.csv b/tablas/ordenes.csv new file mode 100644 index 0000000..1ee9dbe --- /dev/null +++ b/tablas/ordenes.csv @@ -0,0 +1,11 @@ +id,id_cliente,producto,cantidad +101,1,Laptop,1 +103,1,Monitor,1 +102,2,Teléfono,2 +104,3,Teclado,1 +105,4,Mouse,2 +106,5,Impresora,1 +107,6,Altavoces,1 +108,4,Auriculares,1 +109,5,Laptop,1 +110,6,Teléfono,2 \ No newline at end of file From 843b20d36aed0f706a633f812381aa6c87bcd7b7 Mon Sep 17 00:00:00 2001 From: German Douce Date: Tue, 27 Aug 2024 20:59:53 -0300 Subject: [PATCH 05/54] agrego funcion para parsear la consulta y obtener nombre tabla --- .idea/.gitignore | 5 +++ .idea/modules.xml | 8 +++++ .idea/tp-individual-taller-9508.iml | 11 +++++++ .idea/vcs.xml | 6 ++++ src/main.rs | 50 +++++++++++++++++++++++++++-- 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/tp-individual-taller-9508.iml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..50979f2 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/tp-individual-taller-9508.iml b/.idea/tp-individual-taller-9508.iml new file mode 100644 index 0000000..cf84ae4 --- /dev/null +++ b/.idea/tp-individual-taller-9508.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8b75163..7af41b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,9 @@ fn main() { let args: Vec = env::args().collect(); if args.len() < 3 { - println!("[ERROR]: Por favor, proporcione una ruta a la carpeta de tablas y una consulta SQL."); + println!( + "[ERROR]: Por favor, proporcione una ruta a la carpeta de tablas y una consulta SQL." + ); return; } @@ -13,5 +15,47 @@ fn main() { let consulta = &args[2]; println!("Ruta a las tablas: {}", ruta_carpeta_tablas); - println!("Consulta sql: {}", consulta) -} \ No newline at end of file + println!("Consulta sql: {}", consulta); + + //obtengo el nombre de la tabla + + match parsear_consulta(consulta) { + Some(nombre_tabla) => { + println!("Nombre de la tabla: {}", nombre_tabla); + let file_path = format!("{}/{}.csv", ruta_carpeta_tablas, nombre_tabla); + println!("Ruta del archivo: {}", file_path); + } + None => { + println!("[ERROR]: No se pudo encontrar el nombre de la tabla en la consulta."); + } + } +} + +fn parsear_consulta(consulta: &str) -> Option { + let consulta_minuscula = consulta.to_lowercase(); + + //"nombre" de consulta" + + if consulta_minuscula.starts_with("select") { + extraer_nombre_tabla(&consulta_minuscula, "from") + } else if consulta_minuscula.starts_with("insert into") { + extraer_nombre_tabla(&consulta_minuscula, "into") + } else if consulta_minuscula.starts_with("update") { + extraer_nombre_tabla(&consulta_minuscula, "update") + } else if consulta_minuscula.starts_with("delete from") { + extraer_nombre_tabla(&consulta_minuscula, "from") + } else { + None + } +} + +fn extraer_nombre_tabla(consulta: &str, palabra_clave: &str) -> Option { + let palabras_consulta: Vec<&str> = consulta.split_whitespace().collect(); + for i in 0..palabras_consulta.len() { + if palabras_consulta[i] == palabra_clave && i + 1 < palabras_consulta.len() { + return Some(palabras_consulta[i + 1].to_string()); // Devuelvo la palabra (nombre de la tabla) que viene justo dssps de la palabra clave + //select from table, insert into table, update table, delete from table + } + } + None // si no encuentra la palabra clave o no viene nada dsps de ella +} From 0d54ccdea14f31f1afe184ffcf457cf009bdeb4e Mon Sep 17 00:00:00 2001 From: German Douce Date: Wed, 28 Aug 2024 01:16:21 -0300 Subject: [PATCH 06/54] cambio completamente el enfoque dsps de 4 hrs de no llegar a nada. Mi dios no juegas dados, quiza esta a mi favor?? no compila --- src/main.rs | 61 ++++++++++++----------------------- src/parser.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 41 deletions(-) create mode 100644 src/parser.rs diff --git a/src/main.rs b/src/main.rs index 7af41b4..12f3faa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,15 @@ +mod parser; + +use parser::parsear_consulta; use std::env; fn main() { - //leo argumentos + // leo argumentos let args: Vec = env::args().collect(); - - if args.len() < 3 { + if args.len() <3 { println!( "[ERROR]: Por favor, proporcione una ruta a la carpeta de tablas y una consulta SQL." ); - return; } let ruta_carpeta_tablas = &args[1]; @@ -17,45 +18,23 @@ fn main() { println!("Ruta a las tablas: {}", ruta_carpeta_tablas); println!("Consulta sql: {}", consulta); - //obtengo el nombre de la tabla - match parsear_consulta(consulta) { - Some(nombre_tabla) => { - println!("Nombre de la tabla: {}", nombre_tabla); - let file_path = format!("{}/{}.csv", ruta_carpeta_tablas, nombre_tabla); - println!("Ruta del archivo: {}", file_path); - } - None => { - println!("[ERROR]: No se pudo encontrar el nombre de la tabla en la consulta."); + // Parsear la consulta + let _comando = match parsear_consulta(consulta) { + Ok(result) => result, + Err(_e) => { + println!("Error al parsear la consulta:"); + std::process::exit(1); } - } -} + }; -fn parsear_consulta(consulta: &str) -> Option { - let consulta_minuscula = consulta.to_lowercase(); - - //"nombre" de consulta" - - if consulta_minuscula.starts_with("select") { - extraer_nombre_tabla(&consulta_minuscula, "from") - } else if consulta_minuscula.starts_with("insert into") { - extraer_nombre_tabla(&consulta_minuscula, "into") - } else if consulta_minuscula.starts_with("update") { - extraer_nombre_tabla(&consulta_minuscula, "update") - } else if consulta_minuscula.starts_with("delete from") { - extraer_nombre_tabla(&consulta_minuscula, "from") - } else { - None - } -} + //vep soi hago esto o no + //construyo la ruta + // let _file_path = format!("{}/{}.csv", ruta_carpeta_tablas, nombre_tabla); -fn extraer_nombre_tabla(consulta: &str, palabra_clave: &str) -> Option { - let palabras_consulta: Vec<&str> = consulta.split_whitespace().collect(); - for i in 0..palabras_consulta.len() { - if palabras_consulta[i] == palabra_clave && i + 1 < palabras_consulta.len() { - return Some(palabras_consulta[i + 1].to_string()); // Devuelvo la palabra (nombre de la tabla) que viene justo dssps de la palabra clave - //select from table, insert into table, update table, delete from table - } - } - None // si no encuentra la palabra clave o no viene nada dsps de ella + // Ejecuto el comando + //tengo q ver si el comando guarda o no el nombre de la tabla con si. seguramnte + // if let Err(e) = comando.ejecutar(&file_path) { + // println!("Error al ejecutar el comando:"); + // } } diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..2b1bd87 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,88 @@ +pub enum Comando { + Select(Select), + // Insert(Insert), + // Update(Update), + // Delete(Delete), +} + +pub struct Select { + pub columnas: Vec, + pub restricciones: Option, + pub ordenamiento: Option, +} + +// pub struct Insert { +// } +// +// pub struct Update { +// } +// +// pub struct Delete { +// } + +#[derive(Debug)] +pub enum ParseError { + ComandoNoReconocido, +} + +// leo la consulta SQL y devuelve un comando específico. +pub fn parsear_consulta(consulta: &str) -> Result { + let consulta = consulta.to_lowercase(); + + // Determino el tipo de comando y me guardo el resto de la consulta + let resto = if consulta.starts_with("select") { + &consulta[6..] + // } else if consulta.starts_with("insert into") { + // &consulta[11..] + // } else if consulta.starts_with("update") { + // &consulta[6..] + // } else if consulta.starts_with("delete from") { + // &consulta[12..] + } else { + return Err(ParseError::ComandoNoReconocido); + }; + + // Crear el comando basado en el tipo de comando detectado + let comando = if consulta.starts_with("select") { + let select = parse_select(resto)?; + Comando::Select(select) + // } else if consulta.starts_with("insert into") { + // let insert = parse_insert(resto)?; + // Comando::Insert(insert) + // } else if consulta.starts_with("update") { + // let update = parse_update(resto)?; + // Comando::Update(update) + // } else if consulta.starts_with("delete from") { + // let delete = parse_delete(resto)?; + // Comando::Delete(delete) + } else { + return Err(ParseError::ComandoNoReconocido); + }; + + Ok(comando) // Devolvemos el comando directamente, no una tupla +} + + +/// Función para analizar una consulta SELECT. +fn parse_select(_resto: &str) -> Result { + Ok(Select { + columnas: vec!["id".to_string(), "producto".to_string()], + restricciones: Some("cantidad > 1".to_string()), + ordenamiento: None, + }) +} + +// fn parse_insert(resto: &str) -> Result { +// Ok(Insert { +// }) +// } +// +// fn parse_update(resto: &str) -> Result { +// Ok(Update { +// }) +// } +// +// fn parse_delete(resto: &str) -> Result { +// Ok(Delete { +// }) +// } From dd107198b3547ee566b5c47d82ad44762fcf955a Mon Sep 17 00:00:00 2001 From: German Douce Date: Wed, 28 Aug 2024 17:24:07 -0300 Subject: [PATCH 07/54] agrego logica a parse_select y ejecutar select. El struct select ya guarda la info de la consulta --- src/main.rs | 16 +++--- src/parser.rs | 144 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 126 insertions(+), 34 deletions(-) diff --git a/src/main.rs b/src/main.rs index 12f3faa..0526631 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,12 +15,8 @@ fn main() { let ruta_carpeta_tablas = &args[1]; let consulta = &args[2]; - println!("Ruta a las tablas: {}", ruta_carpeta_tablas); - println!("Consulta sql: {}", consulta); - - // Parsear la consulta - let _comando = match parsear_consulta(consulta) { + let comando = match parsear_consulta(consulta) { Ok(result) => result, Err(_e) => { println!("Error al parsear la consulta:"); @@ -28,13 +24,19 @@ fn main() { } }; + println!("Ruta a las tablas: {}", ruta_carpeta_tablas); + println!("Consulta sql: {}", consulta); + //vep soi hago esto o no //construyo la ruta - // let _file_path = format!("{}/{}.csv", ruta_carpeta_tablas, nombre_tabla); + let file_path = format!("{}/{}.csv", ruta_carpeta_tablas, "clientes"); // Ejecuto el comando //tengo q ver si el comando guarda o no el nombre de la tabla con si. seguramnte + + let _res = comando.ejecutar(&file_path); + // if let Err(e) = comando.ejecutar(&file_path) { - // println!("Error al ejecutar el comando:"); + // println!("Error al ejecutar el comando:", e); // } } diff --git a/src/parser.rs b/src/parser.rs index 2b1bd87..c050e3a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,12 +5,49 @@ pub enum Comando { // Delete(Delete), } +impl Comando { + pub fn ejecutar(&self, ruta_archivo: &str) -> Result<(), String>{ + match self { + Comando::Select(select) => select.ejecutar(ruta_archivo), + } + } +} + +use std::fs::File; +use std::io::{BufRead, BufReader}; + pub struct Select { pub columnas: Vec, + pub tabla: String, pub restricciones: Option, pub ordenamiento: Option, } +impl Select { + pub fn ejecutar(&self, ruta_archivo: &str) -> Result<(), String> { + println!("Ejecutar consulta en la tabla: {:?}", self.tabla); + println!("Columnas a seleccionar: {:?}", self.columnas); + if let Some(ref restricciones) = self.restricciones { + println!("Restricciones: {:?}", restricciones); + } + if let Some(ref ordenamiento) = self.ordenamiento { + println!("Order by: {:?}", ordenamiento); + } + + let file = File::open(ruta_archivo).map_err(|e| e.to_string())?; + let reader = BufReader::new(file); + + for line in reader.lines() { + let linea = line.map_err(|e| e.to_string())?; + // aplico restricciones + println!("{}", linea); + } + + Ok(()) + } +} + + // pub struct Insert { // } // @@ -23,40 +60,46 @@ pub struct Select { #[derive(Debug)] pub enum ParseError { ComandoNoReconocido, + ErrorDeSintaxis, } // leo la consulta SQL y devuelve un comando específico. pub fn parsear_consulta(consulta: &str) -> Result { + let consulta = consulta.to_lowercase(); - // Determino el tipo de comando y me guardo el resto de la consulta - let resto = if consulta.starts_with("select") { - &consulta[6..] - // } else if consulta.starts_with("insert into") { - // &consulta[11..] - // } else if consulta.starts_with("update") { - // &consulta[6..] - // } else if consulta.starts_with("delete from") { - // &consulta[12..] + // Determino el tipo de comando y guardar el resto de la consulta + let (comando, resto) = if let Some(resto) = consulta.strip_prefix("select") { + ("select", resto.trim()) + } else if let Some(resto) = consulta.strip_prefix("insert into") { + ("insert", resto.trim()) + } else if let Some(resto) = consulta.strip_prefix("update") { + ("update", resto.trim()) + } else if let Some(resto) = consulta.strip_prefix("delete from") { + ("delete", resto.trim()) } else { return Err(ParseError::ComandoNoReconocido); }; // Crear el comando basado en el tipo de comando detectado - let comando = if consulta.starts_with("select") { - let select = parse_select(resto)?; - Comando::Select(select) - // } else if consulta.starts_with("insert into") { - // let insert = parse_insert(resto)?; - // Comando::Insert(insert) - // } else if consulta.starts_with("update") { - // let update = parse_update(resto)?; - // Comando::Update(update) - // } else if consulta.starts_with("delete from") { - // let delete = parse_delete(resto)?; - // Comando::Delete(delete) - } else { - return Err(ParseError::ComandoNoReconocido); + let comando = match comando { + "select" => { + let select = parse_select(resto)?; + Comando::Select(select) + }, + // "insert" => { + // let insert = parse_insert(resto)?; + // Comando::Insert(insert) + // }, + // "update" => { + // let update = parse_update(resto)?; + // Comando::Update(update) + // }, + // "delete" => { + // let delete = parse_delete(resto)?; + // Comando::Delete(delete) + // }, + _ => return Err(ParseError::ComandoNoReconocido), }; Ok(comando) // Devolvemos el comando directamente, no una tupla @@ -64,11 +107,58 @@ pub fn parsear_consulta(consulta: &str) -> Result { /// Función para analizar una consulta SELECT. -fn parse_select(_resto: &str) -> Result { +fn parse_select(resto: &str) -> Result { + // Separo la consulta en la parte de las columnas y la parte de las restricciones (si existen) + // resto = SELECT id, producto, id_cliente FROM ordenes WHERE cantidad > 1 ORDER BY email DESC; + // columnas_y_tabla = "SELECT id, producto, id_cliente FROM ordenes" + // restricciones = cantidad > 1 ORDER BY email DESC; + let (columnas_y_tabla, restricciones) = + if let Some((partes, restricciones)) = resto.split_once("where") { + (partes.trim(), Some(restricciones.trim())) // elimina espacios en blanco x las dudas + } else { + (resto, None) // "SELECT id, producto, id_cliente FROM ordenes" (no habia where) + }; + + // Separo la parte de las columnas y la tabla usando "from" + // columnas_y_tabla = "SELECT id, producto, id_cliente FROM ordenes" + // columnas = "id, producto, id_cliente" + // tabla_y_ordenamiento = "ordenes" + let (columnas, tabla_y_ordenamiento) = if let Some((columnas, tabla)) = columnas_y_tabla.split_once("from") { + (columnas.trim_start_matches("select").trim(), tabla.trim()) //saco select + } else { + return Err(ParseError::ErrorDeSintaxis); + }; + + // Convierto la parte de las columnas en un vector de strings + //columnas =["id", "producto", "id_cliente"] + let columnas: Vec = columnas + .split(',') + .map(|columna| columna.trim().to_string()) // Limpia los espacios en blanco + .collect(); + + // Separar las restricciones y el ordenamiento si existen + // restricciones = cantidad > 1 ORDER BY email DESC; + // restricciones = cantidad > 1 + // ordenamiento = email DESC + let (restricciones, ordenamiento) = if let Some(restricciones) = restricciones { + // Intento separar las restricciones y el ordenamiento usando "order by" + if let Some((restricciones, orden)) = restricciones.split_once("order by") { + (Some(restricciones.trim().to_string()), Some(orden.trim().to_string())) + } else { + (Some(restricciones.to_string()), None) // No había "order by", solo restricciones + } + } else { + (None, None) // No había restricciones ni ordenamiento + }; + + let tabla = tabla_y_ordenamiento.trim().to_string(); + + // Devolver un struct Select con las columnas, restricciones, y ordenamiento encontrados Ok(Select { - columnas: vec!["id".to_string(), "producto".to_string()], - restricciones: Some("cantidad > 1".to_string()), - ordenamiento: None, + columnas, + tabla, + restricciones: restricciones.map(String::from), + ordenamiento: ordenamiento.map(String::from), }) } From e95a50cd2116ba51dce32f42996b179103f8c5fb Mon Sep 17 00:00:00 2001 From: German Douce Date: Thu, 29 Aug 2024 15:06:36 -0300 Subject: [PATCH 08/54] cargo fmt --- src/main.rs | 2 +- src/parser.rs | 33 +++++++++++++++++---------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0526631..8706e8a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use std::env; fn main() { // leo argumentos let args: Vec = env::args().collect(); - if args.len() <3 { + if args.len() < 3 { println!( "[ERROR]: Por favor, proporcione una ruta a la carpeta de tablas y una consulta SQL." ); diff --git a/src/parser.rs b/src/parser.rs index c050e3a..1dd49a2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -6,7 +6,7 @@ pub enum Comando { } impl Comando { - pub fn ejecutar(&self, ruta_archivo: &str) -> Result<(), String>{ + pub fn ejecutar(&self, ruta_archivo: &str) -> Result<(), String> { match self { Comando::Select(select) => select.ejecutar(ruta_archivo), } @@ -47,7 +47,6 @@ impl Select { } } - // pub struct Insert { // } // @@ -65,7 +64,6 @@ pub enum ParseError { // leo la consulta SQL y devuelve un comando específico. pub fn parsear_consulta(consulta: &str) -> Result { - let consulta = consulta.to_lowercase(); // Determino el tipo de comando y guardar el resto de la consulta @@ -86,7 +84,7 @@ pub fn parsear_consulta(consulta: &str) -> Result { "select" => { let select = parse_select(resto)?; Comando::Select(select) - }, + } // "insert" => { // let insert = parse_insert(resto)?; // Comando::Insert(insert) @@ -102,10 +100,9 @@ pub fn parsear_consulta(consulta: &str) -> Result { _ => return Err(ParseError::ComandoNoReconocido), }; - Ok(comando) // Devolvemos el comando directamente, no una tupla + Ok(comando) // Devolvemos el comando directamente, no una tupla } - /// Función para analizar una consulta SELECT. fn parse_select(resto: &str) -> Result { // Separo la consulta en la parte de las columnas y la parte de las restricciones (si existen) @@ -114,20 +111,21 @@ fn parse_select(resto: &str) -> Result { // restricciones = cantidad > 1 ORDER BY email DESC; let (columnas_y_tabla, restricciones) = if let Some((partes, restricciones)) = resto.split_once("where") { - (partes.trim(), Some(restricciones.trim())) // elimina espacios en blanco x las dudas - } else { - (resto, None) // "SELECT id, producto, id_cliente FROM ordenes" (no habia where) - }; + (partes.trim(), Some(restricciones.trim())) // elimina espacios en blanco x las dudas + } else { + (resto, None) // "SELECT id, producto, id_cliente FROM ordenes" (no habia where) + }; // Separo la parte de las columnas y la tabla usando "from" // columnas_y_tabla = "SELECT id, producto, id_cliente FROM ordenes" // columnas = "id, producto, id_cliente" // tabla_y_ordenamiento = "ordenes" - let (columnas, tabla_y_ordenamiento) = if let Some((columnas, tabla)) = columnas_y_tabla.split_once("from") { - (columnas.trim_start_matches("select").trim(), tabla.trim()) //saco select - } else { - return Err(ParseError::ErrorDeSintaxis); - }; + let (columnas, tabla_y_ordenamiento) = + if let Some((columnas, tabla)) = columnas_y_tabla.split_once("from") { + (columnas.trim_start_matches("select").trim(), tabla.trim()) //saco select + } else { + return Err(ParseError::ErrorDeSintaxis); + }; // Convierto la parte de las columnas en un vector de strings //columnas =["id", "producto", "id_cliente"] @@ -143,7 +141,10 @@ fn parse_select(resto: &str) -> Result { let (restricciones, ordenamiento) = if let Some(restricciones) = restricciones { // Intento separar las restricciones y el ordenamiento usando "order by" if let Some((restricciones, orden)) = restricciones.split_once("order by") { - (Some(restricciones.trim().to_string()), Some(orden.trim().to_string())) + ( + Some(restricciones.trim().to_string()), + Some(orden.trim().to_string()), + ) } else { (Some(restricciones.to_string()), None) // No había "order by", solo restricciones } From f82a03c453cd5ed8f817eabecd254a571f37dba5 Mon Sep 17 00:00:00 2001 From: German Douce Date: Thu, 29 Aug 2024 17:46:49 -0300 Subject: [PATCH 09/54] implemento select col1, col2 from table. Parece funcar. Al linter no le gustan mis errores.. --- output.csv | 14 +++++++ src/main.rs | 26 ++++++------ src/parser.rs | 113 ++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 124 insertions(+), 29 deletions(-) create mode 100644 output.csv diff --git a/output.csv b/output.csv new file mode 100644 index 0000000..046fb95 --- /dev/null +++ b/output.csv @@ -0,0 +1,14 @@ +Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas +Consulta sql: SELECT id_cliente FROM ordenes +Ejecutar consulta en la tabla: "ordenes" +Columnas a seleccionar: ["id_cliente"] +1 +1 +2 +3 +4 +5 +6 +4 +5 +6 diff --git a/src/main.rs b/src/main.rs index 8706e8a..1478ba4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,16 +27,18 @@ fn main() { println!("Ruta a las tablas: {}", ruta_carpeta_tablas); println!("Consulta sql: {}", consulta); - //vep soi hago esto o no - //construyo la ruta - let file_path = format!("{}/{}.csv", ruta_carpeta_tablas, "clientes"); - - // Ejecuto el comando - //tengo q ver si el comando guarda o no el nombre de la tabla con si. seguramnte - - let _res = comando.ejecutar(&file_path); - - // if let Err(e) = comando.ejecutar(&file_path) { - // println!("Error al ejecutar el comando:", e); - // } + match comando.ejecutar(ruta_carpeta_tablas) { + Ok(results) => { + // Imprimir los resultados + comando.imprimir_resultados(&results); + } + Err(err) => { + println!("Error: {:?}", err); + } + } } + +// if let Err(e) = comando.ejecutar(&file_path) { +// println!("Error al ejecutar el comando:", e); +// } +// } diff --git a/src/parser.rs b/src/parser.rs index 1dd49a2..1649dab 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -6,9 +6,14 @@ pub enum Comando { } impl Comando { - pub fn ejecutar(&self, ruta_archivo: &str) -> Result<(), String> { + pub fn ejecutar(&self, ruta_carpeta: &str) -> Result>, ParseError> { match self { - Comando::Select(select) => select.ejecutar(ruta_archivo), + Comando::Select(select) => select.ejecutar(ruta_carpeta), + } + } + pub fn imprimir_resultados(&self, results: &[Vec]) { + match self { + Comando::Select(_select) => Select::imprimir_resultados(results), } } } @@ -16,6 +21,13 @@ impl Comando { use std::fs::File; use std::io::{BufRead, BufReader}; +#[derive(Debug)] +pub enum ParseError { + ComandoNoReconocido, + ErrorDeSintaxis, + Error(String), +} + pub struct Select { pub columnas: Vec, pub tabla: String, @@ -24,7 +36,7 @@ pub struct Select { } impl Select { - pub fn ejecutar(&self, ruta_archivo: &str) -> Result<(), String> { + pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, ParseError> { println!("Ejecutar consulta en la tabla: {:?}", self.tabla); println!("Columnas a seleccionar: {:?}", self.columnas); if let Some(ref restricciones) = self.restricciones { @@ -34,16 +46,89 @@ impl Select { println!("Order by: {:?}", ordenamiento); } - let file = File::open(ruta_archivo).map_err(|e| e.to_string())?; - let reader = BufReader::new(file); + // Abro el archivo CSV + let ruta_archivo = format!("{}/{}.csv", ruta_carpeta_tablas, self.tabla); + let file = File::open(&ruta_archivo).map_err(|err| { + ParseError::Error(format!( + "Error al abrir el archivo '{}': {}", + ruta_archivo, err + )) + })?; + let mut reader = BufReader::new(file); + + // Leo la primera línea con las columnas + let mut linea_encabezados = String::new(); + if reader.read_line(&mut linea_encabezados).map_err(|err| { + ParseError::Error(format!( + "Error al leer el archivo '{}': {}", + ruta_archivo, err + )) + })? == 0 + { + return Err(ParseError::Error(format!( + "El archivo '{}' está vacío", + ruta_archivo + ))); + } + let vector_encabezados: Vec<&str> = linea_encabezados.trim_end().split(',').collect(); + + let mut resultado = Vec::new(); + + // leo linea a linea + for linea in reader.lines() { + let line = linea.map_err(|err| { + ParseError::Error(format!( + "Error al leer una línea del archivo '{}': {}", + ruta_archivo, err + )) + })?; + let registro: Vec<&str> = line.split(',').collect(); - for line in reader.lines() { - let linea = line.map_err(|e| e.to_string())?; - // aplico restricciones - println!("{}", linea); + // Aplico restricciones (WHERE) + if self.aplicar_restricciones(®istro, &vector_encabezados)? { + // Crear un vector para las columnas seleccionadas + let mut columnas_select = Vec::new(); + + for col in &self.columnas { + // Encuentro índice de la columna en los encabezados + match vector_encabezados + .iter() + .position(|&encabezado| encabezado == col.as_str()) + { + Some(index) => { + // Obtener el valor de la columna, si existe + if let Some(value) = registro.get(index) { + columnas_select.push(value.to_string()); + } else { + // Si el índice está fuera de rango, meto una cadena vacía + columnas_select.push(String::new()); + } + } + None => { + // Si no se encuentra el índice, meto una cadena vacía + columnas_select.push(String::new()); + } + } + } + resultado.push(columnas_select); + } } + Ok(resultado) + } - Ok(()) + fn aplicar_restricciones(&self, _registro: &[&str], _encabezados: &[&str]) -> Result { + //LOgica de los (WHERE) + Ok(true) + } +} + +impl Select { + pub fn imprimir_resultados(results: &[Vec]) { + for row in results { + // Imprimir cada fila + let row_string = row.join(", "); + println!("{}", row_string); + } } } @@ -56,12 +141,6 @@ impl Select { // pub struct Delete { // } -#[derive(Debug)] -pub enum ParseError { - ComandoNoReconocido, - ErrorDeSintaxis, -} - // leo la consulta SQL y devuelve un comando específico. pub fn parsear_consulta(consulta: &str) -> Result { let consulta = consulta.to_lowercase(); @@ -103,7 +182,7 @@ pub fn parsear_consulta(consulta: &str) -> Result { Ok(comando) // Devolvemos el comando directamente, no una tupla } -/// Función para analizar una consulta SELECT. +//praseo el select fn parse_select(resto: &str) -> Result { // Separo la consulta en la parte de las columnas y la parte de las restricciones (si existen) // resto = SELECT id, producto, id_cliente FROM ordenes WHERE cantidad > 1 ORDER BY email DESC; From 3ef9b0342cb5a1b54528480ebf37ee1264eee808 Mon Sep 17 00:00:00 2001 From: German Douce Date: Thu, 29 Aug 2024 20:31:34 -0300 Subject: [PATCH 10/54] modularizo dividiendo en varios archivos --- output.csv | 24 ++++----- src/comandos.rs | 22 ++++++++ src/errores.rs | 6 +++ src/main.rs | 8 ++- src/parser.rs | 137 ++---------------------------------------------- src/select.rs | 111 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 158 insertions(+), 150 deletions(-) create mode 100644 src/comandos.rs create mode 100644 src/errores.rs create mode 100644 src/select.rs diff --git a/output.csv b/output.csv index 046fb95..f24229f 100644 --- a/output.csv +++ b/output.csv @@ -1,14 +1,14 @@ Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas -Consulta sql: SELECT id_cliente FROM ordenes +Consulta sql: SELECT producto FROM ordenes Ejecutar consulta en la tabla: "ordenes" -Columnas a seleccionar: ["id_cliente"] -1 -1 -2 -3 -4 -5 -6 -4 -5 -6 +Columnas a seleccionar: ["producto"] +Laptop +Monitor +Teléfono +Teclado +Mouse +Impresora +Altavoces +Auriculares +Laptop +Teléfono diff --git a/src/comandos.rs b/src/comandos.rs new file mode 100644 index 0000000..40ae3d0 --- /dev/null +++ b/src/comandos.rs @@ -0,0 +1,22 @@ +use crate::errores::ParseError; +use crate::select::Select; + +pub enum Comando { + Select(Select), + // Insert(Insert), + // Update(Update), + // Delete(Delete), +} + +impl Comando { + pub fn ejecutar(&self, ruta_carpeta: &str) -> Result>, ParseError> { + match self { + Comando::Select(select) => select.ejecutar(ruta_carpeta), + } + } + pub fn imprimir_resultados(&self, results: &[Vec]) { + match self { + Comando::Select(_select) => Select::imprimir_resultados(results), + } + } +} diff --git a/src/errores.rs b/src/errores.rs new file mode 100644 index 0000000..a2f1bab --- /dev/null +++ b/src/errores.rs @@ -0,0 +1,6 @@ +#[derive(Debug)] +pub enum ParseError { + ComandoNoReconocido, + ErrorDeSintaxis, + Error(String), +} diff --git a/src/main.rs b/src/main.rs index 1478ba4..4245ce2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,7 @@ +mod comandos; +mod errores; mod parser; +mod select; use parser::parsear_consulta; use std::env; @@ -37,8 +40,3 @@ fn main() { } } } - -// if let Err(e) = comando.ejecutar(&file_path) { -// println!("Error al ejecutar el comando:", e); -// } -// } diff --git a/src/parser.rs b/src/parser.rs index 1649dab..b3c1191 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,136 +1,7 @@ -pub enum Comando { - Select(Select), - // Insert(Insert), - // Update(Update), - // Delete(Delete), -} - -impl Comando { - pub fn ejecutar(&self, ruta_carpeta: &str) -> Result>, ParseError> { - match self { - Comando::Select(select) => select.ejecutar(ruta_carpeta), - } - } - pub fn imprimir_resultados(&self, results: &[Vec]) { - match self { - Comando::Select(_select) => Select::imprimir_resultados(results), - } - } -} - -use std::fs::File; -use std::io::{BufRead, BufReader}; - -#[derive(Debug)] -pub enum ParseError { - ComandoNoReconocido, - ErrorDeSintaxis, - Error(String), -} - -pub struct Select { - pub columnas: Vec, - pub tabla: String, - pub restricciones: Option, - pub ordenamiento: Option, -} - -impl Select { - pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, ParseError> { - println!("Ejecutar consulta en la tabla: {:?}", self.tabla); - println!("Columnas a seleccionar: {:?}", self.columnas); - if let Some(ref restricciones) = self.restricciones { - println!("Restricciones: {:?}", restricciones); - } - if let Some(ref ordenamiento) = self.ordenamiento { - println!("Order by: {:?}", ordenamiento); - } - - // Abro el archivo CSV - let ruta_archivo = format!("{}/{}.csv", ruta_carpeta_tablas, self.tabla); - let file = File::open(&ruta_archivo).map_err(|err| { - ParseError::Error(format!( - "Error al abrir el archivo '{}': {}", - ruta_archivo, err - )) - })?; - let mut reader = BufReader::new(file); - - // Leo la primera línea con las columnas - let mut linea_encabezados = String::new(); - if reader.read_line(&mut linea_encabezados).map_err(|err| { - ParseError::Error(format!( - "Error al leer el archivo '{}': {}", - ruta_archivo, err - )) - })? == 0 - { - return Err(ParseError::Error(format!( - "El archivo '{}' está vacío", - ruta_archivo - ))); - } - let vector_encabezados: Vec<&str> = linea_encabezados.trim_end().split(',').collect(); - - let mut resultado = Vec::new(); - - // leo linea a linea - for linea in reader.lines() { - let line = linea.map_err(|err| { - ParseError::Error(format!( - "Error al leer una línea del archivo '{}': {}", - ruta_archivo, err - )) - })?; - let registro: Vec<&str> = line.split(',').collect(); - - // Aplico restricciones (WHERE) - if self.aplicar_restricciones(®istro, &vector_encabezados)? { - // Crear un vector para las columnas seleccionadas - let mut columnas_select = Vec::new(); - - for col in &self.columnas { - // Encuentro índice de la columna en los encabezados - match vector_encabezados - .iter() - .position(|&encabezado| encabezado == col.as_str()) - { - Some(index) => { - // Obtener el valor de la columna, si existe - if let Some(value) = registro.get(index) { - columnas_select.push(value.to_string()); - } else { - // Si el índice está fuera de rango, meto una cadena vacía - columnas_select.push(String::new()); - } - } - None => { - // Si no se encuentra el índice, meto una cadena vacía - columnas_select.push(String::new()); - } - } - } - resultado.push(columnas_select); - } - } - Ok(resultado) - } - - fn aplicar_restricciones(&self, _registro: &[&str], _encabezados: &[&str]) -> Result { - //LOgica de los (WHERE) - Ok(true) - } -} - -impl Select { - pub fn imprimir_resultados(results: &[Vec]) { - for row in results { - // Imprimir cada fila - let row_string = row.join(", "); - println!("{}", row_string); - } - } -} +// use std::io::{BufRead, BufReader}; +use crate::comandos::Comando; +use crate::errores::ParseError; +use crate::select::Select; // pub struct Insert { // } diff --git a/src/select.rs b/src/select.rs new file mode 100644 index 0000000..7d01a3e --- /dev/null +++ b/src/select.rs @@ -0,0 +1,111 @@ +use crate::errores::ParseError; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +pub struct Select { + pub columnas: Vec, + pub tabla: String, + pub restricciones: Option, + pub ordenamiento: Option, +} + +impl Select { + pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, ParseError> { + println!("Ejecutar consulta en la tabla: {:?}", self.tabla); + println!("Columnas a seleccionar: {:?}", self.columnas); + if let Some(ref restricciones) = self.restricciones { + println!("Restricciones: {:?}", restricciones); + } + if let Some(ref ordenamiento) = self.ordenamiento { + println!("Order by: {:?}", ordenamiento); + } + + // Abro el archivo CSV + let ruta_archivo = format!("{}/{}.csv", ruta_carpeta_tablas, self.tabla); + let file = File::open(&ruta_archivo).map_err(|err| { + ParseError::Error(format!( + "Error al abrir el archivo '{}': {}", + ruta_archivo, err + )) + })?; + let mut reader = BufReader::new(file); + + // Leo la primera línea con las columnas + let mut linea_encabezados = String::new(); + if reader.read_line(&mut linea_encabezados).map_err(|err| { + ParseError::Error(format!( + "Error al leer el archivo '{}': {}", + ruta_archivo, err + )) + })? == 0 + { + return Err(ParseError::Error(format!( + "El archivo '{}' está vacío", + ruta_archivo + ))); + } + let vector_encabezados: Vec<&str> = linea_encabezados.trim_end().split(',').collect(); + + let mut resultado = Vec::new(); + + // leo linea a linea + for linea in reader.lines() { + let line = linea.map_err(|err| { + ParseError::Error(format!( + "Error al leer una línea del archivo '{}': {}", + ruta_archivo, err + )) + })?; + let registro: Vec<&str> = line.split(',').collect(); + + // Aplico restricciones (WHERE) + if self.aplicar_restricciones(®istro, &vector_encabezados)? { + // Crear un vector para las columnas seleccionadas + let mut columnas_select = Vec::new(); + + for col in &self.columnas { + // Encuentro índice de la columna en los encabezados + match vector_encabezados + .iter() + .position(|&encabezado| encabezado == col.as_str()) + { + Some(index) => { + // Obtener el valor de la columna, si existe + if let Some(value) = registro.get(index) { + columnas_select.push(value.to_string()); + } else { + // Si el índice está fuera de rango, meto una cadena vacía + columnas_select.push(String::new()); + } + } + None => { + // Si no se encuentra el índice, meto una cadena vacía + columnas_select.push(String::new()); + } + } + } + resultado.push(columnas_select); + } + } + Ok(resultado) + } + + fn aplicar_restricciones( + &self, + _registro: &[&str], + _encabezados: &[&str], + ) -> Result { + //LOgica de los (WHERE) + Ok(true) + } +} + +impl Select { + pub fn imprimir_resultados(results: &[Vec]) { + for row in results { + // Imprimir cada fila + let row_string = row.join(", "); + println!("{}", row_string); + } + } +} From eb6b1ab0fde77120274a4dd588085bfdacd1f2fe Mon Sep 17 00:00:00 2001 From: German Douce Date: Fri, 30 Aug 2024 02:07:26 -0300 Subject: [PATCH 11/54] agrego logica para ejecutar restricciones select --- output.csv | 21 ++++++-------- src/parser.rs | 18 ++++++++---- src/select.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 95 insertions(+), 24 deletions(-) diff --git a/output.csv b/output.csv index f24229f..b48b981 100644 --- a/output.csv +++ b/output.csv @@ -1,14 +1,11 @@ Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas -Consulta sql: SELECT producto FROM ordenes +Consulta sql: SELECT id, producto, id_cliente +FROM ordenes +WHERE cantidad > 1; + Ejecutar consulta en la tabla: "ordenes" -Columnas a seleccionar: ["producto"] -Laptop -Monitor -Teléfono -Teclado -Mouse -Impresora -Altavoces -Auriculares -Laptop -Teléfono +Columnas a seleccionar: ["id", "producto", "id_cliente"] +Restricciones: "cantidad > 1" +102, Teléfono, 2 +105, Mouse, 4 +110, Teléfono, 6 diff --git a/src/parser.rs b/src/parser.rs index b3c1191..0e909b0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -14,10 +14,13 @@ use crate::select::Select; // leo la consulta SQL y devuelve un comando específico. pub fn parsear_consulta(consulta: &str) -> Result { - let consulta = consulta.to_lowercase(); + //Si pongo toda la consulta enn minusucula, poerdo las mayuduclas de las tablas, columnas y APELLIDOCS + //INTENMTE CAMBIAR LA LOGICA PERO SE ME HIZO MUUUY COMPLICADO SOLO COMPARAR ESAS PALABRAS + //ASUMO Q LAS CALVES VIENEN EN MAYSUCULA YA QUE EN LOS EJEMPLOS ESTA ASI + // let consulta_lower = consulta.to_lowercase(); // Determino el tipo de comando y guardar el resto de la consulta - let (comando, resto) = if let Some(resto) = consulta.strip_prefix("select") { + let (comando, resto) = if let Some(resto) = consulta.strip_prefix("SELECT") { ("select", resto.trim()) } else if let Some(resto) = consulta.strip_prefix("insert into") { ("insert", resto.trim()) @@ -55,12 +58,15 @@ pub fn parsear_consulta(consulta: &str) -> Result { //praseo el select fn parse_select(resto: &str) -> Result { + + let resto = resto.trim_end_matches(';').trim(); + // Separo la consulta en la parte de las columnas y la parte de las restricciones (si existen) // resto = SELECT id, producto, id_cliente FROM ordenes WHERE cantidad > 1 ORDER BY email DESC; // columnas_y_tabla = "SELECT id, producto, id_cliente FROM ordenes" // restricciones = cantidad > 1 ORDER BY email DESC; let (columnas_y_tabla, restricciones) = - if let Some((partes, restricciones)) = resto.split_once("where") { + if let Some((partes, restricciones)) = resto.split_once("WHERE") { (partes.trim(), Some(restricciones.trim())) // elimina espacios en blanco x las dudas } else { (resto, None) // "SELECT id, producto, id_cliente FROM ordenes" (no habia where) @@ -71,8 +77,8 @@ fn parse_select(resto: &str) -> Result { // columnas = "id, producto, id_cliente" // tabla_y_ordenamiento = "ordenes" let (columnas, tabla_y_ordenamiento) = - if let Some((columnas, tabla)) = columnas_y_tabla.split_once("from") { - (columnas.trim_start_matches("select").trim(), tabla.trim()) //saco select + if let Some((columnas, tabla)) = columnas_y_tabla.split_once("FROM") { + (columnas.trim_start_matches("SELECT").trim(), tabla.trim()) //saco select } else { return Err(ParseError::ErrorDeSintaxis); }; @@ -90,7 +96,7 @@ fn parse_select(resto: &str) -> Result { // ordenamiento = email DESC let (restricciones, ordenamiento) = if let Some(restricciones) = restricciones { // Intento separar las restricciones y el ordenamiento usando "order by" - if let Some((restricciones, orden)) = restricciones.split_once("order by") { + if let Some((restricciones, orden)) = restricciones.split_once("ORDER BY") { ( Some(restricciones.trim().to_string()), Some(orden.trim().to_string()), diff --git a/src/select.rs b/src/select.rs index 7d01a3e..269e60e 100644 --- a/src/select.rs +++ b/src/select.rs @@ -89,14 +89,82 @@ impl Select { } Ok(resultado) } - - fn aplicar_restricciones( + pub fn aplicar_restricciones( &self, - _registro: &[&str], - _encabezados: &[&str], + registro: &[&str], + encabezados: &[&str], ) -> Result { - //LOgica de los (WHERE) - Ok(true) + // Verificamos si hay restricciones + if let Some(ref restricciones) = self.restricciones { + // Separo las restricciones por operadores lógicos `AND` + let condiciones: Vec<&str> = restricciones.split(" AND ").collect(); + let mut resultado = true; + + for condicion in &condiciones { + // hay algun `OR` + if condicion.contains(" OR ") { + let or_condiciones: Vec<&str> = condicion.split(" OR ").collect(); + let mut or_resultado = false; + + // Evaluo cada condición `OR` + for or_condicion in &or_condiciones { + if self.aplicar_condicion( or_condicion.trim(), registro, encabezados)? { + or_resultado = true; + break; // Si se cumple una condición `OR`, no es necesario verificar las demás + } + } + + resultado = resultado && or_resultado; + } else { + // Evalo la condición `AND` + if !self.aplicar_condicion(condicion.trim(), registro, encabezados)? { + return Ok(false); + } + } + } + + Ok(resultado) + } else { + // Si no hay restricciones + Ok(true) + } + } + + fn aplicar_condicion(&self, + condicion: &str, + registro: &[&str], + encabezados: &[&str], + ) -> Result { + // Identifico el operador en la condición + let (columna, operador, valor) = if let Some(pos) = condicion.find('=') { + (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) + } else if let Some(pos) = condicion.find('>') { + (&condicion[..pos].trim(), ">", &condicion[pos + 1..].trim()) + } else if let Some(pos) = condicion.find('<') { + (&condicion[..pos].trim(), "<", &condicion[pos + 1..].trim()) + } else { + return Err(ParseError::Error(format!("Error al hacer alguna operacion"))); + }; + + let valor = valor.trim().trim_matches('\''); + + // Encuentro el índice de la columna en los encabezados + if let Some(indice) = encabezados.iter().position(|&enc| enc == *columna) { + // Obtengo el valor del registro correspondiente a la columna + let valor_registro = registro[indice]; + + // Comparo el valor en el registro con el valor del operador + let resultado = match operador { + "=" => valor_registro == valor, + ">" => valor_registro > valor, + "<" => valor_registro < valor, + _ => false, + }; + + return Ok(resultado); + } + + Ok(false) } } From 60303a8151c23960ea5cc53f2e43f062be121a79 Mon Sep 17 00:00:00 2001 From: German Douce Date: Fri, 30 Aug 2024 19:16:17 -0300 Subject: [PATCH 12/54] agrego encabezados y ordenamiento --- output.csv | 26 +++++++++++------- src/select.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 10 deletions(-) diff --git a/output.csv b/output.csv index b48b981..b030739 100644 --- a/output.csv +++ b/output.csv @@ -1,11 +1,17 @@ Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas -Consulta sql: SELECT id, producto, id_cliente -FROM ordenes -WHERE cantidad > 1; - -Ejecutar consulta en la tabla: "ordenes" -Columnas a seleccionar: ["id", "producto", "id_cliente"] -Restricciones: "cantidad > 1" -102, Teléfono, 2 -105, Mouse, 4 -110, Teléfono, 6 +Consulta sql: SELECT id, nombre, email +FROM clientes +WHERE apellido = 'López' ORDER BY email DESC; +Ejecutar consulta en la tabla: "clientes" +Columnas a seleccionar: ["id", "nombre", "email"] +Restricciones: "apellido = 'López'" +Order by: "email DESC" +Encabezados antes de agregar: ["id", "nombre", "apellido", "email"] +Encabezados select: ["id", "nombre", "email"] +Estado inicial: +Ordenamiento: email DESC +Encabezados select: ["id", "nombre", "email"] +Resultado antes del ordenamiento: [["id", "nombre", "email"], ["2", "Ana", "ana.lopez@email.com"], ["5", "José", "jose.lopez@email.com"]] +id, nombre, email +5, José, jose.lopez@email.com +2, Ana, ana.lopez@email.com diff --git a/src/select.rs b/src/select.rs index 269e60e..a4b07f7 100644 --- a/src/select.rs +++ b/src/select.rs @@ -48,6 +48,13 @@ impl Select { let mut resultado = Vec::new(); + println!("Encabezados antes de agregar: {:?}", vector_encabezados); + + let mut encabezados_select = Vec::new(); + self.agregar_encabezados(&vector_encabezados, &mut resultado, &mut encabezados_select); + + println!("Encabezados select: {:?}", encabezados_select); + // leo linea a linea for linea in reader.lines() { let line = linea.map_err(|err| { @@ -87,8 +94,77 @@ impl Select { resultado.push(columnas_select); } } + //Ordenamiento (ORDER BY) + if let Some(ref ordenamiento) = self.ordenamiento { + self.aplicar_ordenamiento(& mut resultado, ordenamiento, &encabezados_select)?; + }; Ok(resultado) } + + fn aplicar_ordenamiento( + &self, + resultado: &mut Vec>, + ordenamiento: &str, + encabezados_select: &[String], + ) -> Result<(), ParseError> { + println!("Estado inicial:"); + println!("Ordenamiento: {}", ordenamiento); + println!("Encabezados select: {:?}", encabezados_select); + println!("Resultado antes del ordenamiento: {:?}", resultado); + + // Encuentro el índice de la columna para el ordenamiento + let (columna, direccion) = if let Some((col, dir)) = ordenamiento.split_once(' ') { + (col.trim(), dir.trim().to_uppercase()) + } else { + (ordenamiento.trim(), "ASC".to_string()) + }; + + // Encuentro el índice en encabezados_select, si existe + if let Some(index) = encabezados_select.iter().position(|encabezado| encabezado == columna) { + // Ordeno las filas menos la primera fila que es la que tiene los encabezaods + resultado[1..].sort_by(|a, b| { + // Obtiene los valores para la columna especificada, o cadena vacía si no está presente + let a_value = a.get(index).map_or("", |v| v.as_str()); + let b_value = b.get(index).map_or("", |v| v.as_str()); + + //comparo + let ord = a_value.cmp(b_value); + if direccion == "DESC" { + ord.reverse() + } else { + ord + } + }); + Ok(()) + } else { + Err(ParseError::Error(format!("La columna '{}' no existe en los encabezados", columna))) + } + } + + fn agregar_encabezados( + &self, + vector_encabezados: &[&str], + resultado: &mut Vec>, + encabezados_select: &mut Vec, + ) { + // Genera los encabezados seleccionados + for col in &self.columnas { + if vector_encabezados.iter().any(|&encabezado| encabezado == col.as_str()) { + encabezados_select.push(col.to_string()); + } else { + encabezados_select.push(String::new()); + } + } + //nuevo vector para los encabezados y los agrego al resultado + let encabezados_nuevos = encabezados_select.to_vec(); + + if resultado.is_empty() { + resultado.push(encabezados_nuevos); + } else { + resultado[0] = encabezados_nuevos; + } + } + pub fn aplicar_restricciones( &self, registro: &[&str], From 027896374b55386e73be17e2379d4518926ce53d Mon Sep 17 00:00:00 2001 From: German Douce Date: Sat, 31 Aug 2024 17:52:14 -0300 Subject: [PATCH 13/54] parseo el update y mando un cargo fmt --- output.csv | 22 +++++-------- src/comandos.rs | 5 ++- src/main.rs | 1 + src/parser.rs | 82 ++++++++++++++++++++++++++++++++++++++++++------- src/select.rs | 26 +++++++++++----- src/update.rs | 20 ++++++++++++ 6 files changed, 122 insertions(+), 34 deletions(-) create mode 100644 src/update.rs diff --git a/output.csv b/output.csv index b030739..3eaef48 100644 --- a/output.csv +++ b/output.csv @@ -1,17 +1,9 @@ +bbbbbbbbbbb: Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas -Consulta sql: SELECT id, nombre, email -FROM clientes -WHERE apellido = 'López' ORDER BY email DESC; +Consulta sql: UPDATE clientes +SET email='mrodriguez@hotmail.com',nombre ='pepe',lalala =1 WHERE id=1; + Ejecutar consulta en la tabla: "clientes" -Columnas a seleccionar: ["id", "nombre", "email"] -Restricciones: "apellido = 'López'" -Order by: "email DESC" -Encabezados antes de agregar: ["id", "nombre", "apellido", "email"] -Encabezados select: ["id", "nombre", "email"] -Estado inicial: -Ordenamiento: email DESC -Encabezados select: ["id", "nombre", "email"] -Resultado antes del ordenamiento: [["id", "nombre", "email"], ["2", "Ana", "ana.lopez@email.com"], ["5", "José", "jose.lopez@email.com"]] -id, nombre, email -5, José, jose.lopez@email.com -2, Ana, ana.lopez@email.com +Columnas a actualizar: ["email", "nombre", "lalala"] +Valores a asignar: ["'mrodriguez@hotmail.com'", "'pepe'", "1"] +Restricciones: "id=1" diff --git a/src/comandos.rs b/src/comandos.rs index 40ae3d0..aa11368 100644 --- a/src/comandos.rs +++ b/src/comandos.rs @@ -1,10 +1,11 @@ use crate::errores::ParseError; use crate::select::Select; +use crate::update::Update; pub enum Comando { Select(Select), // Insert(Insert), - // Update(Update), + Update(Update), // Delete(Delete), } @@ -12,11 +13,13 @@ impl Comando { pub fn ejecutar(&self, ruta_carpeta: &str) -> Result>, ParseError> { match self { Comando::Select(select) => select.ejecutar(ruta_carpeta), + Comando::Update(update) => update.ejecutar(ruta_carpeta), } } pub fn imprimir_resultados(&self, results: &[Vec]) { match self { Comando::Select(_select) => Select::imprimir_resultados(results), + Comando::Update(update) => Select::imprimir_resultados(results), } } } diff --git a/src/main.rs b/src/main.rs index 4245ce2..c546c24 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod comandos; mod errores; mod parser; mod select; +mod update; use parser::parsear_consulta; use std::env; diff --git a/src/parser.rs b/src/parser.rs index 0e909b0..57dc570 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,6 +2,7 @@ use crate::comandos::Comando; use crate::errores::ParseError; use crate::select::Select; +use crate::update::Update; // pub struct Insert { // } @@ -24,7 +25,7 @@ pub fn parsear_consulta(consulta: &str) -> Result { ("select", resto.trim()) } else if let Some(resto) = consulta.strip_prefix("insert into") { ("insert", resto.trim()) - } else if let Some(resto) = consulta.strip_prefix("update") { + } else if let Some(resto) = consulta.strip_prefix("UPDATE") { ("update", resto.trim()) } else if let Some(resto) = consulta.strip_prefix("delete from") { ("delete", resto.trim()) @@ -42,23 +43,22 @@ pub fn parsear_consulta(consulta: &str) -> Result { // let insert = parse_insert(resto)?; // Comando::Insert(insert) // }, - // "update" => { - // let update = parse_update(resto)?; - // Comando::Update(update) - // }, + "update" => { + let update = parse_update(resto)?; + println!("bbbbbbbbbbb:"); + Comando::Update(update) + } // "delete" => { // let delete = parse_delete(resto)?; // Comando::Delete(delete) // }, _ => return Err(ParseError::ComandoNoReconocido), }; - Ok(comando) // Devolvemos el comando directamente, no una tupla } //praseo el select fn parse_select(resto: &str) -> Result { - let resto = resto.trim_end_matches(';').trim(); // Separo la consulta en la parte de las columnas y la parte de las restricciones (si existen) @@ -119,15 +119,75 @@ fn parse_select(resto: &str) -> Result { }) } +fn parse_update(resto: &str) -> Result { + // saco ; y saltos de linea + let resto = resto.trim_end_matches(';').trim(); + let resto = resto + .replace("\n", " ") + .replace("\r", " ") + .trim() + .to_string(); + + // Separo SET y WHERE + let partes: Vec<&str> = resto.splitn(2, " SET ").collect(); + if partes.len() != 2 { + return Err(ParseError::ErrorDeSintaxis); + } + + //tabla + let table_part = partes[0].trim(); + let tabla = table_part.to_string(); + + let set_where_partes: Vec<&str> = partes[1].splitn(2, " WHERE ").collect(); + let parte_set = set_where_partes[0].trim(); + + //columnas a modif y valores + let mut columnas = Vec::new(); + let mut valores = Vec::new(); + + for asignacion in parte_set.split(',') { + let mut parts = asignacion.splitn(2, '='); + + let columna = parts + .next() + .ok_or(ParseError::ErrorDeSintaxis)? + .trim() + .to_string(); + + let valor = parts + .next() + .ok_or(ParseError::ErrorDeSintaxis)? + .trim() + .to_string(); + + // Verificar si la columna o valor están vacíos + if columna.is_empty() || valor.is_empty() { + return Err(ParseError::ErrorDeSintaxis); + } + + columnas.push(columna); + valores.push(valor); + } + + // Parseo restricciones, si hay + let restricciones = if set_where_partes.len() == 2 { + Some(set_where_partes[1].trim().to_string()) + } else { + None + }; + + Ok(Update { + columnas, + tabla, + valores, + restricciones, + }) +} // fn parse_insert(resto: &str) -> Result { // Ok(Insert { // }) // } // -// fn parse_update(resto: &str) -> Result { -// Ok(Update { -// }) -// } // // fn parse_delete(resto: &str) -> Result { // Ok(Delete { diff --git a/src/select.rs b/src/select.rs index a4b07f7..4f24379 100644 --- a/src/select.rs +++ b/src/select.rs @@ -96,7 +96,7 @@ impl Select { } //Ordenamiento (ORDER BY) if let Some(ref ordenamiento) = self.ordenamiento { - self.aplicar_ordenamiento(& mut resultado, ordenamiento, &encabezados_select)?; + self.aplicar_ordenamiento(&mut resultado, ordenamiento, &encabezados_select)?; }; Ok(resultado) } @@ -120,7 +120,10 @@ impl Select { }; // Encuentro el índice en encabezados_select, si existe - if let Some(index) = encabezados_select.iter().position(|encabezado| encabezado == columna) { + if let Some(index) = encabezados_select + .iter() + .position(|encabezado| encabezado == columna) + { // Ordeno las filas menos la primera fila que es la que tiene los encabezaods resultado[1..].sort_by(|a, b| { // Obtiene los valores para la columna especificada, o cadena vacía si no está presente @@ -137,7 +140,10 @@ impl Select { }); Ok(()) } else { - Err(ParseError::Error(format!("La columna '{}' no existe en los encabezados", columna))) + Err(ParseError::Error(format!( + "La columna '{}' no existe en los encabezados", + columna + ))) } } @@ -149,7 +155,10 @@ impl Select { ) { // Genera los encabezados seleccionados for col in &self.columnas { - if vector_encabezados.iter().any(|&encabezado| encabezado == col.as_str()) { + if vector_encabezados + .iter() + .any(|&encabezado| encabezado == col.as_str()) + { encabezados_select.push(col.to_string()); } else { encabezados_select.push(String::new()); @@ -184,7 +193,7 @@ impl Select { // Evaluo cada condición `OR` for or_condicion in &or_condiciones { - if self.aplicar_condicion( or_condicion.trim(), registro, encabezados)? { + if self.aplicar_condicion(or_condicion.trim(), registro, encabezados)? { or_resultado = true; break; // Si se cumple una condición `OR`, no es necesario verificar las demás } @@ -206,7 +215,8 @@ impl Select { } } - fn aplicar_condicion(&self, + fn aplicar_condicion( + &self, condicion: &str, registro: &[&str], encabezados: &[&str], @@ -219,7 +229,9 @@ impl Select { } else if let Some(pos) = condicion.find('<') { (&condicion[..pos].trim(), "<", &condicion[pos + 1..].trim()) } else { - return Err(ParseError::Error(format!("Error al hacer alguna operacion"))); + return Err(ParseError::Error(format!( + "Error al hacer alguna operacion" + ))); }; let valor = valor.trim().trim_matches('\''); diff --git a/src/update.rs b/src/update.rs new file mode 100644 index 0000000..8217730 --- /dev/null +++ b/src/update.rs @@ -0,0 +1,20 @@ +use crate::errores::ParseError; +pub struct Update { + pub columnas: Vec, + pub tabla: String, + pub valores: Vec, + pub restricciones: Option, +} + +impl Update { + pub fn ejecutar(&self, _ruta_carpeta_tablas: &str) -> Result>, ParseError> { + println!("Ejecutar consulta en la tabla: {:?}", self.tabla); + println!("Columnas a actualizar: {:?}", self.columnas); + println!("Valores a asignar: {:?}", self.valores); + + if let Some(ref restricciones) = self.restricciones { + println!("Restricciones: {:?}", restricciones); + } + Ok(vec![]) + } +} From 3b926b9ad31015a8963184535006aace5048237b Mon Sep 17 00:00:00 2001 From: German Douce Date: Sat, 31 Aug 2024 17:53:39 -0300 Subject: [PATCH 14/54] corrijo un detalle del clippy --- src/comandos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/comandos.rs b/src/comandos.rs index aa11368..ff18fec 100644 --- a/src/comandos.rs +++ b/src/comandos.rs @@ -19,7 +19,7 @@ impl Comando { pub fn imprimir_resultados(&self, results: &[Vec]) { match self { Comando::Select(_select) => Select::imprimir_resultados(results), - Comando::Update(update) => Select::imprimir_resultados(results), + Comando::Update(_update) => Select::imprimir_resultados(results), } } } From 0cae027fb65a12365ff4c0ac04514265f75489ae Mon Sep 17 00:00:00 2001 From: German Douce Date: Sat, 31 Aug 2024 21:19:43 -0300 Subject: [PATCH 15/54] agrego funcion ejecutar a update. Faltan restricciones. Tengo q ver como hago xa q solo se printee por STDOUT el select y no los demas. xc ahora queda asi... +facilito manejo de errores --- output.csv | 14 ++++-- src/errores.rs | 9 ++++ src/update.rs | 109 ++++++++++++++++++++++++++++++++++++++++++-- tablas/clientes.csv | 12 ++--- 4 files changed, 130 insertions(+), 14 deletions(-) diff --git a/output.csv b/output.csv index 3eaef48..2786ec6 100644 --- a/output.csv +++ b/output.csv @@ -1,9 +1,13 @@ bbbbbbbbbbb: Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas Consulta sql: UPDATE clientes -SET email='mrodriguez@hotmail.com',nombre ='pepe',lalala =1 WHERE id=1; - +SET email = 'mrodriguez@hotmail.com', nombre ='jacinto', apellido='perez'; Ejecutar consulta en la tabla: "clientes" -Columnas a actualizar: ["email", "nombre", "lalala"] -Valores a asignar: ["'mrodriguez@hotmail.com'", "'pepe'", "1"] -Restricciones: "id=1" +Columnas a actualizar: ["email", "nombre", "apellido"] +Valores a asignar: ["'mrodriguez@hotmail.com'", "'jacinto'", "'perez'"] +1, 'jacinto', 'perez', 'mrodriguez@hotmail.com' +2, 'jacinto', 'perez', 'mrodriguez@hotmail.com' +3, 'jacinto', 'perez', 'mrodriguez@hotmail.com' +4, 'jacinto', 'perez', 'mrodriguez@hotmail.com' +5, 'jacinto', 'perez', 'mrodriguez@hotmail.com' +6, 'jacinto', 'perez', 'mrodriguez@hotmail.com' diff --git a/src/errores.rs b/src/errores.rs index a2f1bab..3f0616a 100644 --- a/src/errores.rs +++ b/src/errores.rs @@ -4,3 +4,12 @@ pub enum ParseError { ErrorDeSintaxis, Error(String), } +impl ParseError { + pub fn to_string(&self) -> &str { + match self { + ParseError::ComandoNoReconocido => "Comando no reconocido", + ParseError::ErrorDeSintaxis => "Error de sintaxis", + ParseError::Error(msg) => &msg, + } + } +} \ No newline at end of file diff --git a/src/update.rs b/src/update.rs index 8217730..26407f2 100644 --- a/src/update.rs +++ b/src/update.rs @@ -1,4 +1,8 @@ use crate::errores::ParseError; +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; +use std::path::Path; + pub struct Update { pub columnas: Vec, pub tabla: String, @@ -7,7 +11,7 @@ pub struct Update { } impl Update { - pub fn ejecutar(&self, _ruta_carpeta_tablas: &str) -> Result>, ParseError> { + pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, ParseError> { println!("Ejecutar consulta en la tabla: {:?}", self.tabla); println!("Columnas a actualizar: {:?}", self.columnas); println!("Valores a asignar: {:?}", self.valores); @@ -15,6 +19,105 @@ impl Update { if let Some(ref restricciones) = self.restricciones { println!("Restricciones: {:?}", restricciones); } - Ok(vec![]) + + let ruta_archivo = Path::new(ruta_carpeta_tablas).join(&self.tabla).with_extension("csv"); + let ruta_archivo_temporal = ruta_archivo.with_extension("tmp"); + + // Abro archivo tabla + let archivo_entrada = match File::open(&ruta_archivo) { + Ok(file) => file, + Err(err) => { + println!("Error al abrir el archivo de entrada: {}", ParseError::Error(err.to_string()).to_string()); + return Err(ParseError::Error(err.to_string())); + } + }; + + let reader = BufReader::new(archivo_entrada); + + // Creo un archivo temporal para ir escribiendo los resultados actualizados + let mut archivo_temporal = match File::create(&ruta_archivo_temporal) { + Ok(file) => file, + Err(err) => { + println!("Error al crear el archivo temporal: {}", ParseError::Error(err.to_string()).to_string()); + return Err(ParseError::Error(err.to_string())); + } + }; + + // encabezadosss + let mut lineas = reader.lines(); + let encabezados = match lineas.next() { + Some(Ok(line)) => line, + Some(Err(err)) => { + println!("Error al leer la primera línea del archivo: {}", ParseError::Error(err.to_string()).to_string()); + return Err(ParseError::Error(err.to_string())); + } + None => { + println!("{}", ParseError::Error("Archivo vacío".to_string()).to_string()); + return Err(ParseError::Error("Archivo vacío".to_string())); + } + }; + + let encabezados: Vec = encabezados.split(',') + .map(|s| s.trim().to_string()) + .collect(); + + // Escribo encabezados al archivo de salida + if let Err(err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { + println!("Error al escribir encabezados en el archivo temporal: {}", ParseError::Error(err.to_string()).to_string()); + return Err(ParseError::Error(err.to_string())); + } + + let mut filas_actualizadas = Vec::new(); + + // leo linea a linea el archivo de entrada + for linea in lineas { + let mut registro: Vec = match linea { + Ok(line) => line.split(',') + .map(|s| s.trim().to_string()) + .collect(), + Err(err) => { + println!("Error al leer una línea del archivo: {}", ParseError::Error(err.to_string()).to_string()); + continue; + } + }; + + // Aplico restricciones + if let Some(ref restricciones) = self.restricciones { + if let Err(err) = self.aplicar_restricciones(®istro, &encabezados, restricciones) { + println!("Error al aplicar restricciones: {}", err.to_string()); + continue; + } + } + + // Actualizo valores directamente en `registro` + for (columna, valor) in self.columnas.iter().zip(&self.valores) { + if let Some(indice) = encabezados.iter().position(|h| h == columna) { + if indice < registro.len() { + registro[indice] = valor.to_string(); + } + } + } + + // Escribo el registro actualizada al archivo temporal + if let Err(err) = writeln!(archivo_temporal, "{}", registro.join(",")) { + println!("Error al escribir el registro actualizada en el archivo temporal: {}", ParseError::Error(err.to_string()).to_string()); + return Err(ParseError::Error(err.to_string())); + } + + filas_actualizadas.push(registro); + } + + // Reemplazo el archivo original con el archivo temporal + match std::fs::rename(&ruta_archivo_temporal, &ruta_archivo) { + Ok(_) => Ok(filas_actualizadas), + Err(err) => { + println!("Error al reemplazar el archivo original: {}", ParseError::Error(err.to_string()).to_string()); + Err(ParseError::Error(err.to_string())) + } + } } -} + fn aplicar_restricciones(&self, _fila: &[String], _encabezados: &[String], _restricciones: &str) -> Result { + //restricciones + Ok(true) + } +} \ No newline at end of file diff --git a/tablas/clientes.csv b/tablas/clientes.csv index 1ccbf63..fd0b5ec 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -1,7 +1,7 @@ id,nombre,apellido,email -1,Juan,Pérez,juan.perez@email.com -2,Ana,López,ana.lopez@email.com -3,Carlos,Gómez,carlos.gomez@email.com -4,María,Rodríguez,maria.rodriguez@email.com -5,José,López,jose.lopez@email.com -6,Laura,Fernández,laura.fernandez@email.com \ No newline at end of file +1,'jacinto','perez','mrodriguez@hotmail.com' +2,'jacinto','perez','mrodriguez@hotmail.com' +3,'jacinto','perez','mrodriguez@hotmail.com' +4,'jacinto','perez','mrodriguez@hotmail.com' +5,'jacinto','perez','mrodriguez@hotmail.com' +6,'jacinto','perez','mrodriguez@hotmail.com' From ca2c8b3495c22dbe10d8c0f18ffdc80608f93e5b Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 1 Sep 2024 01:25:26 -0300 Subject: [PATCH 16/54] mi dios no juega dados quizas esta a mi favor (agrego conmdiciones del where al update y corrijo el parseo para que los strings se guarden sin comillas) --- output.csv | 21 ++++--- src/parser.rs | 3 + src/update.rs | 120 +++++++++++++++++++++++++++++++++---- tablas/clientes.csv | 12 ++-- tablas/clientes_limpio.csv | 7 +++ 5 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 tablas/clientes_limpio.csv diff --git a/output.csv b/output.csv index 2786ec6..90ae1d9 100644 --- a/output.csv +++ b/output.csv @@ -1,13 +1,16 @@ bbbbbbbbbbb: Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas Consulta sql: UPDATE clientes -SET email = 'mrodriguez@hotmail.com', nombre ='jacinto', apellido='perez'; +SET email = 'mrodriguez@hotmail.com' +WHERE id = 4; + Ejecutar consulta en la tabla: "clientes" -Columnas a actualizar: ["email", "nombre", "apellido"] -Valores a asignar: ["'mrodriguez@hotmail.com'", "'jacinto'", "'perez'"] -1, 'jacinto', 'perez', 'mrodriguez@hotmail.com' -2, 'jacinto', 'perez', 'mrodriguez@hotmail.com' -3, 'jacinto', 'perez', 'mrodriguez@hotmail.com' -4, 'jacinto', 'perez', 'mrodriguez@hotmail.com' -5, 'jacinto', 'perez', 'mrodriguez@hotmail.com' -6, 'jacinto', 'perez', 'mrodriguez@hotmail.com' +Columnas a actualizar: ["email"] +Valores a asignar: ["mrodriguez@hotmail.com"] +Restricciones: "id = 4" +1, Juan, Pérez, juan.perez@email.com +2, Ana, López, ana.lopez@email.com +3, Carlos, Gómez, carlos.gomez@email.com +4, María, Rodríguez, mrodriguez@hotmail.com +5, José, López, jose.lopez@email.com +6, Laura, Fernández, laura.fernandez@email.com diff --git a/src/parser.rs b/src/parser.rs index 57dc570..92f448c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -160,6 +160,9 @@ fn parse_update(resto: &str) -> Result { .trim() .to_string(); + // Elimino comillas simples alrededor del valorrr + let valor = valor.trim_matches('\'').to_string(); + // Verificar si la columna o valor están vacíos if columna.is_empty() || valor.is_empty() { return Err(ParseError::ErrorDeSintaxis); diff --git a/src/update.rs b/src/update.rs index 26407f2..863a093 100644 --- a/src/update.rs +++ b/src/update.rs @@ -82,18 +82,25 @@ impl Update { }; // Aplico restricciones - if let Some(ref restricciones) = self.restricciones { - if let Err(err) = self.aplicar_restricciones(®istro, &encabezados, restricciones) { - println!("Error al aplicar restricciones: {}", err.to_string()); - continue; + let registro_valido = if let Some(ref restricciones) = self.restricciones { + match self.aplicar_restricciones(®istro, &encabezados) { + Ok(valido) => valido, + Err(err) => { + println!("Error al aplicar restricciones: {}", err.to_string()); + continue; // Salto al siguiente registro en caso de error en restricciones + } } - } + } else { + true // Si no hay restricciones, el registro es válido + }; - // Actualizo valores directamente en `registro` - for (columna, valor) in self.columnas.iter().zip(&self.valores) { - if let Some(indice) = encabezados.iter().position(|h| h == columna) { - if indice < registro.len() { - registro[indice] = valor.to_string(); + if registro_valido { + // Modifico el registro que cumple con las restricciones + for (columna, valor) in self.columnas.iter().zip(&self.valores) { + if let Some(indice) = encabezados.iter().position(|h| h == columna) { + if indice < registro.len() { + registro[indice] = valor.to_string(); + } } } } @@ -116,8 +123,95 @@ impl Update { } } } - fn aplicar_restricciones(&self, _fila: &[String], _encabezados: &[String], _restricciones: &str) -> Result { - //restricciones - Ok(true) + pub fn aplicar_restricciones( + &self, + registro: &[String], + encabezados: &[String], + ) -> Result { + if let Some(ref restricciones) = self.restricciones { + // Reemplazo de operadores lógicos por simbolos + let restricciones = restricciones + .replace(" AND NOT ", " && !") + .replace(" AND ", " && ") + .replace(" OR ", " || "); + + let mut resultado = true; // Empezamos asumiendo que se cumplen todas las condiciones + + // Separamos las restricciones en condiciones individuales + let condiciones = restricciones.split("&&").flat_map(|s| s.split("||")); + + for condicion in condiciones { + let condicion = condicion.trim(); + let es_negado = condicion.starts_with('!'); + let condicion_limpia = if es_negado { + &condicion[1..].trim() + } else { + condicion + }; + + let cumple_condicion = self.aplicar_condicion(condicion_limpia, registro, encabezados)?; + + // Si es `AND NOT`, se niega el resultado de la condición + let cumple_condicion = if es_negado { !cumple_condicion } else { cumple_condicion }; + + // Si encontramos una condición `OR` que es verdadera, podemos dejar de evaluar + if condicion.contains("||") { + if cumple_condicion { + return Ok(true); + } + } else { + resultado = resultado && cumple_condicion; + // Si alguna condición `AND` es falsa, podemos dejar de evaluar + if !resultado { + return Ok(false); + } + } + } + + Ok(resultado) + } else { + Ok(true) // Sin restricciones, el registro siempre es válido + } } + + fn aplicar_condicion( + &self, + condicion: &str, + registro: &[String], + encabezados: &[String], + ) -> Result { + let (columna, operador, valor) = if let Some(pos) = condicion.find('=') { + (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) + } else if let Some(pos) = condicion.find('>') { + (&condicion[..pos].trim(), ">", &condicion[pos + 1..].trim()) + } else if let Some(pos) = condicion.find('<') { + (&condicion[..pos].trim(), "<", &condicion[pos + 1..].trim()) + } else { + return Err(ParseError::Error("Operador no válido en la condición".to_string())); + }; + + let valor = valor.trim().trim_matches('\'').to_string(); + // Encuentro el índice de la columna en los encabezados + match encabezados.iter().position(|enc| enc == columna) { + Some(indice) => { + if let Some(valor_registro) = registro.get(indice) { + // println!("operador: {:}",operador); + let resultado = match operador { + "=" => valor_registro == &valor, + ">" => valor_registro > &valor, + "<" => valor_registro < &valor, + _ => false, + }; + Ok(resultado) + } else { + Err(ParseError::Error("No se encontró el valor en el registro".to_string())) + } + } + None => Err(ParseError::Error(format!( + "Columna '{}' no encontrada en los encabezados", + columna + ))), + } + } + } \ No newline at end of file diff --git a/tablas/clientes.csv b/tablas/clientes.csv index fd0b5ec..d2f4336 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -1,7 +1,7 @@ id,nombre,apellido,email -1,'jacinto','perez','mrodriguez@hotmail.com' -2,'jacinto','perez','mrodriguez@hotmail.com' -3,'jacinto','perez','mrodriguez@hotmail.com' -4,'jacinto','perez','mrodriguez@hotmail.com' -5,'jacinto','perez','mrodriguez@hotmail.com' -6,'jacinto','perez','mrodriguez@hotmail.com' +1,Juan,Pérez,juan.perez@email.com +2,Ana,López,ana.lopez@email.com +3,Carlos,Gómez,carlos.gomez@email.com +4,María,Rodríguez,mrodriguez@hotmail.com +5,José,López,jose.lopez@email.com +6,Laura,Fernández,laura.fernandez@email.com diff --git a/tablas/clientes_limpio.csv b/tablas/clientes_limpio.csv new file mode 100644 index 0000000..85310a6 --- /dev/null +++ b/tablas/clientes_limpio.csv @@ -0,0 +1,7 @@ +id,nombre,apellido,email +1,Juan,Pérez,juan.perez@email.com +2,Ana,López,ana.lopez@email.com +3,Carlos,Gómez,carlos.gomez@email.com +4,María,Rodríguez,maria.rodriguez@email.com +5,José,López,jose.lopez@email.com +6,Laura,Fernández,laura.fernandez@email.com From c9d6c91eeca436674933adb5436ad8b0d03898ca Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 1 Sep 2024 20:53:07 -0300 Subject: [PATCH 17/54] parseo insert into --- output.csv | 22 +++++------- src/comandos.rs | 6 +++- src/insert.rs | 25 ++++++++++++++ src/main.rs | 1 + src/parser.rs | 92 ++++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 118 insertions(+), 28 deletions(-) create mode 100644 src/insert.rs diff --git a/output.csv b/output.csv index 90ae1d9..1d74c20 100644 --- a/output.csv +++ b/output.csv @@ -1,16 +1,10 @@ -bbbbbbbbbbb: +ordenes (id, id_cliente, producto, cantidad) +VALUES (111, 6 6, 'Laptop', 3); Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas -Consulta sql: UPDATE clientes -SET email = 'mrodriguez@hotmail.com' -WHERE id = 4; +Consulta sql: INSERT INTO ordenes (id, id_cliente, producto, cantidad) +VALUES (111, 6 6, 'Laptop', 3); -Ejecutar consulta en la tabla: "clientes" -Columnas a actualizar: ["email"] -Valores a asignar: ["mrodriguez@hotmail.com"] -Restricciones: "id = 4" -1, Juan, Pérez, juan.perez@email.com -2, Ana, López, ana.lopez@email.com -3, Carlos, Gómez, carlos.gomez@email.com -4, María, Rodríguez, mrodriguez@hotmail.com -5, José, López, jose.lopez@email.com -6, Laura, Fernández, laura.fernandez@email.com +Ejecutar consulta en la tabla: "ordenes" +Columnas a insertar: ["id", "id_cliente", "producto", "cantidad"] +Valores a insertar: [["111", "6 6", "Laptop", "3"]] +111, 6 6, Laptop, 3 diff --git a/src/comandos.rs b/src/comandos.rs index ff18fec..2d238ba 100644 --- a/src/comandos.rs +++ b/src/comandos.rs @@ -1,10 +1,11 @@ use crate::errores::ParseError; use crate::select::Select; use crate::update::Update; +use crate::insert::Insert; pub enum Comando { Select(Select), - // Insert(Insert), + Insert(Insert), Update(Update), // Delete(Delete), } @@ -14,12 +15,15 @@ impl Comando { match self { Comando::Select(select) => select.ejecutar(ruta_carpeta), Comando::Update(update) => update.ejecutar(ruta_carpeta), + Comando::Insert(insert) => insert.ejecutar(ruta_carpeta), } } pub fn imprimir_resultados(&self, results: &[Vec]) { match self { Comando::Select(_select) => Select::imprimir_resultados(results), Comando::Update(_update) => Select::imprimir_resultados(results), + Comando::Insert(insert) => Select::imprimir_resultados(results), + } } } diff --git a/src/insert.rs b/src/insert.rs new file mode 100644 index 0000000..821a785 --- /dev/null +++ b/src/insert.rs @@ -0,0 +1,25 @@ +use crate::errores::ParseError; + +pub struct Insert { + pub tabla: String, + pub columnas: Vec, + pub valores: Vec>, +} + +impl Insert { + pub fn ejecutar(&self, _ruta_carpeta_tablas: &str) -> Result>, ParseError> { + println!("Ejecutar consulta en la tabla: {:?}", self.tabla); + println!("Columnas a insertar: {:?}", self.columnas); + println!("Valores a insertar: {:?}", self.valores); + let mut valores_nuevos = Vec::with_capacity(self.valores.len()); + for fila in &self.valores { + let mut fila_nueva = Vec::with_capacity(fila.len()); + for valor in fila { + fila_nueva.push(valor.to_string()); + } + valores_nuevos.push(fila_nueva); + } + + Ok(valores_nuevos) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c546c24..8dac29c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod errores; mod parser; mod select; mod update; +mod insert; use parser::parsear_consulta; use std::env; diff --git a/src/parser.rs b/src/parser.rs index 92f448c..c72e8d0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,10 +3,8 @@ use crate::comandos::Comando; use crate::errores::ParseError; use crate::select::Select; use crate::update::Update; +use crate::insert::Insert; -// pub struct Insert { -// } -// // pub struct Update { // } // @@ -23,7 +21,7 @@ pub fn parsear_consulta(consulta: &str) -> Result { // Determino el tipo de comando y guardar el resto de la consulta let (comando, resto) = if let Some(resto) = consulta.strip_prefix("SELECT") { ("select", resto.trim()) - } else if let Some(resto) = consulta.strip_prefix("insert into") { + } else if let Some(resto) = consulta.strip_prefix("INSERT INTO") { ("insert", resto.trim()) } else if let Some(resto) = consulta.strip_prefix("UPDATE") { ("update", resto.trim()) @@ -39,10 +37,10 @@ pub fn parsear_consulta(consulta: &str) -> Result { let select = parse_select(resto)?; Comando::Select(select) } - // "insert" => { - // let insert = parse_insert(resto)?; - // Comando::Insert(insert) - // }, + "insert" => { + let insert = parse_insert(resto)?; + Comando::Insert(insert) + }, "update" => { let update = parse_update(resto)?; println!("bbbbbbbbbbb:"); @@ -186,11 +184,79 @@ fn parse_update(resto: &str) -> Result { restricciones, }) } -// fn parse_insert(resto: &str) -> Result { -// Ok(Insert { -// }) -// } -// +fn parse_insert(resto: &str) -> Result { + let resto = resto.trim(); + + let pos_values = match resto.find("VALUES") { + Some(pos) => pos, + None => { + let err_msg = "Falta la palabra clave 'VALUES'"; + println!("Error: {}", err_msg); + return Err(ParseError::Error(err_msg.to_string())); + } + }; + + let tabla_y_columnas = &resto[..pos_values].trim(); + + let paos_parentesis_abre = match tabla_y_columnas.find('(') { + Some(pos) => pos, + None => { + let err_msg = "Falta el '(' después del nombre de la tabla"; + println!("Error: {}", err_msg); + return Err(ParseError::Error(err_msg.to_string())); + } + }; + + let pos_parentesis_cierra = match tabla_y_columnas.find(')') { + Some(pos) => pos, + None => { + let err_msg = "Falta el ')' después de la lista de columnas"; + println!("Error: {}", err_msg); + return Err(ParseError::Error(err_msg.to_string())); + } + }; + + let tabla = tabla_y_columnas[..paos_parentesis_abre].trim(); + let columnas_str = &tabla_y_columnas[paos_parentesis_abre + 1..pos_parentesis_cierra].trim(); + + let columnas: Vec = columnas_str.split(',') + .map(|col| col.trim().to_string()) + .collect(); + + let values_str = &resto[pos_values + 6..].trim(); + let values_str = values_str.trim_end_matches(';'); + + //Divido los valores haciendo que se respeten las comillas simples + let valores: Vec> = values_str + .split("),") + .map(|val_list| { + val_list + .trim() + .trim_start_matches('(') + .trim_end_matches(')') + .split(',') + .map(|val| { + let trimmed_val = val.trim(); + // Elimino comillas simples si hay + if trimmed_val.starts_with('\'') && trimmed_val.ends_with('\'') { + &trimmed_val[1..trimmed_val.len() - 1] + } else { + trimmed_val + } + }) + .map(|val| val.to_string()) + .collect() + }) + .collect(); + + Ok(Insert { + tabla: tabla.to_string(), + columnas, + valores, + }) +} + + // // fn parse_delete(resto: &str) -> Result { // Ok(Delete { From 1c77e0ea2ff3e2fdec65e745c3052edc865e943e Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 1 Sep 2024 21:09:24 -0300 Subject: [PATCH 18/54] cargo fmt y permito que imprimir_resultado no hagan nada en update e insert --- output.csv | 16 +++++------ src/comandos.rs | 12 +++++--- src/errores.rs | 4 +-- src/insert.rs | 2 +- src/main.rs | 2 +- src/parser.rs | 8 +++--- src/update.rs | 73 +++++++++++++++++++++++++++++++++++-------------- 7 files changed, 77 insertions(+), 40 deletions(-) diff --git a/output.csv b/output.csv index 1d74c20..0fb8493 100644 --- a/output.csv +++ b/output.csv @@ -1,10 +1,10 @@ -ordenes (id, id_cliente, producto, cantidad) -VALUES (111, 6 6, 'Laptop', 3); +bbbbbbbbbbb: Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas -Consulta sql: INSERT INTO ordenes (id, id_cliente, producto, cantidad) -VALUES (111, 6 6, 'Laptop', 3); +Consulta sql: UPDATE clientes +SET email = 'marodriguez@hotmail.com' +WHERE id = 4; -Ejecutar consulta en la tabla: "ordenes" -Columnas a insertar: ["id", "id_cliente", "producto", "cantidad"] -Valores a insertar: [["111", "6 6", "Laptop", "3"]] -111, 6 6, Laptop, 3 +Ejecutar consulta en la tabla: "clientes" +Columnas a actualizar: ["email"] +Valores a asignar: ["marodriguez@hotmail.com"] +Restricciones: "id = 4" diff --git a/src/comandos.rs b/src/comandos.rs index 2d238ba..9eb2407 100644 --- a/src/comandos.rs +++ b/src/comandos.rs @@ -1,7 +1,7 @@ use crate::errores::ParseError; +use crate::insert::Insert; use crate::select::Select; use crate::update::Update; -use crate::insert::Insert; pub enum Comando { Select(Select), @@ -21,9 +21,13 @@ impl Comando { pub fn imprimir_resultados(&self, results: &[Vec]) { match self { Comando::Select(_select) => Select::imprimir_resultados(results), - Comando::Update(_update) => Select::imprimir_resultados(results), - Comando::Insert(insert) => Select::imprimir_resultados(results), - + Comando::Update(_update) => { + //nadaaa + } + // } + Comando::Insert(_insert) => { + // nadaaa + } } } } diff --git a/src/errores.rs b/src/errores.rs index 3f0616a..7c1d7a9 100644 --- a/src/errores.rs +++ b/src/errores.rs @@ -9,7 +9,7 @@ impl ParseError { match self { ParseError::ComandoNoReconocido => "Comando no reconocido", ParseError::ErrorDeSintaxis => "Error de sintaxis", - ParseError::Error(msg) => &msg, + ParseError::Error(msg) => msg, } } -} \ No newline at end of file +} diff --git a/src/insert.rs b/src/insert.rs index 821a785..659cfb9 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -22,4 +22,4 @@ impl Insert { Ok(valores_nuevos) } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 8dac29c..d66229a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ mod comandos; mod errores; +mod insert; mod parser; mod select; mod update; -mod insert; use parser::parsear_consulta; use std::env; diff --git a/src/parser.rs b/src/parser.rs index c72e8d0..32480a6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,9 +1,9 @@ // use std::io::{BufRead, BufReader}; use crate::comandos::Comando; use crate::errores::ParseError; +use crate::insert::Insert; use crate::select::Select; use crate::update::Update; -use crate::insert::Insert; // pub struct Update { // } @@ -40,7 +40,7 @@ pub fn parsear_consulta(consulta: &str) -> Result { "insert" => { let insert = parse_insert(resto)?; Comando::Insert(insert) - }, + } "update" => { let update = parse_update(resto)?; println!("bbbbbbbbbbb:"); @@ -219,7 +219,8 @@ fn parse_insert(resto: &str) -> Result { let tabla = tabla_y_columnas[..paos_parentesis_abre].trim(); let columnas_str = &tabla_y_columnas[paos_parentesis_abre + 1..pos_parentesis_cierra].trim(); - let columnas: Vec = columnas_str.split(',') + let columnas: Vec = columnas_str + .split(',') .map(|col| col.trim().to_string()) .collect(); @@ -256,7 +257,6 @@ fn parse_insert(resto: &str) -> Result { }) } - // // fn parse_delete(resto: &str) -> Result { // Ok(Delete { diff --git a/src/update.rs b/src/update.rs index 863a093..fde1175 100644 --- a/src/update.rs +++ b/src/update.rs @@ -20,14 +20,19 @@ impl Update { println!("Restricciones: {:?}", restricciones); } - let ruta_archivo = Path::new(ruta_carpeta_tablas).join(&self.tabla).with_extension("csv"); + let ruta_archivo = Path::new(ruta_carpeta_tablas) + .join(&self.tabla) + .with_extension("csv"); let ruta_archivo_temporal = ruta_archivo.with_extension("tmp"); // Abro archivo tabla let archivo_entrada = match File::open(&ruta_archivo) { Ok(file) => file, Err(err) => { - println!("Error al abrir el archivo de entrada: {}", ParseError::Error(err.to_string()).to_string()); + println!( + "Error al abrir el archivo de entrada: {}", + ParseError::Error(err.to_string()).to_string() + ); return Err(ParseError::Error(err.to_string())); } }; @@ -38,7 +43,10 @@ impl Update { let mut archivo_temporal = match File::create(&ruta_archivo_temporal) { Ok(file) => file, Err(err) => { - println!("Error al crear el archivo temporal: {}", ParseError::Error(err.to_string()).to_string()); + println!( + "Error al crear el archivo temporal: {}", + ParseError::Error(err.to_string()).to_string() + ); return Err(ParseError::Error(err.to_string())); } }; @@ -48,22 +56,32 @@ impl Update { let encabezados = match lineas.next() { Some(Ok(line)) => line, Some(Err(err)) => { - println!("Error al leer la primera línea del archivo: {}", ParseError::Error(err.to_string()).to_string()); + println!( + "Error al leer la primera línea del archivo: {}", + ParseError::Error(err.to_string()).to_string() + ); return Err(ParseError::Error(err.to_string())); } None => { - println!("{}", ParseError::Error("Archivo vacío".to_string()).to_string()); + println!( + "{}", + ParseError::Error("Archivo vacío".to_string()).to_string() + ); return Err(ParseError::Error("Archivo vacío".to_string())); } }; - let encabezados: Vec = encabezados.split(',') + let encabezados: Vec = encabezados + .split(',') .map(|s| s.trim().to_string()) .collect(); // Escribo encabezados al archivo de salida if let Err(err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { - println!("Error al escribir encabezados en el archivo temporal: {}", ParseError::Error(err.to_string()).to_string()); + println!( + "Error al escribir encabezados en el archivo temporal: {}", + ParseError::Error(err.to_string()).to_string() + ); return Err(ParseError::Error(err.to_string())); } @@ -72,17 +90,18 @@ impl Update { // leo linea a linea el archivo de entrada for linea in lineas { let mut registro: Vec = match linea { - Ok(line) => line.split(',') - .map(|s| s.trim().to_string()) - .collect(), + Ok(line) => line.split(',').map(|s| s.trim().to_string()).collect(), Err(err) => { - println!("Error al leer una línea del archivo: {}", ParseError::Error(err.to_string()).to_string()); + println!( + "Error al leer una línea del archivo: {}", + ParseError::Error(err.to_string()).to_string() + ); continue; } }; // Aplico restricciones - let registro_valido = if let Some(ref restricciones) = self.restricciones { + let registro_valido = if let Some(ref _restricciones) = self.restricciones { match self.aplicar_restricciones(®istro, &encabezados) { Ok(valido) => valido, Err(err) => { @@ -107,7 +126,10 @@ impl Update { // Escribo el registro actualizada al archivo temporal if let Err(err) = writeln!(archivo_temporal, "{}", registro.join(",")) { - println!("Error al escribir el registro actualizada en el archivo temporal: {}", ParseError::Error(err.to_string()).to_string()); + println!( + "Error al escribir el registro actualizada en el archivo temporal: {}", + ParseError::Error(err.to_string()).to_string() + ); return Err(ParseError::Error(err.to_string())); } @@ -118,7 +140,10 @@ impl Update { match std::fs::rename(&ruta_archivo_temporal, &ruta_archivo) { Ok(_) => Ok(filas_actualizadas), Err(err) => { - println!("Error al reemplazar el archivo original: {}", ParseError::Error(err.to_string()).to_string()); + println!( + "Error al reemplazar el archivo original: {}", + ParseError::Error(err.to_string()).to_string() + ); Err(ParseError::Error(err.to_string())) } } @@ -149,10 +174,15 @@ impl Update { condicion }; - let cumple_condicion = self.aplicar_condicion(condicion_limpia, registro, encabezados)?; + let cumple_condicion = + self.aplicar_condicion(condicion_limpia, registro, encabezados)?; // Si es `AND NOT`, se niega el resultado de la condición - let cumple_condicion = if es_negado { !cumple_condicion } else { cumple_condicion }; + let cumple_condicion = if es_negado { + !cumple_condicion + } else { + cumple_condicion + }; // Si encontramos una condición `OR` que es verdadera, podemos dejar de evaluar if condicion.contains("||") { @@ -187,7 +217,9 @@ impl Update { } else if let Some(pos) = condicion.find('<') { (&condicion[..pos].trim(), "<", &condicion[pos + 1..].trim()) } else { - return Err(ParseError::Error("Operador no válido en la condición".to_string())); + return Err(ParseError::Error( + "Operador no válido en la condición".to_string(), + )); }; let valor = valor.trim().trim_matches('\'').to_string(); @@ -204,7 +236,9 @@ impl Update { }; Ok(resultado) } else { - Err(ParseError::Error("No se encontró el valor en el registro".to_string())) + Err(ParseError::Error( + "No se encontró el valor en el registro".to_string(), + )) } } None => Err(ParseError::Error(format!( @@ -213,5 +247,4 @@ impl Update { ))), } } - -} \ No newline at end of file +} From ee3f61fb098c6362a88357fe779c937c9e34c4b2 Mon Sep 17 00:00:00 2001 From: German Douce Date: Mon, 2 Sep 2024 01:01:37 -0300 Subject: [PATCH 19/54] agrego codigo para ejcutar insert. Cambio csv's para ue tengan el salto de linea finallll --- output.csv | 22 ++++++---- src/comandos.rs | 1 + src/insert.rs | 86 +++++++++++++++++++++++++++++++++++---- src/main.rs | 2 +- tablas/clientes.csv | 2 +- tablas/ordenes.csv | 3 +- tablas/ordenes_limpio.csv | 11 +++++ 7 files changed, 109 insertions(+), 18 deletions(-) create mode 100644 tablas/ordenes_limpio.csv diff --git a/output.csv b/output.csv index 0fb8493..5f4bb4e 100644 --- a/output.csv +++ b/output.csv @@ -1,10 +1,18 @@ -bbbbbbbbbbb: Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas -Consulta sql: UPDATE clientes -SET email = 'marodriguez@hotmail.com' -WHERE id = 4; +Consulta sql: SELECT id, nombre, email +FROM clientes WHERE apellido = 'López' +ORDER BY email DESC; Ejecutar consulta en la tabla: "clientes" -Columnas a actualizar: ["email"] -Valores a asignar: ["marodriguez@hotmail.com"] -Restricciones: "id = 4" +Columnas a seleccionar: ["id", "nombre", "email"] +Restricciones: "apellido = 'López'" +Order by: "email DESC" +Encabezados antes de agregar: ["id", "nombre", "apellido", "email"] +Encabezados select: ["id", "nombre", "email"] +Estado inicial: +Ordenamiento: email DESC +Encabezados select: ["id", "nombre", "email"] +Resultado antes del ordenamiento: [["id", "nombre", "email"], ["2", "Ana", "ana.lopez@email.com"], ["5", "José", "jose.lopez@email.com"]] +id, nombre, email +5, José, jose.lopez@email.com +2, Ana, ana.lopez@email.com diff --git a/src/comandos.rs b/src/comandos.rs index 9eb2407..ddcd032 100644 --- a/src/comandos.rs +++ b/src/comandos.rs @@ -22,6 +22,7 @@ impl Comando { match self { Comando::Select(_select) => Select::imprimir_resultados(results), Comando::Update(_update) => { + Select::imprimir_resultados(results) //nadaaa } // } diff --git a/src/insert.rs b/src/insert.rs index 659cfb9..25d016c 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -1,4 +1,8 @@ use crate::errores::ParseError; +use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::path::Path; +use std::fs::{File, OpenOptions}; + pub struct Insert { pub tabla: String, @@ -7,19 +11,85 @@ pub struct Insert { } impl Insert { - pub fn ejecutar(&self, _ruta_carpeta_tablas: &str) -> Result>, ParseError> { + pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, ParseError> { println!("Ejecutar consulta en la tabla: {:?}", self.tabla); println!("Columnas a insertar: {:?}", self.columnas); println!("Valores a insertar: {:?}", self.valores); - let mut valores_nuevos = Vec::with_capacity(self.valores.len()); - for fila in &self.valores { - let mut fila_nueva = Vec::with_capacity(fila.len()); - for valor in fila { - fila_nueva.push(valor.to_string()); + + let archivo_csv = Path::new(ruta_carpeta_tablas).join(format!("{}.csv", self.tabla)); + + // Leo archivo para obtener los encabezados + let file = match File::open(&archivo_csv) { + Ok(file) => file, + Err(err) => { + let err_msg = format!("Error al abrir el archivo: {}", err); + println!("Error: {}", err_msg); + return Err(ParseError::Error(err_msg)); } - valores_nuevos.push(fila_nueva); + }; + + let mut reader = BufReader::new(file); + let mut encabezados = String::new(); + + if let Err(err) = reader.read_line(&mut encabezados) { + let err_msg = format!("Error al leer los encabezados: {}", err); + println!("Error: {}", err_msg); + return Err(ParseError::Error(err_msg)); } - Ok(valores_nuevos) + let encabezados: Vec = encabezados + .trim() + .split(',') + .map(|col| col.trim().to_string()) + .collect(); + + // Verifio que las columnas de la consulta existan en el archivo + let mut columnas_indices = Vec::with_capacity(self.columnas.len()); + for col in &self.columnas { + match encabezados.iter().position(|header| header == col) { + Some(index) => columnas_indices.push(index), + None => { + let err_msg = format!("Columna '{}' no existe en la tabla", col); + println!("ERROR: {}", err_msg); + return Err(ParseError::Error(err_msg)); + } + } + } + + // Abro el archivo en modo append para agregar los nuevos + let file = match OpenOptions::new().append(true).open(&archivo_csv) { + Ok(file) => file, + Err(err) => { + let err_msg = format!("Error al abrir el archivo para escritura: {}", err); + println!("Error: {}", err_msg); + return Err(ParseError::Error(err_msg)); + } + }; + + let mut writer = BufWriter::new(file); + + let mut filas_insertadas = Vec::with_capacity(self.valores.len()); + + // Escribo archivo + for fila in &self.valores { + let mut fila_nueva = vec!["".to_string(); encabezados.len()]; + + for (i, col_index) in columnas_indices.iter().enumerate() { + if i < fila.len() { + fila_nueva[*col_index] = fila[i].to_string(); + } + } + + //la agrego + if let Err(err) = writeln!(writer, "{}", fila_nueva.join(",")) { + let err_msg = format!("Error al escribir en el archivo: {}", err); + println!("Error: {}", err_msg); + return Err(ParseError::Error(err_msg)); + } + + filas_insertadas.push(fila_nueva); + } + // devuelvo pa q no tire error + Ok(filas_insertadas) } } diff --git a/src/main.rs b/src/main.rs index d66229a..cfd800d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,7 +38,7 @@ fn main() { comando.imprimir_resultados(&results); } Err(err) => { - println!("Error: {:?}", err); + println!("{:?}", err); } } } diff --git a/tablas/clientes.csv b/tablas/clientes.csv index d2f4336..85310a6 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -2,6 +2,6 @@ id,nombre,apellido,email 1,Juan,Pérez,juan.perez@email.com 2,Ana,López,ana.lopez@email.com 3,Carlos,Gómez,carlos.gomez@email.com -4,María,Rodríguez,mrodriguez@hotmail.com +4,María,Rodríguez,maria.rodriguez@email.com 5,José,López,jose.lopez@email.com 6,Laura,Fernández,laura.fernandez@email.com diff --git a/tablas/ordenes.csv b/tablas/ordenes.csv index 1ee9dbe..e8d32be 100644 --- a/tablas/ordenes.csv +++ b/tablas/ordenes.csv @@ -8,4 +8,5 @@ id,id_cliente,producto,cantidad 107,6,Altavoces,1 108,4,Auriculares,1 109,5,Laptop,1 -110,6,Teléfono,2 \ No newline at end of file +110,6,Teléfono,2 +111,6,Laptop,3 diff --git a/tablas/ordenes_limpio.csv b/tablas/ordenes_limpio.csv new file mode 100644 index 0000000..bf38946 --- /dev/null +++ b/tablas/ordenes_limpio.csv @@ -0,0 +1,11 @@ +id,id_cliente,producto,cantidad +101,1,Laptop,1 +103,1,Monitor,1 +102,2,Teléfono,2 +104,3,Teclado,1 +105,4,Mouse,2 +106,5,Impresora,1 +107,6,Altavoces,1 +108,4,Auriculares,1 +109,5,Laptop,1 +110,6,Teléfono,2 From 263a2c80f82e81f51e2b1195d9833fe382153e25 Mon Sep 17 00:00:00 2001 From: German Douce Date: Tue, 3 Sep 2024 19:06:01 -0300 Subject: [PATCH 20/54] agrego codigo para parsear delete --- output.csv | 21 ++++----------------- src/comandos.rs | 7 ++++++- src/delete.rs | 19 +++++++++++++++++++ src/main.rs | 1 + src/parser.rs | 44 ++++++++++++++++++++++++++++++++++---------- 5 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 src/delete.rs diff --git a/output.csv b/output.csv index 5f4bb4e..9344d74 100644 --- a/output.csv +++ b/output.csv @@ -1,18 +1,5 @@ Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas -Consulta sql: SELECT id, nombre, email -FROM clientes WHERE apellido = 'López' -ORDER BY email DESC; - -Ejecutar consulta en la tabla: "clientes" -Columnas a seleccionar: ["id", "nombre", "email"] -Restricciones: "apellido = 'López'" -Order by: "email DESC" -Encabezados antes de agregar: ["id", "nombre", "apellido", "email"] -Encabezados select: ["id", "nombre", "email"] -Estado inicial: -Ordenamiento: email DESC -Encabezados select: ["id", "nombre", "email"] -Resultado antes del ordenamiento: [["id", "nombre", "email"], ["2", "Ana", "ana.lopez@email.com"], ["5", "José", "jose.lopez@email.com"]] -id, nombre, email -5, José, jose.lopez@email.com -2, Ana, ana.lopez@email.com +Consulta sql: DELETE FROM clientes + WHERE apellido='López' AND id =2; +Tabla: "clientes" +Restricciones: "apellido=López AND id =2" diff --git a/src/comandos.rs b/src/comandos.rs index ddcd032..5fef1c9 100644 --- a/src/comandos.rs +++ b/src/comandos.rs @@ -1,3 +1,4 @@ +use crate::delete::Delete; use crate::errores::ParseError; use crate::insert::Insert; use crate::select::Select; @@ -7,7 +8,7 @@ pub enum Comando { Select(Select), Insert(Insert), Update(Update), - // Delete(Delete), + Delete(Delete), } impl Comando { @@ -16,6 +17,7 @@ impl Comando { Comando::Select(select) => select.ejecutar(ruta_carpeta), Comando::Update(update) => update.ejecutar(ruta_carpeta), Comando::Insert(insert) => insert.ejecutar(ruta_carpeta), + Comando::Delete(delete) => delete.ejecutar(ruta_carpeta), } } pub fn imprimir_resultados(&self, results: &[Vec]) { @@ -29,6 +31,9 @@ impl Comando { Comando::Insert(_insert) => { // nadaaa } + Comando::Delete(_delete) => { + // nadaaa + } } } } diff --git a/src/delete.rs b/src/delete.rs new file mode 100644 index 0000000..1f14b2d --- /dev/null +++ b/src/delete.rs @@ -0,0 +1,19 @@ +use crate::errores::ParseError; + +pub struct Delete { + pub tabla: String, + pub restricciones: Option, +} +impl Delete { + pub fn ejecutar(&self, _ruta_carpeta_tablas: &str) -> Result>, ParseError> { + println!("Tabla: {:?}", self.tabla); + + if let Some(ref restricciones) = self.restricciones { + println!("Restricciones: {:?}", restricciones); + } else { + println!("Sin restricciones"); + } + + Ok(vec![]) + } +} diff --git a/src/main.rs b/src/main.rs index cfd800d..76118f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod comandos; +mod delete; mod errores; mod insert; mod parser; diff --git a/src/parser.rs b/src/parser.rs index 32480a6..eb8d612 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,5 +1,6 @@ // use std::io::{BufRead, BufReader}; use crate::comandos::Comando; +use crate::delete::Delete; use crate::errores::ParseError; use crate::insert::Insert; use crate::select::Select; @@ -25,7 +26,7 @@ pub fn parsear_consulta(consulta: &str) -> Result { ("insert", resto.trim()) } else if let Some(resto) = consulta.strip_prefix("UPDATE") { ("update", resto.trim()) - } else if let Some(resto) = consulta.strip_prefix("delete from") { + } else if let Some(resto) = consulta.strip_prefix("DELETE FROM") { ("delete", resto.trim()) } else { return Err(ParseError::ComandoNoReconocido); @@ -46,10 +47,10 @@ pub fn parsear_consulta(consulta: &str) -> Result { println!("bbbbbbbbbbb:"); Comando::Update(update) } - // "delete" => { - // let delete = parse_delete(resto)?; - // Comando::Delete(delete) - // }, + "delete" => { + let delete = parse_delete(resto)?; + Comando::Delete(delete) + } _ => return Err(ParseError::ComandoNoReconocido), }; Ok(comando) // Devolvemos el comando directamente, no una tupla @@ -257,8 +258,31 @@ fn parse_insert(resto: &str) -> Result { }) } -// -// fn parse_delete(resto: &str) -> Result { -// Ok(Delete { -// }) -// } +fn parse_delete(resto: &str) -> Result { + // saco todos los ; y saltos de linea + let resto = resto + .replace(";", "") + .replace("\n", " ") + .replace("\r", " ") + .trim() + .to_string(); + + let partes: Vec<&str> = resto.splitn(2, "WHERE").collect(); + + let tabla = partes[0].trim().to_string(); + + let restricciones = if partes.len() > 1 { + Some(partes[1].trim().replace("'", "")) + } else { + None + }; + + if tabla.is_empty() { + return Err(ParseError::Error("Nombre de tabla faltante".to_string())); + } + + Ok(Delete { + tabla, + restricciones, + }) +} From a2210543414d8308919f19224b1da7e898db004f Mon Sep 17 00:00:00 2001 From: German Douce Date: Tue, 3 Sep 2024 20:41:02 -0300 Subject: [PATCH 21/54] agrego codigo para parsear ejecutar delete. Por ahora borra todas las filas --- output.csv | 5 +- src/delete.rs | 126 +++++++++++++++++++++++++++++++++++++++++--- tablas/clientes.csv | 6 --- 3 files changed, 119 insertions(+), 18 deletions(-) diff --git a/output.csv b/output.csv index 9344d74..eca7908 100644 --- a/output.csv +++ b/output.csv @@ -1,5 +1,2 @@ Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas -Consulta sql: DELETE FROM clientes - WHERE apellido='López' AND id =2; -Tabla: "clientes" -Restricciones: "apellido=López AND id =2" +Consulta sql: DELETE FROM clientes WHERE id >3; diff --git a/src/delete.rs b/src/delete.rs index 1f14b2d..ed3a01c 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -1,19 +1,129 @@ use crate::errores::ParseError; - +use std::fs::{self, File}; +use std::io::{BufRead, BufReader, Write}; pub struct Delete { pub tabla: String, pub restricciones: Option, } impl Delete { - pub fn ejecutar(&self, _ruta_carpeta_tablas: &str) -> Result>, ParseError> { - println!("Tabla: {:?}", self.tabla); + pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, ParseError> { + let ruta_archivo = format!("{}/{}.csv", ruta_carpeta_tablas, self.tabla); + let ruta_temporal = format!("{}/{}.tmp", ruta_carpeta_tablas, self.tabla); + + // Abro archivo tabla + let archivo = match File::open(&ruta_archivo) { + Ok(file) => file, + Err(err) => { + println!( + "Error al abrir el archivo de entrada: {}", + err + ); + return Err(ParseError::Error(err.to_string())); + } + }; + + let reader = BufReader::new(archivo); + + // Creo un archivo temporal + let mut archivo_temporal = match File::create(&ruta_temporal) { + Ok(file) => file, + Err(err) => { + println!( + "Error al crear el archivo temporal: {}", + err + ); + return Err(ParseError::Error(err.to_string())); + } + }; + + // encabezadosss + let mut lineas = reader.lines(); + let encabezados = match lineas.next() { + Some(Ok(line)) => line, + Some(Err(err)) => { + println!( + "Error al leer la primera línea del archivo: {}", + ParseError::Error(err.to_string()).to_string() + ); + return Err(ParseError::Error(err.to_string())); + } + None => { + println!( + "{}", + ParseError::Error("Archivo vacío".to_string()).to_string() + ); + return Err(ParseError::Error("Archivo vacío".to_string())); + } + }; - if let Some(ref restricciones) = self.restricciones { - println!("Restricciones: {:?}", restricciones); - } else { - println!("Sin restricciones"); + let encabezados: Vec = encabezados + .split(',') + .map(|s| s.trim().to_string()) + .collect(); + + // Escribo encabezados al archivo de salida + if let Err(err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { + println!( + "Error al escribir encabezados en el archivo temporal: {}", + ParseError::Error(err.to_string()).to_string() + ); + return Err(ParseError::Error(err.to_string())); } - Ok(vec![]) + let mut filas_resultantes = Vec::new(); + + // Leo el resto del archivo y veo con cuales me quedo + for linea in lineas { + let linea = match linea { + Ok(l) => l, + Err(err) => { + println!( + "Error al leer una línea del archivo: {}", + err + ); + return Err(ParseError::Error(err.to_string())); + } + }; + + let fila: Vec = linea.split(',') + .map(|s| s.trim().to_string()) + .collect(); + + if self.restricciones.is_none() || !self.aplicar_restricciones(&fila, &encabezados)? { + // Si no hay restricciones o si la fila no cumple con las restricciones, + // la escribimos en el archivo temporal + if let Err(err) = writeln!(archivo_temporal, "{}", linea) { + println!( + "Error al escribir en el archivo temporal: {}", + err + ); + return Err(ParseError::Error(err.to_string())); + } + } else { + // Fila que cumple con las restricciones y se elimina + filas_resultantes.push(fila); + } + } + + // Reemplazo el archivo original con el archivo temporal + if let Err(err) = fs::rename(&ruta_temporal, &ruta_archivo) { + println!( + "Error al reemplazar el archivo original con el archivo temporal: {}", + err + ); + return Err(ParseError::Error(err.to_string())); + } + + Ok(filas_resultantes) + } + + + pub fn aplicar_restricciones( + &self, + _registro: &[String], + _encabezados: &[String], + ) -> Result { + Ok(true) + } } diff --git a/tablas/clientes.csv b/tablas/clientes.csv index 85310a6..d65e942 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -1,7 +1 @@ id,nombre,apellido,email -1,Juan,Pérez,juan.perez@email.com -2,Ana,López,ana.lopez@email.com -3,Carlos,Gómez,carlos.gomez@email.com -4,María,Rodríguez,maria.rodriguez@email.com -5,José,López,jose.lopez@email.com -6,Laura,Fernández,laura.fernandez@email.com From 5a646c36663de8817506bbe125203e9f848b36a9 Mon Sep 17 00:00:00 2001 From: German Douce Date: Wed, 4 Sep 2024 01:04:36 -0300 Subject: [PATCH 22/54] codigo para restriccione del delete. FUNCIONA MUY MAL.No andan los ORS (ya se qxq). Mucho menos tengo la menor de idea de como meter parentesis --- output.csv | 2 +- src/delete.rs | 100 ++++++++++++++++++++++++++++++++++++++++++-- tablas/clientes.csv | 6 +++ 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/output.csv b/output.csv index eca7908..0498c1c 100644 --- a/output.csv +++ b/output.csv @@ -1,2 +1,2 @@ Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas -Consulta sql: DELETE FROM clientes WHERE id >3; +Consulta sql: DELETE FROM clientes WHERE nombre ='Juan' OR id=1; diff --git a/src/delete.rs b/src/delete.rs index ed3a01c..801b513 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -120,10 +120,104 @@ impl Delete { pub fn aplicar_restricciones( &self, - _registro: &[String], - _encabezados: &[String], + registro: &[String], + encabezados: &[String], ) -> Result { - Ok(true) + if let Some(ref restricciones) = self.restricciones { + // Reemplazo de operadores lógicos por simbolos + let restricciones = restricciones + .replace(" AND NOT ", " && !") + .replace(" AND ", " && ") + .replace(" OR ", " || "); + let mut resultado = true; // Empezamos asumiendo que se cumplen todas las condiciones + + // Separamos las restricciones en condiciones individuales + let condiciones = restricciones.split("&&").flat_map(|s| s.split("||")); + for condicion in condiciones { + // println!("condiciones: {:?}",condicion); + let condicion = condicion.trim(); + let es_negado = condicion.starts_with('!'); + let condicion_limpia = if es_negado { + &condicion[1..].trim() + } else { + condicion + }; + + let cumple_condicion = + self.aplicar_condicion(condicion_limpia, registro, encabezados)?; + + // Si es `AND NOT`, se niega el resultado de la condición + let cumple_condicion = if es_negado { + !cumple_condicion + } else { + cumple_condicion + }; + + // Si encontramos una condición `OR` que es verdadera, podemos dejar de evaluar + if condicion.contains("||") { + if cumple_condicion { + println!("registro: {:?}",registro); + return Ok(true); + } + } else { + resultado = resultado && cumple_condicion; + // Si alguna condición `AND` es falsa, podemos dejar de evaluar + if !resultado { + return Ok(false); + } + } + } + + Ok(resultado) + } else { + Ok(true) // Sin restricciones, el registro siempre es válido + } } + + fn aplicar_condicion( + &self, + condicion: &str, + registro: &[String], + encabezados: &[String], + ) -> Result { + let (columna, operador, valor) = if let Some(pos) = condicion.find('=') { + (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) + } else if let Some(pos) = condicion.find('>') { + (&condicion[..pos].trim(), ">", &condicion[pos + 1..].trim()) + } else if let Some(pos) = condicion.find('<') { + (&condicion[..pos].trim(), "<", &condicion[pos + 1..].trim()) + } else { + return Err(ParseError::Error( + "Operador no válido en la condición".to_string(), + )); + }; + + let valor = valor.trim().trim_matches('\'').to_string(); + // Encuentro el índice de la columna en los encabezados + match encabezados.iter().position(|enc| enc == columna) { + Some(indice) => { + if let Some(valor_registro) = registro.get(indice) { + // println!("operador: {:}",operador); + let resultado = match operador { + "=" => valor_registro == &valor, + ">" => valor_registro > &valor, + "<" => valor_registro < &valor, + _ => false, + }; + Ok(resultado) + } else { + Err(ParseError::Error( + "No se encontró el valor en el registro".to_string(), + )) + } + } + None => Err(ParseError::Error(format!( + "Columna '{}' no encontrada en los encabezados", + columna + ))), + } + } + + } diff --git a/tablas/clientes.csv b/tablas/clientes.csv index d65e942..85310a6 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -1 +1,7 @@ id,nombre,apellido,email +1,Juan,Pérez,juan.perez@email.com +2,Ana,López,ana.lopez@email.com +3,Carlos,Gómez,carlos.gomez@email.com +4,María,Rodríguez,maria.rodriguez@email.com +5,José,López,jose.lopez@email.com +6,Laura,Fernández,laura.fernandez@email.com From 8d5f95f728e553e1901efef64e4f6013307f3e28 Mon Sep 17 00:00:00 2001 From: German Douce Date: Thu, 5 Sep 2024 21:03:20 -0300 Subject: [PATCH 23/54] agrego las condidiones del WHERE para delete. Falta pasar esa logica a update y select --- output.csv | 111 +++++++++++++++++++- src/delete.rs | 286 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 327 insertions(+), 70 deletions(-) diff --git a/output.csv b/output.csv index 0498c1c..0c23b01 100644 --- a/output.csv +++ b/output.csv @@ -1,2 +1,109 @@ -Ruta a las tablas: /home/linuxlite/Documents/taller/tp-individual-taller-9508/tablas -Consulta sql: DELETE FROM clientes WHERE nombre ='Juan' OR id=1; +Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas +Consulta sql: DELETE FROM clientes WHERE id > 1 AND NOT nombre = 'Ana'; +Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +token: id>1 +token: AND +token: NOT +token: nombre=Ana +tokenn: "id>1" +tokenn: "nombre=Ana" +tokenn: "NOT" +token en evaluate postfix "NOT" +left: "nombre=Ana" +right: "nombre=Ana" +Evaluando condición: "nombre=Ana" +tokenn: "AND" +token en evaluate postfix "AND" +left: "true" +right: "true" +Evaluando condición: "id>1" +Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +token: id>1 +token: AND +token: NOT +token: nombre=Ana +tokenn: "id>1" +tokenn: "nombre=Ana" +tokenn: "NOT" +token en evaluate postfix "NOT" +left: "nombre=Ana" +right: "nombre=Ana" +Evaluando condición: "nombre=Ana" +tokenn: "AND" +token en evaluate postfix "AND" +left: "false" +right: "false" +Evaluando condición: "id>1" +Evaluando condición: "false" +Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +token: id>1 +token: AND +token: NOT +token: nombre=Ana +tokenn: "id>1" +tokenn: "nombre=Ana" +tokenn: "NOT" +token en evaluate postfix "NOT" +left: "nombre=Ana" +right: "nombre=Ana" +Evaluando condición: "nombre=Ana" +tokenn: "AND" +token en evaluate postfix "AND" +left: "true" +right: "true" +Evaluando condición: "id>1" +Evaluando condición: "true" +Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +token: id>1 +token: AND +token: NOT +token: nombre=Ana +tokenn: "id>1" +tokenn: "nombre=Ana" +tokenn: "NOT" +token en evaluate postfix "NOT" +left: "nombre=Ana" +right: "nombre=Ana" +Evaluando condición: "nombre=Ana" +tokenn: "AND" +token en evaluate postfix "AND" +left: "true" +right: "true" +Evaluando condición: "id>1" +Evaluando condición: "true" +Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +token: id>1 +token: AND +token: NOT +token: nombre=Ana +tokenn: "id>1" +tokenn: "nombre=Ana" +tokenn: "NOT" +token en evaluate postfix "NOT" +left: "nombre=Ana" +right: "nombre=Ana" +Evaluando condición: "nombre=Ana" +tokenn: "AND" +token en evaluate postfix "AND" +left: "true" +right: "true" +Evaluando condición: "id>1" +Evaluando condición: "true" +Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +token: id>1 +token: AND +token: NOT +token: nombre=Ana +tokenn: "id>1" +tokenn: "nombre=Ana" +tokenn: "NOT" +token en evaluate postfix "NOT" +left: "nombre=Ana" +right: "nombre=Ana" +Evaluando condición: "nombre=Ana" +tokenn: "AND" +token en evaluate postfix "AND" +left: "true" +right: "true" +Evaluando condición: "id>1" +Evaluando condición: "true" diff --git a/src/delete.rs b/src/delete.rs index 801b513..6694926 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -1,6 +1,9 @@ use crate::errores::ParseError; use std::fs::{self, File}; use std::io::{BufRead, BufReader, Write}; +use std::iter::Peekable; +use std::str::Chars; + pub struct Delete { pub tabla: String, pub restricciones: Option, @@ -123,64 +126,220 @@ impl Delete { registro: &[String], encabezados: &[String], ) -> Result { - if let Some(ref restricciones) = self.restricciones { - // Reemplazo de operadores lógicos por simbolos - let restricciones = restricciones - .replace(" AND NOT ", " && !") - .replace(" AND ", " && ") - .replace(" OR ", " || "); - - let mut resultado = true; // Empezamos asumiendo que se cumplen todas las condiciones - - // Separamos las restricciones en condiciones individuales - let condiciones = restricciones.split("&&").flat_map(|s| s.split("||")); - for condicion in condiciones { - // println!("condiciones: {:?}",condicion); - let condicion = condicion.trim(); - let es_negado = condicion.starts_with('!'); - let condicion_limpia = if es_negado { - &condicion[1..].trim() - } else { - condicion - }; - - let cumple_condicion = - self.aplicar_condicion(condicion_limpia, registro, encabezados)?; - - // Si es `AND NOT`, se niega el resultado de la condición - let cumple_condicion = if es_negado { - !cumple_condicion - } else { - cumple_condicion - }; - - // Si encontramos una condición `OR` que es verdadera, podemos dejar de evaluar - if condicion.contains("||") { - if cumple_condicion { - println!("registro: {:?}",registro); - return Ok(true); + if let Some(restricciones) = &self.restricciones { + let tokens = self.tokenizar(restricciones); + let postfix = self.infix_a_postfix(&tokens)?; + let resultado = self.evaluar_postfix(&postfix, registro, encabezados)?; + Ok(resultado) + } else { + Ok(true) + } + } + + fn sacar_espacios_alrededor_operadores(&self, restricciones: &str) -> String { + let mut restricciones_limpio = String::new(); + let mut chars = restricciones.chars().peekable(); + + while let Some(ch) = chars.next() { + match ch { + '=' | '<' | '>' => { + //Eliminamos espacios antes del operador + if let Some(last_char) = restricciones_limpio.chars().last() { + if last_char == ' ' { + restricciones_limpio.pop(); + } } - } else { - resultado = resultado && cumple_condicion; - // Si alguna condición `AND` es falsa, podemos dejar de evaluar - if !resultado { - return Ok(false); + //Agregamos el operador al resultado + restricciones_limpio.push(ch); + + //Ignoramos cualquier espacio después del operador + while let Some(&next_ch) = chars.peek() { + if next_ch == ' ' { + chars.next(); + } else { + break; + } } } + _ => { + restricciones_limpio.push(ch); + } } + } - Ok(resultado) - } else { - Ok(true) // Sin restricciones, el registro siempre es válido + restricciones_limpio + } + + //Tokeniza las restricciones + fn tokenizar(&self, restricciones: &str) -> Vec { + let restricciones_limpio = self.sacar_espacios_alrededor_operadores(restricciones); + let mut chars = restricciones_limpio.chars().peekable(); + + let mut tokens = Vec::new(); + let mut token_actual = String::new(); + + let keywords = ["AND", "OR", "NOT", "(", ")"]; + + Self::loop_tokenizar(&mut tokens, &mut token_actual, keywords, &mut chars); + + if !token_actual.is_empty() { + tokens.push(token_actual.trim().to_string()); + } + + tokens.retain(|token| !token.is_empty()); + + // Debug: ver el estado final de los tokens + println!("Final tokens: {:?}", tokens); + + tokens + } + + fn loop_tokenizar(tokens: &mut Vec, token_actual: &mut String, keywords: [&str; 5], chars: &mut Peekable) { + while let Some(ch) = chars.next() { + match ch { + ' ' if !token_actual.is_empty() => { + tokens.push(token_actual.trim().to_string()); + token_actual.clear(); + } + '(' | ')' => { + if !token_actual.is_empty() { + tokens.push(token_actual.trim().to_string()); + token_actual.clear(); + } + tokens.push(ch.to_string()); + } + _ => { + token_actual.push(ch); + let token_upper = token_actual.trim().to_uppercase(); + if keywords.contains(&token_upper.as_str()) { + tokens.push(token_upper); + token_actual.clear(); + } + } + } + } + } + + //Convierte la notación infix (con paréntesis) a notación postfija (sin parentesis) + //infix: (A OR B) AND (C OR D) ====> postfix: A B OR C D OR AND + fn infix_a_postfix( + &self, + tokens: &[String] + ) -> Result, ParseError> { + let mut resultado = Vec::new(); + let mut operadores = Vec::new(); + + let precedencia = |op: &str| match op { + "NOT" => 3, + "AND" => 2, + "OR" => 1, + "(" => 0, + ")" => 0, + _ => -1, + }; + + for token in tokens.iter() { + println!("token: {}", token); + match token.as_str() { + "(" => operadores.push(token.to_string()), + ")" => { + while let Some(op) = operadores.pop() { + if op == "(" { + break; + } + resultado.push(op); + } + } + "AND" | "OR" | "NOT" => { + while let Some(op) = operadores.last() { + if precedencia(op) >= precedencia(token) { + // el operador en la cima de la pila se extrae de la pila + // y se coloca en el output antes de agregar el nuevo operador. + if let Some(op) = operadores.pop() { + resultado.push(op); //saco + } else { + return Err(ParseError::Error("restricciones invalidas".into())); + } + } else { + break; + } + } + operadores.push(token.to_string()); + } + _ => resultado.push(token.to_string()), + } } + + while let Some(op) = operadores.pop() { + resultado.push(op); + } + + Ok(resultado) } + + //Evalúo la expresión en notación postfija + fn evaluar_postfix( + &self, + tokens: &Vec, + registro: &[String], + encabezados: &[String], + ) -> Result { + let mut stack = Vec::new(); + + for token in tokens.iter() { + println!("tokenn: {:?}",token); + match token.as_str() { + "AND" | "OR" | "NOT" => { + println!("token en evaluate postfix {:?}",token); + let derecha = stack.pop().ok_or(ParseError::Error("expresión invalida".into()))?; + let izquierda = if token != "NOT" { + stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? + } else { + String::new() + }; + println!("left: {:?}",&derecha); + println!("right: {:?}",&derecha); + let resultado = match token.as_str() { + "AND" => self.aplicar_condicion(&izquierda, registro, encabezados)? && self.aplicar_condicion(&derecha, registro, encabezados)?, + "OR" => self.aplicar_condicion(&izquierda, registro, encabezados)? || self.aplicar_condicion(&derecha, registro, encabezados)?, + "NOT" => !self.aplicar_condicion(&derecha, registro, encabezados)?, + _ => false, + }; + stack.push(resultado.to_string()); + } + _ => //Proceso la condición directa + if stack.is_empty() && tokens.len() == 1 { + // Si el primer token es la única condición, evalúo directamente + let result = self.aplicar_condicion(token, registro, encabezados)?; + return Ok(result); + } else { + // Para otros tokens, los mete al stack para ser procesados con operadores + stack.push(token.to_string()); + } + } + } + //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) + Ok(stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? == "true") + } + + + //Aplico condición simple (comparaciones > < =) fn aplicar_condicion( &self, condicion: &str, registro: &[String], encabezados: &[String], ) -> Result { + println!("Evaluando condición: {:?}", condicion); + + if condicion == "true" { + return Ok(true); + } else if condicion == "false" { + return Ok(false); + } + + // Parseo la condición para encontrar operadores de comparación let (columna, operador, valor) = if let Some(pos) = condicion.find('=') { (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) } else if let Some(pos) = condicion.find('>') { @@ -193,31 +352,22 @@ impl Delete { )); }; - let valor = valor.trim().trim_matches('\'').to_string(); - // Encuentro el índice de la columna en los encabezados - match encabezados.iter().position(|enc| enc == columna) { - Some(indice) => { - if let Some(valor_registro) = registro.get(indice) { - // println!("operador: {:}",operador); - let resultado = match operador { - "=" => valor_registro == &valor, - ">" => valor_registro > &valor, - "<" => valor_registro < &valor, - _ => false, - }; - Ok(resultado) - } else { - Err(ParseError::Error( - "No se encontró el valor en el registro".to_string(), - )) - } - } - None => Err(ParseError::Error(format!( - "Columna '{}' no encontrada en los encabezados", - columna - ))), + let valor = valor.trim().trim_matches('\''); + + // Encuentro el índice de la columna, o devuelvo un error si no se encuentra + if let Some(indice) = encabezados.iter().position(|enc| enc == columna) { + let valor_registro = ®istro[indice]; + let resultado = match operador { + "=" => valor_registro.as_str() == valor, + ">" => valor_registro.as_str() > valor, + "<" => valor_registro.as_str() < valor, + _ => false, + }; + + Ok(resultado) + } else { + Err(ParseError::Error(format!("La Columna '{}' no esta en los encabezados", columna))) } } - } From 3fbee8f6156bf00bcc21e760b405211901e5eb8a Mon Sep 17 00:00:00 2001 From: German Douce Date: Thu, 5 Sep 2024 21:28:38 -0300 Subject: [PATCH 24/54] agrego las condidiones del WHERE para update --- .idea/.gitignore | 1 + output.csv | 85 +++++++------ src/update.rs | 283 +++++++++++++++++++++++++++++++++----------- tablas/clientes.csv | 8 +- 4 files changed, 270 insertions(+), 107 deletions(-) diff --git a/.idea/.gitignore b/.idea/.gitignore index b58b603..c7cfaa8 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -3,3 +3,4 @@ /workspace.xml # Editor-based HTTP Client requests /httpRequests/ +/inspectionProfiles/Project_Default.xml diff --git a/output.csv b/output.csv index 0c23b01..a9eed29 100644 --- a/output.csv +++ b/output.csv @@ -1,109 +1,120 @@ +bbbbbbbbbbb: Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas -Consulta sql: DELETE FROM clientes WHERE id > 1 AND NOT nombre = 'Ana'; -Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +Consulta sql: UPDATE clientes SET nombre='pepito' , apellido='pepe' WHERE id > 1 AND NOT nombre = 'Ana'; +Ejecutar consulta en la tabla: "clientes" +Columnas a actualizar: ["nombre", "apellido"] +Valores a asignar: ["pepito", "pepe"] +Restricciones: "id > 1 AND NOT nombre = 'Ana'" +Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] token: id>1 token: AND token: NOT -token: nombre=Ana +token: nombre='Ana' tokenn: "id>1" -tokenn: "nombre=Ana" +tokenn: "nombre='Ana'" tokenn: "NOT" token en evaluate postfix "NOT" -left: "nombre=Ana" -right: "nombre=Ana" -Evaluando condición: "nombre=Ana" +left: "nombre='Ana'" +right: "nombre='Ana'" +Evaluando condición: "nombre='Ana'" tokenn: "AND" token en evaluate postfix "AND" left: "true" right: "true" Evaluando condición: "id>1" -Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] token: id>1 token: AND token: NOT -token: nombre=Ana +token: nombre='Ana' tokenn: "id>1" -tokenn: "nombre=Ana" +tokenn: "nombre='Ana'" tokenn: "NOT" token en evaluate postfix "NOT" -left: "nombre=Ana" -right: "nombre=Ana" -Evaluando condición: "nombre=Ana" +left: "nombre='Ana'" +right: "nombre='Ana'" +Evaluando condición: "nombre='Ana'" tokenn: "AND" token en evaluate postfix "AND" left: "false" right: "false" Evaluando condición: "id>1" Evaluando condición: "false" -Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] token: id>1 token: AND token: NOT -token: nombre=Ana +token: nombre='Ana' tokenn: "id>1" -tokenn: "nombre=Ana" +tokenn: "nombre='Ana'" tokenn: "NOT" token en evaluate postfix "NOT" -left: "nombre=Ana" -right: "nombre=Ana" -Evaluando condición: "nombre=Ana" +left: "nombre='Ana'" +right: "nombre='Ana'" +Evaluando condición: "nombre='Ana'" tokenn: "AND" token en evaluate postfix "AND" left: "true" right: "true" Evaluando condición: "id>1" Evaluando condición: "true" -Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] token: id>1 token: AND token: NOT -token: nombre=Ana +token: nombre='Ana' tokenn: "id>1" -tokenn: "nombre=Ana" +tokenn: "nombre='Ana'" tokenn: "NOT" token en evaluate postfix "NOT" -left: "nombre=Ana" -right: "nombre=Ana" -Evaluando condición: "nombre=Ana" +left: "nombre='Ana'" +right: "nombre='Ana'" +Evaluando condición: "nombre='Ana'" tokenn: "AND" token en evaluate postfix "AND" left: "true" right: "true" Evaluando condición: "id>1" Evaluando condición: "true" -Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] token: id>1 token: AND token: NOT -token: nombre=Ana +token: nombre='Ana' tokenn: "id>1" -tokenn: "nombre=Ana" +tokenn: "nombre='Ana'" tokenn: "NOT" token en evaluate postfix "NOT" -left: "nombre=Ana" -right: "nombre=Ana" -Evaluando condición: "nombre=Ana" +left: "nombre='Ana'" +right: "nombre='Ana'" +Evaluando condición: "nombre='Ana'" tokenn: "AND" token en evaluate postfix "AND" left: "true" right: "true" Evaluando condición: "id>1" Evaluando condición: "true" -Final tokens: ["id>1", "AND", "NOT", "nombre=Ana"] +Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] token: id>1 token: AND token: NOT -token: nombre=Ana +token: nombre='Ana' tokenn: "id>1" -tokenn: "nombre=Ana" +tokenn: "nombre='Ana'" tokenn: "NOT" token en evaluate postfix "NOT" -left: "nombre=Ana" -right: "nombre=Ana" -Evaluando condición: "nombre=Ana" +left: "nombre='Ana'" +right: "nombre='Ana'" +Evaluando condición: "nombre='Ana'" tokenn: "AND" token en evaluate postfix "AND" left: "true" right: "true" Evaluando condición: "id>1" Evaluando condición: "true" +1, Juan, Pérez, juan.perez@email.com +2, Ana, López, ana.lopez@email.com +3, pepito, pepe, carlos.gomez@email.com +4, pepito, pepe, maria.rodriguez@email.com +5, pepito, pepe, jose.lopez@email.com +6, pepito, pepe, laura.fernandez@email.com diff --git a/src/update.rs b/src/update.rs index fde1175..3902bbd 100644 --- a/src/update.rs +++ b/src/update.rs @@ -1,7 +1,9 @@ use crate::errores::ParseError; use std::fs::File; use std::io::{BufRead, BufReader, Write}; +use std::iter::Peekable; use std::path::Path; +use std::str::Chars; pub struct Update { pub columnas: Vec, @@ -153,63 +155,220 @@ impl Update { registro: &[String], encabezados: &[String], ) -> Result { - if let Some(ref restricciones) = self.restricciones { - // Reemplazo de operadores lógicos por simbolos - let restricciones = restricciones - .replace(" AND NOT ", " && !") - .replace(" AND ", " && ") - .replace(" OR ", " || "); - - let mut resultado = true; // Empezamos asumiendo que se cumplen todas las condiciones - - // Separamos las restricciones en condiciones individuales - let condiciones = restricciones.split("&&").flat_map(|s| s.split("||")); - - for condicion in condiciones { - let condicion = condicion.trim(); - let es_negado = condicion.starts_with('!'); - let condicion_limpia = if es_negado { - &condicion[1..].trim() - } else { - condicion - }; - - let cumple_condicion = - self.aplicar_condicion(condicion_limpia, registro, encabezados)?; - - // Si es `AND NOT`, se niega el resultado de la condición - let cumple_condicion = if es_negado { - !cumple_condicion - } else { - cumple_condicion - }; - - // Si encontramos una condición `OR` que es verdadera, podemos dejar de evaluar - if condicion.contains("||") { - if cumple_condicion { - return Ok(true); + if let Some(restricciones) = &self.restricciones { + let tokens = self.tokenizar(restricciones); + let postfix = self.infix_a_postfix(&tokens)?; + let resultado = self.evaluar_postfix(&postfix, registro, encabezados)?; + Ok(resultado) + } else { + Ok(true) + } + } + + fn sacar_espacios_alrededor_operadores(&self, restricciones: &str) -> String { + let mut restricciones_limpio = String::new(); + let mut chars = restricciones.chars().peekable(); + + while let Some(ch) = chars.next() { + match ch { + '=' | '<' | '>' => { + //Eliminamos espacios antes del operador + if let Some(last_char) = restricciones_limpio.chars().last() { + if last_char == ' ' { + restricciones_limpio.pop(); + } } - } else { - resultado = resultado && cumple_condicion; - // Si alguna condición `AND` es falsa, podemos dejar de evaluar - if !resultado { - return Ok(false); + //Agregamos el operador al resultado + restricciones_limpio.push(ch); + + //Ignoramos cualquier espacio después del operador + while let Some(&next_ch) = chars.peek() { + if next_ch == ' ' { + chars.next(); + } else { + break; + } } } + _ => { + restricciones_limpio.push(ch); + } } + } - Ok(resultado) - } else { - Ok(true) // Sin restricciones, el registro siempre es válido + restricciones_limpio + } + + //Tokeniza las restricciones + fn tokenizar(&self, restricciones: &str) -> Vec { + let restricciones_limpio = self.sacar_espacios_alrededor_operadores(restricciones); + let mut chars = restricciones_limpio.chars().peekable(); + + let mut tokens = Vec::new(); + let mut token_actual = String::new(); + + let keywords = ["AND", "OR", "NOT", "(", ")"]; + + Self::loop_tokenizar(&mut tokens, &mut token_actual, keywords, &mut chars); + + if !token_actual.is_empty() { + tokens.push(token_actual.trim().to_string()); } + + tokens.retain(|token| !token.is_empty()); + + // Debug: ver el estado final de los tokens + println!("Final tokens: {:?}", tokens); + + tokens } + fn loop_tokenizar(tokens: &mut Vec, token_actual: &mut String, keywords: [&str; 5], chars: &mut Peekable) { + while let Some(ch) = chars.next() { + match ch { + ' ' if !token_actual.is_empty() => { + tokens.push(token_actual.trim().to_string()); + token_actual.clear(); + } + '(' | ')' => { + if !token_actual.is_empty() { + tokens.push(token_actual.trim().to_string()); + token_actual.clear(); + } + tokens.push(ch.to_string()); + } + _ => { + token_actual.push(ch); + let token_upper = token_actual.trim().to_uppercase(); + if keywords.contains(&token_upper.as_str()) { + tokens.push(token_upper); + token_actual.clear(); + } + } + } + } + } + + //Convierte la notación infix (con paréntesis) a notación postfija (sin parentesis) + //infix: (A OR B) AND (C OR D) ====> postfix: A B OR C D OR AND + fn infix_a_postfix( + &self, + tokens: &[String] + ) -> Result, ParseError> { + let mut resultado = Vec::new(); + let mut operadores = Vec::new(); + + let precedencia = |op: &str| match op { + "NOT" => 3, + "AND" => 2, + "OR" => 1, + "(" => 0, + ")" => 0, + _ => -1, + }; + + for token in tokens.iter() { + println!("token: {}", token); + match token.as_str() { + "(" => operadores.push(token.to_string()), + ")" => { + while let Some(op) = operadores.pop() { + if op == "(" { + break; + } + resultado.push(op); + } + } + "AND" | "OR" | "NOT" => { + while let Some(op) = operadores.last() { + if precedencia(op) >= precedencia(token) { + // el operador en la cima de la pila se extrae de la pila + // y se coloca en el output antes de agregar el nuevo operador. + if let Some(op) = operadores.pop() { + resultado.push(op); //saco + } else { + return Err(ParseError::Error("restricciones invalidas".into())); + } + } else { + break; + } + } + operadores.push(token.to_string()); + } + _ => resultado.push(token.to_string()), + } + } + + while let Some(op) = operadores.pop() { + resultado.push(op); + } + + Ok(resultado) + } + + + //Evalúo la expresión en notación postfija + fn evaluar_postfix( + &self, + tokens: &Vec, + registro: &[String], + encabezados: &[String], + ) -> Result { + let mut stack = Vec::new(); + + for token in tokens.iter() { + println!("tokenn: {:?}",token); + match token.as_str() { + "AND" | "OR" | "NOT" => { + println!("token en evaluate postfix {:?}",token); + let derecha = stack.pop().ok_or(ParseError::Error("expresión invalida".into()))?; + let izquierda = if token != "NOT" { + stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? + } else { + String::new() + }; + println!("left: {:?}",&derecha); + println!("right: {:?}",&derecha); + let resultado = match token.as_str() { + "AND" => self.aplicar_condicion(&izquierda, registro, encabezados)? && self.aplicar_condicion(&derecha, registro, encabezados)?, + "OR" => self.aplicar_condicion(&izquierda, registro, encabezados)? || self.aplicar_condicion(&derecha, registro, encabezados)?, + "NOT" => !self.aplicar_condicion(&derecha, registro, encabezados)?, + _ => false, + }; + stack.push(resultado.to_string()); + } + _ => //Proceso la condición directa + if stack.is_empty() && tokens.len() == 1 { + // Si el primer token es la única condición, evalúo directamente + let result = self.aplicar_condicion(token, registro, encabezados)?; + return Ok(result); + } else { + // Para otros tokens, los mete al stack para ser procesados con operadores + stack.push(token.to_string()); + } + } + } + //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) + Ok(stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? == "true") + } + + + //Aplico condición simple (comparaciones > < =) fn aplicar_condicion( &self, condicion: &str, registro: &[String], encabezados: &[String], ) -> Result { + println!("Evaluando condición: {:?}", condicion); + + if condicion == "true" { + return Ok(true); + } else if condicion == "false" { + return Ok(false); + } + + // Parseo la condición para encontrar operadores de comparación let (columna, operador, valor) = if let Some(pos) = condicion.find('=') { (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) } else if let Some(pos) = condicion.find('>') { @@ -222,29 +381,21 @@ impl Update { )); }; - let valor = valor.trim().trim_matches('\'').to_string(); - // Encuentro el índice de la columna en los encabezados - match encabezados.iter().position(|enc| enc == columna) { - Some(indice) => { - if let Some(valor_registro) = registro.get(indice) { - // println!("operador: {:}",operador); - let resultado = match operador { - "=" => valor_registro == &valor, - ">" => valor_registro > &valor, - "<" => valor_registro < &valor, - _ => false, - }; - Ok(resultado) - } else { - Err(ParseError::Error( - "No se encontró el valor en el registro".to_string(), - )) - } - } - None => Err(ParseError::Error(format!( - "Columna '{}' no encontrada en los encabezados", - columna - ))), + let valor = valor.trim().trim_matches('\''); + + // Encuentro el índice de la columna, o devuelvo un error si no se encuentra + if let Some(indice) = encabezados.iter().position(|enc| enc == columna) { + let valor_registro = ®istro[indice]; + let resultado = match operador { + "=" => valor_registro.as_str() == valor, + ">" => valor_registro.as_str() > valor, + "<" => valor_registro.as_str() < valor, + _ => false, + }; + + Ok(resultado) + } else { + Err(ParseError::Error(format!("La Columna '{}' no esta en los encabezados", columna))) } } } diff --git a/tablas/clientes.csv b/tablas/clientes.csv index 85310a6..9bf2941 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -1,7 +1,7 @@ id,nombre,apellido,email 1,Juan,Pérez,juan.perez@email.com 2,Ana,López,ana.lopez@email.com -3,Carlos,Gómez,carlos.gomez@email.com -4,María,Rodríguez,maria.rodriguez@email.com -5,José,López,jose.lopez@email.com -6,Laura,Fernández,laura.fernandez@email.com +3,pepito,pepe,carlos.gomez@email.com +4,pepito,pepe,maria.rodriguez@email.com +5,pepito,pepe,jose.lopez@email.com +6,pepito,pepe,laura.fernandez@email.com From ce6233d9efd089339e817ec6db343511b5a572de Mon Sep 17 00:00:00 2001 From: German Douce Date: Thu, 5 Sep 2024 23:35:30 -0300 Subject: [PATCH 25/54] agrego 2 tests para delete --- .idea/tp-individual-taller-9508.iml | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- output.csv | 154 ++++++++++++---------------- src/lib.rs | 7 ++ tablas/clientes.csv | 8 +- tests/delete_test.rs | 80 +++++++++++++++ 7 files changed, 157 insertions(+), 97 deletions(-) create mode 100644 src/lib.rs create mode 100644 tests/delete_test.rs diff --git a/.idea/tp-individual-taller-9508.iml b/.idea/tp-individual-taller-9508.iml index cf84ae4..bbe0a70 100644 --- a/.idea/tp-individual-taller-9508.iml +++ b/.idea/tp-individual-taller-9508.iml @@ -3,6 +3,7 @@ + diff --git a/Cargo.lock b/Cargo.lock index baa1c2e..48d8d5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,5 +3,5 @@ version = 3 [[package]] -name = "tp-individual-taller-9508" +name = "tp_individual_taller_9508" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a51d521..662d1d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tp-individual-taller-9508" +name = "tp_individual_taller_9508" version = "0.1.0" edition = "2021" diff --git a/output.csv b/output.csv index a9eed29..8f03cbd 100644 --- a/output.csv +++ b/output.csv @@ -1,120 +1,92 @@ bbbbbbbbbbb: Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas -Consulta sql: UPDATE clientes SET nombre='pepito' , apellido='pepe' WHERE id > 1 AND NOT nombre = 'Ana'; +Consulta sql: UPDATE clientes SET nombre='pepe', apellido='el pepe' WHERE (nombre='Ana' AND id = '2'); Ejecutar consulta en la tabla: "clientes" Columnas a actualizar: ["nombre", "apellido"] -Valores a asignar: ["pepito", "pepe"] -Restricciones: "id > 1 AND NOT nombre = 'Ana'" -Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] -token: id>1 -token: AND -token: NOT +Valores a asignar: ["pepe", "el pepe"] +Restricciones: "(nombre='Ana' AND id = '2')" +Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +token: ( token: nombre='Ana' -tokenn: "id>1" +token: AND +token: id='2' +token: ) tokenn: "nombre='Ana'" -tokenn: "NOT" -token en evaluate postfix "NOT" -left: "nombre='Ana'" -right: "nombre='Ana'" -Evaluando condición: "nombre='Ana'" +tokenn: "id='2'" tokenn: "AND" token en evaluate postfix "AND" -left: "true" -right: "true" -Evaluando condición: "id>1" -Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] -token: id>1 -token: AND -token: NOT +left: "id='2'" +right: "id='2'" +Evaluando condición: "nombre='Ana'" +Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +token: ( token: nombre='Ana' -tokenn: "id>1" +token: AND +token: id='2' +token: ) tokenn: "nombre='Ana'" -tokenn: "NOT" -token en evaluate postfix "NOT" -left: "nombre='Ana'" -right: "nombre='Ana'" -Evaluando condición: "nombre='Ana'" +tokenn: "id='2'" tokenn: "AND" token en evaluate postfix "AND" -left: "false" -right: "false" -Evaluando condición: "id>1" -Evaluando condición: "false" -Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] -token: id>1 -token: AND -token: NOT +left: "id='2'" +right: "id='2'" +Evaluando condición: "nombre='Ana'" +Evaluando condición: "id='2'" +Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +token: ( token: nombre='Ana' -tokenn: "id>1" +token: AND +token: id='2' +token: ) tokenn: "nombre='Ana'" -tokenn: "NOT" -token en evaluate postfix "NOT" -left: "nombre='Ana'" -right: "nombre='Ana'" -Evaluando condición: "nombre='Ana'" +tokenn: "id='2'" tokenn: "AND" token en evaluate postfix "AND" -left: "true" -right: "true" -Evaluando condición: "id>1" -Evaluando condición: "true" -Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] -token: id>1 -token: AND -token: NOT +left: "id='2'" +right: "id='2'" +Evaluando condición: "nombre='Ana'" +Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +token: ( token: nombre='Ana' -tokenn: "id>1" +token: AND +token: id='2' +token: ) tokenn: "nombre='Ana'" -tokenn: "NOT" -token en evaluate postfix "NOT" -left: "nombre='Ana'" -right: "nombre='Ana'" -Evaluando condición: "nombre='Ana'" +tokenn: "id='2'" tokenn: "AND" token en evaluate postfix "AND" -left: "true" -right: "true" -Evaluando condición: "id>1" -Evaluando condición: "true" -Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] -token: id>1 -token: AND -token: NOT +left: "id='2'" +right: "id='2'" +Evaluando condición: "nombre='Ana'" +Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +token: ( token: nombre='Ana' -tokenn: "id>1" +token: AND +token: id='2' +token: ) tokenn: "nombre='Ana'" -tokenn: "NOT" -token en evaluate postfix "NOT" -left: "nombre='Ana'" -right: "nombre='Ana'" -Evaluando condición: "nombre='Ana'" +tokenn: "id='2'" tokenn: "AND" token en evaluate postfix "AND" -left: "true" -right: "true" -Evaluando condición: "id>1" -Evaluando condición: "true" -Final tokens: ["id>1", "AND", "NOT", "nombre='Ana'"] -token: id>1 -token: AND -token: NOT +left: "id='2'" +right: "id='2'" +Evaluando condición: "nombre='Ana'" +Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +token: ( token: nombre='Ana' -tokenn: "id>1" +token: AND +token: id='2' +token: ) tokenn: "nombre='Ana'" -tokenn: "NOT" -token en evaluate postfix "NOT" -left: "nombre='Ana'" -right: "nombre='Ana'" -Evaluando condición: "nombre='Ana'" +tokenn: "id='2'" tokenn: "AND" token en evaluate postfix "AND" -left: "true" -right: "true" -Evaluando condición: "id>1" -Evaluando condición: "true" +left: "id='2'" +right: "id='2'" +Evaluando condición: "nombre='Ana'" 1, Juan, Pérez, juan.perez@email.com -2, Ana, López, ana.lopez@email.com -3, pepito, pepe, carlos.gomez@email.com -4, pepito, pepe, maria.rodriguez@email.com -5, pepito, pepe, jose.lopez@email.com -6, pepito, pepe, laura.fernandez@email.com +2, pepe, el pepe, ana.lopez@email.com +3, Carlos, Gómez, carlos.gomez@email.com +4, María, Rodríguez, maria.rodriguez@email.com +5, José, López, jose.lopez@email.com +6, Laura, Fernández, laura.fernandez@email.com diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..89ad136 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,7 @@ +pub mod comandos; +pub mod delete; +pub mod errores; +pub mod insert; +pub mod parser; +pub mod select; +pub mod update; diff --git a/tablas/clientes.csv b/tablas/clientes.csv index 9bf2941..85310a6 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -1,7 +1,7 @@ id,nombre,apellido,email 1,Juan,Pérez,juan.perez@email.com 2,Ana,López,ana.lopez@email.com -3,pepito,pepe,carlos.gomez@email.com -4,pepito,pepe,maria.rodriguez@email.com -5,pepito,pepe,jose.lopez@email.com -6,pepito,pepe,laura.fernandez@email.com +3,Carlos,Gómez,carlos.gomez@email.com +4,María,Rodríguez,maria.rodriguez@email.com +5,José,López,jose.lopez@email.com +6,Laura,Fernández,laura.fernandez@email.com diff --git a/tests/delete_test.rs b/tests/delete_test.rs new file mode 100644 index 0000000..e9683c1 --- /dev/null +++ b/tests/delete_test.rs @@ -0,0 +1,80 @@ +extern crate tp_individual_taller_9508; + +use std::fs; +use std::fs::File; +use std::io::Write; +use tp_individual_taller_9508::comandos::Comando; +use tp_individual_taller_9508::delete::Delete; + +#[test] +fn test_comando_delete_con_and_not() { + let ruta_carpeta = "./test_data"; + let ruta_archivo = format!("{}/clientes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let mut file = File::create(&ruta_archivo).unwrap(); + writeln!(file, "id,nombre,apellido,email").unwrap(); + writeln!(file, "1,Juan,Pérez,juan.perez@email.com").unwrap(); + writeln!(file, "2,Ana,López,ana.lopez@email.com").unwrap(); + writeln!(file, "3,Carlos,Gómez,carlos.gomez@email.com").unwrap(); + writeln!(file, "4,María,Rodríguez,maria.rodriguez@email.com").unwrap(); + writeln!(file, "5,José,López,jose.lopez@email.com").unwrap(); + writeln!(file, "6,Laura,Fernández,laura.fernandez@email.com").unwrap(); + + let comando_delete = Comando::Delete(Delete { + tabla: "clientes".to_string(), + restricciones: Some("id > 1 AND NOT nombre = 'Ana'".to_string()), + }); + + let resultado = comando_delete.ejecutar(ruta_carpeta); + + assert!(resultado.is_ok()); + + let contenido_actualizado = fs::read_to_string(&ruta_archivo).unwrap(); + let esperado = "id,nombre,apellido,email\n1,Juan,Pérez,juan.perez@email.com\n2,Ana,López,ana.lopez@email.com\n"; + assert_eq!(contenido_actualizado, esperado); + + fs::remove_file(&ruta_archivo).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} +#[test] +fn test_comando_delete_parentesis_or_and_or() { + let ruta_carpeta = "./test_data_2"; + let ruta_archivo = format!("{}/clientes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let mut file = File::create(&ruta_archivo).unwrap(); + writeln!(file, "id,nombre,apellido,email").unwrap(); + writeln!(file, "1,Juan,Pérez,juan.perez@email.com").unwrap(); + writeln!(file, "2,Ana,López,ana.lopez@email.com").unwrap(); + writeln!(file, "3,Carlos,Gómez,carlos.gomez@email.com").unwrap(); + writeln!(file, "4,María,Rodríguez,maria.rodriguez@email.com").unwrap(); + writeln!(file, "5,José,López,jose.lopez@email.com").unwrap(); + writeln!(file, "6,Laura,Fernández,laura.fernandez@email.com").unwrap(); + + let comando_delete = Comando::Delete(Delete { + tabla: "clientes".to_string(), + restricciones: Some( + "DELETE FROM clientes WHERE (apellido='López' OR id=2) AND (nombre='Ana' OR id = 5)" + .to_string(), + ), + }); + + let resultado = comando_delete.ejecutar(ruta_carpeta); + + assert!(resultado.is_ok()); + + let contenido_actualizado = fs::read_to_string(&ruta_archivo).unwrap(); + let esperado = r#"id,nombre,apellido,email +1,Juan,Pérez,juan.perez@email.com +3,Carlos,Gómez,carlos.gomez@email.com +4,María,Rodríguez,maria.rodriguez@email.com +6,Laura,Fernández,laura.fernandez@email.com +"#; + assert_eq!(contenido_actualizado, esperado); + + fs::remove_file(&ruta_archivo).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} From b4b81009db1b465d5ef08e4575a84b2902e9a9b9 Mon Sep 17 00:00:00 2001 From: German Douce Date: Fri, 6 Sep 2024 00:11:47 -0300 Subject: [PATCH 26/54] agrego logica correcta del where al select --- output.csv | 266 ++++++++++++++++++++++++++++++---------- src/select.rs | 293 ++++++++++++++++++++++++++++++++++++--------- tablas/ordenes.csv | 1 - 3 files changed, 437 insertions(+), 123 deletions(-) diff --git a/output.csv b/output.csv index 8f03cbd..49bc198 100644 --- a/output.csv +++ b/output.csv @@ -1,92 +1,232 @@ -bbbbbbbbbbb: Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas -Consulta sql: UPDATE clientes SET nombre='pepe', apellido='el pepe' WHERE (nombre='Ana' AND id = '2'); -Ejecutar consulta en la tabla: "clientes" -Columnas a actualizar: ["nombre", "apellido"] -Valores a asignar: ["pepe", "el pepe"] -Restricciones: "(nombre='Ana' AND id = '2')" -Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +Consulta sql: SELECT id, producto, id_cliente +FROM ordenes +WHERE (cantidad > 1 OR id=101) AND producto=Laptop ; +Ejecutar consulta en la tabla: "ordenes" +Columnas a seleccionar: ["id", "producto", "id_cliente"] +Restricciones: "(cantidad > 1 OR id=101) AND producto=Laptop" +Encabezados antes de agregar: ["id", "id_cliente", "producto", "cantidad"] +Encabezados select: ["id", "producto", "id_cliente"] +Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] token: ( -token: nombre='Ana' -token: AND -token: id='2' +token: cantidad>1 +token: OR +token: id=101 token: ) -tokenn: "nombre='Ana'" -tokenn: "id='2'" +token: AND +token: producto=Laptop +tokenn: "cantidad>1" +tokenn: "id=101" +tokenn: "OR" +token en evaluate postfix "OR" +left: "id=101" +right: "id=101" +Evaluando condición: "cantidad>1" +Evaluando condición: "id=101" +tokenn: "producto=Laptop" tokenn: "AND" token en evaluate postfix "AND" -left: "id='2'" -right: "id='2'" -Evaluando condición: "nombre='Ana'" -Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +left: "producto=Laptop" +right: "producto=Laptop" +Evaluando condición: "true" +Evaluando condición: "producto=Laptop" +Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] token: ( -token: nombre='Ana' -token: AND -token: id='2' +token: cantidad>1 +token: OR +token: id=101 token: ) -tokenn: "nombre='Ana'" -tokenn: "id='2'" +token: AND +token: producto=Laptop +tokenn: "cantidad>1" +tokenn: "id=101" +tokenn: "OR" +token en evaluate postfix "OR" +left: "id=101" +right: "id=101" +Evaluando condición: "cantidad>1" +Evaluando condición: "id=101" +tokenn: "producto=Laptop" tokenn: "AND" token en evaluate postfix "AND" -left: "id='2'" -right: "id='2'" -Evaluando condición: "nombre='Ana'" -Evaluando condición: "id='2'" -Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +left: "producto=Laptop" +right: "producto=Laptop" +Evaluando condición: "false" +Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] token: ( -token: nombre='Ana' +token: cantidad>1 +token: OR +token: id=101 +token: ) token: AND -token: id='2' +token: producto=Laptop +tokenn: "cantidad>1" +tokenn: "id=101" +tokenn: "OR" +token en evaluate postfix "OR" +left: "id=101" +right: "id=101" +Evaluando condición: "cantidad>1" +tokenn: "producto=Laptop" +tokenn: "AND" +token en evaluate postfix "AND" +left: "producto=Laptop" +right: "producto=Laptop" +Evaluando condición: "true" +Evaluando condición: "producto=Laptop" +Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] +token: ( +token: cantidad>1 +token: OR +token: id=101 token: ) -tokenn: "nombre='Ana'" -tokenn: "id='2'" +token: AND +token: producto=Laptop +tokenn: "cantidad>1" +tokenn: "id=101" +tokenn: "OR" +token en evaluate postfix "OR" +left: "id=101" +right: "id=101" +Evaluando condición: "cantidad>1" +Evaluando condición: "id=101" +tokenn: "producto=Laptop" tokenn: "AND" token en evaluate postfix "AND" -left: "id='2'" -right: "id='2'" -Evaluando condición: "nombre='Ana'" -Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +left: "producto=Laptop" +right: "producto=Laptop" +Evaluando condición: "false" +Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] token: ( -token: nombre='Ana' +token: cantidad>1 +token: OR +token: id=101 +token: ) token: AND -token: id='2' +token: producto=Laptop +tokenn: "cantidad>1" +tokenn: "id=101" +tokenn: "OR" +token en evaluate postfix "OR" +left: "id=101" +right: "id=101" +Evaluando condición: "cantidad>1" +tokenn: "producto=Laptop" +tokenn: "AND" +token en evaluate postfix "AND" +left: "producto=Laptop" +right: "producto=Laptop" +Evaluando condición: "true" +Evaluando condición: "producto=Laptop" +Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] +token: ( +token: cantidad>1 +token: OR +token: id=101 token: ) -tokenn: "nombre='Ana'" -tokenn: "id='2'" +token: AND +token: producto=Laptop +tokenn: "cantidad>1" +tokenn: "id=101" +tokenn: "OR" +token en evaluate postfix "OR" +left: "id=101" +right: "id=101" +Evaluando condición: "cantidad>1" +Evaluando condición: "id=101" +tokenn: "producto=Laptop" tokenn: "AND" token en evaluate postfix "AND" -left: "id='2'" -right: "id='2'" -Evaluando condición: "nombre='Ana'" -Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +left: "producto=Laptop" +right: "producto=Laptop" +Evaluando condición: "false" +Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] token: ( -token: nombre='Ana' +token: cantidad>1 +token: OR +token: id=101 +token: ) token: AND -token: id='2' +token: producto=Laptop +tokenn: "cantidad>1" +tokenn: "id=101" +tokenn: "OR" +token en evaluate postfix "OR" +left: "id=101" +right: "id=101" +Evaluando condición: "cantidad>1" +Evaluando condición: "id=101" +tokenn: "producto=Laptop" +tokenn: "AND" +token en evaluate postfix "AND" +left: "producto=Laptop" +right: "producto=Laptop" +Evaluando condición: "false" +Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] +token: ( +token: cantidad>1 +token: OR +token: id=101 token: ) -tokenn: "nombre='Ana'" -tokenn: "id='2'" +token: AND +token: producto=Laptop +tokenn: "cantidad>1" +tokenn: "id=101" +tokenn: "OR" +token en evaluate postfix "OR" +left: "id=101" +right: "id=101" +Evaluando condición: "cantidad>1" +Evaluando condición: "id=101" +tokenn: "producto=Laptop" tokenn: "AND" token en evaluate postfix "AND" -left: "id='2'" -right: "id='2'" -Evaluando condición: "nombre='Ana'" -Final tokens: ["(", "nombre='Ana'", "AND", "id='2'", ")"] +left: "producto=Laptop" +right: "producto=Laptop" +Evaluando condición: "false" +Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] token: ( -token: nombre='Ana' +token: cantidad>1 +token: OR +token: id=101 +token: ) token: AND -token: id='2' +token: producto=Laptop +tokenn: "cantidad>1" +tokenn: "id=101" +tokenn: "OR" +token en evaluate postfix "OR" +left: "id=101" +right: "id=101" +Evaluando condición: "cantidad>1" +Evaluando condición: "id=101" +tokenn: "producto=Laptop" +tokenn: "AND" +token en evaluate postfix "AND" +left: "producto=Laptop" +right: "producto=Laptop" +Evaluando condición: "false" +Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] +token: ( +token: cantidad>1 +token: OR +token: id=101 token: ) -tokenn: "nombre='Ana'" -tokenn: "id='2'" +token: AND +token: producto=Laptop +tokenn: "cantidad>1" +tokenn: "id=101" +tokenn: "OR" +token en evaluate postfix "OR" +left: "id=101" +right: "id=101" +Evaluando condición: "cantidad>1" +tokenn: "producto=Laptop" tokenn: "AND" token en evaluate postfix "AND" -left: "id='2'" -right: "id='2'" -Evaluando condición: "nombre='Ana'" -1, Juan, Pérez, juan.perez@email.com -2, pepe, el pepe, ana.lopez@email.com -3, Carlos, Gómez, carlos.gomez@email.com -4, María, Rodríguez, maria.rodriguez@email.com -5, José, López, jose.lopez@email.com -6, Laura, Fernández, laura.fernandez@email.com +left: "producto=Laptop" +right: "producto=Laptop" +Evaluando condición: "true" +Evaluando condición: "producto=Laptop" +id, producto, id_cliente +101, Laptop, 1 diff --git a/src/select.rs b/src/select.rs index 4f24379..e5768a7 100644 --- a/src/select.rs +++ b/src/select.rs @@ -1,6 +1,8 @@ use crate::errores::ParseError; use std::fs::File; use std::io::{BufRead, BufReader}; +use std::iter::Peekable; +use std::str::Chars; pub struct Select { pub columnas: Vec, @@ -44,7 +46,7 @@ impl Select { ruta_archivo ))); } - let vector_encabezados: Vec<&str> = linea_encabezados.trim_end().split(',').collect(); + let vector_encabezados: Vec = linea_encabezados.trim_end().split(',').map(|s| s.to_string()).collect(); let mut resultado = Vec::new(); @@ -63,7 +65,7 @@ impl Select { ruta_archivo, err )) })?; - let registro: Vec<&str> = line.split(',').collect(); + let registro: Vec = line.split(',').map(|s| s.to_string()).collect(); // Aplico restricciones (WHERE) if self.aplicar_restricciones(®istro, &vector_encabezados)? { @@ -72,10 +74,7 @@ impl Select { for col in &self.columnas { // Encuentro índice de la columna en los encabezados - match vector_encabezados - .iter() - .position(|&encabezado| encabezado == col.as_str()) - { + match vector_encabezados.iter().position(|encabezado| encabezado == col) { Some(index) => { // Obtener el valor de la columna, si existe if let Some(value) = registro.get(index) { @@ -98,9 +97,11 @@ impl Select { if let Some(ref ordenamiento) = self.ordenamiento { self.aplicar_ordenamiento(&mut resultado, ordenamiento, &encabezados_select)?; }; + Ok(resultado) } + fn aplicar_ordenamiento( &self, resultado: &mut Vec>, @@ -149,7 +150,7 @@ impl Select { fn agregar_encabezados( &self, - vector_encabezados: &[&str], + vector_encabezados: &Vec, resultado: &mut Vec>, encabezados_select: &mut Vec, ) { @@ -157,71 +158,248 @@ impl Select { for col in &self.columnas { if vector_encabezados .iter() - .any(|&encabezado| encabezado == col.as_str()) + .any(|encabezado| encabezado == col) { encabezados_select.push(col.to_string()); } else { encabezados_select.push(String::new()); } } - //nuevo vector para los encabezados y los agrego al resultado - let encabezados_nuevos = encabezados_select.to_vec(); + // Reemplaza o agrega los encabezados al resultado if resultado.is_empty() { - resultado.push(encabezados_nuevos); - } else { - resultado[0] = encabezados_nuevos; + resultado.push(Vec::with_capacity(encabezados_select.len())); + } + + // Limpia el primer vector en el resultado + resultado[0].clear(); + + // Copia los encabezados seleccionados al resultado + for encabezado in encabezados_select.iter() { + resultado[0].push(encabezado.to_string()); // Copia los encabezados sin usar `clone()` } } + pub fn aplicar_restricciones( &self, - registro: &[&str], - encabezados: &[&str], + registro: &[String], + encabezados: &[String], ) -> Result { - // Verificamos si hay restricciones - if let Some(ref restricciones) = self.restricciones { - // Separo las restricciones por operadores lógicos `AND` - let condiciones: Vec<&str> = restricciones.split(" AND ").collect(); - let mut resultado = true; - - for condicion in &condiciones { - // hay algun `OR` - if condicion.contains(" OR ") { - let or_condiciones: Vec<&str> = condicion.split(" OR ").collect(); - let mut or_resultado = false; - - // Evaluo cada condición `OR` - for or_condicion in &or_condiciones { - if self.aplicar_condicion(or_condicion.trim(), registro, encabezados)? { - or_resultado = true; - break; // Si se cumple una condición `OR`, no es necesario verificar las demás + if let Some(restricciones) = &self.restricciones { + let tokens = self.tokenizar(restricciones); + let postfix = self.infix_a_postfix(&tokens)?; + let resultado = self.evaluar_postfix(&postfix, registro, encabezados)?; + Ok(resultado) + } else { + Ok(true) + } + } + + fn sacar_espacios_alrededor_operadores(&self, restricciones: &str) -> String { + let mut restricciones_limpio = String::new(); + let mut chars = restricciones.chars().peekable(); + + while let Some(ch) = chars.next() { + match ch { + '=' | '<' | '>' => { + //Eliminamos espacios antes del operador + if let Some(last_char) = restricciones_limpio.chars().last() { + if last_char == ' ' { + restricciones_limpio.pop(); } } + //Agregamos el operador al resultado + restricciones_limpio.push(ch); - resultado = resultado && or_resultado; - } else { - // Evalo la condición `AND` - if !self.aplicar_condicion(condicion.trim(), registro, encabezados)? { - return Ok(false); + //Ignoramos cualquier espacio después del operador + while let Some(&next_ch) = chars.peek() { + if next_ch == ' ' { + chars.next(); + } else { + break; + } } } + _ => { + restricciones_limpio.push(ch); + } } + } - Ok(resultado) - } else { - // Si no hay restricciones - Ok(true) + restricciones_limpio + } + + //Tokeniza las restricciones + fn tokenizar(&self, restricciones: &str) -> Vec { + let restricciones_limpio = self.sacar_espacios_alrededor_operadores(restricciones); + let mut chars = restricciones_limpio.chars().peekable(); + + let mut tokens = Vec::new(); + let mut token_actual = String::new(); + + let keywords = ["AND", "OR", "NOT", "(", ")"]; + + Self::loop_tokenizar(&mut tokens, &mut token_actual, keywords, &mut chars); + + if !token_actual.is_empty() { + tokens.push(token_actual.trim().to_string()); + } + + tokens.retain(|token| !token.is_empty()); + + // Debug: ver el estado final de los tokens + println!("Final tokens: {:?}", tokens); + + tokens + } + + fn loop_tokenizar(tokens: &mut Vec, token_actual: &mut String, keywords: [&str; 5], chars: &mut Peekable) { + while let Some(ch) = chars.next() { + match ch { + ' ' if !token_actual.is_empty() => { + tokens.push(token_actual.trim().to_string()); + token_actual.clear(); + } + '(' | ')' => { + if !token_actual.is_empty() { + tokens.push(token_actual.trim().to_string()); + token_actual.clear(); + } + tokens.push(ch.to_string()); + } + _ => { + token_actual.push(ch); + let token_upper = token_actual.trim().to_uppercase(); + if keywords.contains(&token_upper.as_str()) { + tokens.push(token_upper); + token_actual.clear(); + } + } + } } } + //Convierte la notación infix (con paréntesis) a notación postfija (sin parentesis) + //infix: (A OR B) AND (C OR D) ====> postfix: A B OR C D OR AND + fn infix_a_postfix( + &self, + tokens: &[String] + ) -> Result, ParseError> { + let mut resultado = Vec::new(); + let mut operadores = Vec::new(); + + let precedencia = |op: &str| match op { + "NOT" => 3, + "AND" => 2, + "OR" => 1, + "(" => 0, + ")" => 0, + _ => -1, + }; + + for token in tokens.iter() { + println!("token: {}", token); + match token.as_str() { + "(" => operadores.push(token.to_string()), + ")" => { + while let Some(op) = operadores.pop() { + if op == "(" { + break; + } + resultado.push(op); + } + } + "AND" | "OR" | "NOT" => { + while let Some(op) = operadores.last() { + if precedencia(op) >= precedencia(token) { + // el operador en la cima de la pila se extrae de la pila + // y se coloca en el output antes de agregar el nuevo operador. + if let Some(op) = operadores.pop() { + resultado.push(op); //saco + } else { + return Err(ParseError::Error("restricciones invalidas".into())); + } + } else { + break; + } + } + operadores.push(token.to_string()); + } + _ => resultado.push(token.to_string()), + } + } + + while let Some(op) = operadores.pop() { + resultado.push(op); + } + + Ok(resultado) + } + + + //Evalúo la expresión en notación postfija + fn evaluar_postfix( + &self, + tokens: &Vec, + registro: &[String], + encabezados: &[String], + ) -> Result { + let mut stack = Vec::new(); + + for token in tokens.iter() { + println!("tokenn: {:?}",token); + match token.as_str() { + "AND" | "OR" | "NOT" => { + println!("token en evaluate postfix {:?}",token); + let derecha = stack.pop().ok_or(ParseError::Error("expresión invalida".into()))?; + let izquierda = if token != "NOT" { + stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? + } else { + String::new() + }; + println!("left: {:?}",&derecha); + println!("right: {:?}",&derecha); + let resultado = match token.as_str() { + "AND" => self.aplicar_condicion(&izquierda, registro, encabezados)? && self.aplicar_condicion(&derecha, registro, encabezados)?, + "OR" => self.aplicar_condicion(&izquierda, registro, encabezados)? || self.aplicar_condicion(&derecha, registro, encabezados)?, + "NOT" => !self.aplicar_condicion(&derecha, registro, encabezados)?, + _ => false, + }; + stack.push(resultado.to_string()); + } + _ => //Proceso la condición directa + if stack.is_empty() && tokens.len() == 1 { + // Si el primer token es la única condición, evalúo directamente + let result = self.aplicar_condicion(token, registro, encabezados)?; + return Ok(result); + } else { + // Para otros tokens, los mete al stack para ser procesados con operadores + stack.push(token.to_string()); + } + } + } + //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) + Ok(stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? == "true") + } + + + //Aplico condición simple (comparaciones > < =) fn aplicar_condicion( &self, condicion: &str, - registro: &[&str], - encabezados: &[&str], + registro: &[String], + encabezados: &[String], ) -> Result { - // Identifico el operador en la condición + println!("Evaluando condición: {:?}", condicion); + + if condicion == "true" { + return Ok(true); + } else if condicion == "false" { + return Ok(false); + } + + // Parseo la condición para encontrar operadores de comparación let (columna, operador, valor) = if let Some(pos) = condicion.find('=') { (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) } else if let Some(pos) = condicion.find('>') { @@ -229,30 +407,27 @@ impl Select { } else if let Some(pos) = condicion.find('<') { (&condicion[..pos].trim(), "<", &condicion[pos + 1..].trim()) } else { - return Err(ParseError::Error(format!( - "Error al hacer alguna operacion" - ))); + return Err(ParseError::Error( + "Operador no válido en la condición".to_string(), + )); }; let valor = valor.trim().trim_matches('\''); - // Encuentro el índice de la columna en los encabezados - if let Some(indice) = encabezados.iter().position(|&enc| enc == *columna) { - // Obtengo el valor del registro correspondiente a la columna - let valor_registro = registro[indice]; - - // Comparo el valor en el registro con el valor del operador + // Encuentro el índice de la columna, o devuelvo un error si no se encuentra + if let Some(indice) = encabezados.iter().position(|enc| enc == columna) { + let valor_registro = ®istro[indice]; let resultado = match operador { - "=" => valor_registro == valor, - ">" => valor_registro > valor, - "<" => valor_registro < valor, + "=" => valor_registro.as_str() == valor, + ">" => valor_registro.as_str() > valor, + "<" => valor_registro.as_str() < valor, _ => false, }; - return Ok(resultado); + Ok(resultado) + } else { + Err(ParseError::Error(format!("La Columna '{}' no esta en los encabezados", columna))) } - - Ok(false) } } diff --git a/tablas/ordenes.csv b/tablas/ordenes.csv index e8d32be..bf38946 100644 --- a/tablas/ordenes.csv +++ b/tablas/ordenes.csv @@ -9,4 +9,3 @@ id,id_cliente,producto,cantidad 108,4,Auriculares,1 109,5,Laptop,1 110,6,Teléfono,2 -111,6,Laptop,3 From 9c66d6587b23a4acf69feaf7ae56b4c586e5d149 Mon Sep 17 00:00:00 2001 From: German Douce Date: Fri, 6 Sep 2024 00:16:34 -0300 Subject: [PATCH 27/54] cargo fmt --- output.csv | 214 +++++--------------------------------------------- src/delete.rs | 83 ++++++++++---------- src/insert.rs | 3 +- src/select.rs | 76 +++++++++++------- src/update.rs | 57 +++++++++----- 5 files changed, 150 insertions(+), 283 deletions(-) diff --git a/output.csv b/output.csv index 49bc198..cba8c7a 100644 --- a/output.csv +++ b/output.csv @@ -1,232 +1,58 @@ Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas Consulta sql: SELECT id, producto, id_cliente FROM ordenes -WHERE (cantidad > 1 OR id=101) AND producto=Laptop ; +WHERE cantidad > 1 ORDER BY id DESC; Ejecutar consulta en la tabla: "ordenes" Columnas a seleccionar: ["id", "producto", "id_cliente"] -Restricciones: "(cantidad > 1 OR id=101) AND producto=Laptop" +Restricciones: "cantidad > 1" +Order by: "id DESC" Encabezados antes de agregar: ["id", "id_cliente", "producto", "cantidad"] Encabezados select: ["id", "producto", "id_cliente"] -Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] -token: ( +Final tokens: ["cantidad>1"] token: cantidad>1 -token: OR -token: id=101 -token: ) -token: AND -token: producto=Laptop tokenn: "cantidad>1" -tokenn: "id=101" -tokenn: "OR" -token en evaluate postfix "OR" -left: "id=101" -right: "id=101" Evaluando condición: "cantidad>1" -Evaluando condición: "id=101" -tokenn: "producto=Laptop" -tokenn: "AND" -token en evaluate postfix "AND" -left: "producto=Laptop" -right: "producto=Laptop" -Evaluando condición: "true" -Evaluando condición: "producto=Laptop" -Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] -token: ( +Final tokens: ["cantidad>1"] token: cantidad>1 -token: OR -token: id=101 -token: ) -token: AND -token: producto=Laptop tokenn: "cantidad>1" -tokenn: "id=101" -tokenn: "OR" -token en evaluate postfix "OR" -left: "id=101" -right: "id=101" Evaluando condición: "cantidad>1" -Evaluando condición: "id=101" -tokenn: "producto=Laptop" -tokenn: "AND" -token en evaluate postfix "AND" -left: "producto=Laptop" -right: "producto=Laptop" -Evaluando condición: "false" -Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] -token: ( +Final tokens: ["cantidad>1"] token: cantidad>1 -token: OR -token: id=101 -token: ) -token: AND -token: producto=Laptop tokenn: "cantidad>1" -tokenn: "id=101" -tokenn: "OR" -token en evaluate postfix "OR" -left: "id=101" -right: "id=101" Evaluando condición: "cantidad>1" -tokenn: "producto=Laptop" -tokenn: "AND" -token en evaluate postfix "AND" -left: "producto=Laptop" -right: "producto=Laptop" -Evaluando condición: "true" -Evaluando condición: "producto=Laptop" -Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] -token: ( +Final tokens: ["cantidad>1"] token: cantidad>1 -token: OR -token: id=101 -token: ) -token: AND -token: producto=Laptop tokenn: "cantidad>1" -tokenn: "id=101" -tokenn: "OR" -token en evaluate postfix "OR" -left: "id=101" -right: "id=101" Evaluando condición: "cantidad>1" -Evaluando condición: "id=101" -tokenn: "producto=Laptop" -tokenn: "AND" -token en evaluate postfix "AND" -left: "producto=Laptop" -right: "producto=Laptop" -Evaluando condición: "false" -Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] -token: ( +Final tokens: ["cantidad>1"] token: cantidad>1 -token: OR -token: id=101 -token: ) -token: AND -token: producto=Laptop tokenn: "cantidad>1" -tokenn: "id=101" -tokenn: "OR" -token en evaluate postfix "OR" -left: "id=101" -right: "id=101" Evaluando condición: "cantidad>1" -tokenn: "producto=Laptop" -tokenn: "AND" -token en evaluate postfix "AND" -left: "producto=Laptop" -right: "producto=Laptop" -Evaluando condición: "true" -Evaluando condición: "producto=Laptop" -Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] -token: ( +Final tokens: ["cantidad>1"] token: cantidad>1 -token: OR -token: id=101 -token: ) -token: AND -token: producto=Laptop tokenn: "cantidad>1" -tokenn: "id=101" -tokenn: "OR" -token en evaluate postfix "OR" -left: "id=101" -right: "id=101" Evaluando condición: "cantidad>1" -Evaluando condición: "id=101" -tokenn: "producto=Laptop" -tokenn: "AND" -token en evaluate postfix "AND" -left: "producto=Laptop" -right: "producto=Laptop" -Evaluando condición: "false" -Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] -token: ( +Final tokens: ["cantidad>1"] token: cantidad>1 -token: OR -token: id=101 -token: ) -token: AND -token: producto=Laptop tokenn: "cantidad>1" -tokenn: "id=101" -tokenn: "OR" -token en evaluate postfix "OR" -left: "id=101" -right: "id=101" Evaluando condición: "cantidad>1" -Evaluando condición: "id=101" -tokenn: "producto=Laptop" -tokenn: "AND" -token en evaluate postfix "AND" -left: "producto=Laptop" -right: "producto=Laptop" -Evaluando condición: "false" -Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] -token: ( +Final tokens: ["cantidad>1"] token: cantidad>1 -token: OR -token: id=101 -token: ) -token: AND -token: producto=Laptop tokenn: "cantidad>1" -tokenn: "id=101" -tokenn: "OR" -token en evaluate postfix "OR" -left: "id=101" -right: "id=101" Evaluando condición: "cantidad>1" -Evaluando condición: "id=101" -tokenn: "producto=Laptop" -tokenn: "AND" -token en evaluate postfix "AND" -left: "producto=Laptop" -right: "producto=Laptop" -Evaluando condición: "false" -Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] -token: ( +Final tokens: ["cantidad>1"] token: cantidad>1 -token: OR -token: id=101 -token: ) -token: AND -token: producto=Laptop tokenn: "cantidad>1" -tokenn: "id=101" -tokenn: "OR" -token en evaluate postfix "OR" -left: "id=101" -right: "id=101" Evaluando condición: "cantidad>1" -Evaluando condición: "id=101" -tokenn: "producto=Laptop" -tokenn: "AND" -token en evaluate postfix "AND" -left: "producto=Laptop" -right: "producto=Laptop" -Evaluando condición: "false" -Final tokens: ["(", "cantidad>1", "OR", "id=101", ")", "AND", "producto=Laptop"] -token: ( +Final tokens: ["cantidad>1"] token: cantidad>1 -token: OR -token: id=101 -token: ) -token: AND -token: producto=Laptop tokenn: "cantidad>1" -tokenn: "id=101" -tokenn: "OR" -token en evaluate postfix "OR" -left: "id=101" -right: "id=101" Evaluando condición: "cantidad>1" -tokenn: "producto=Laptop" -tokenn: "AND" -token en evaluate postfix "AND" -left: "producto=Laptop" -right: "producto=Laptop" -Evaluando condición: "true" -Evaluando condición: "producto=Laptop" +Estado inicial: +Ordenamiento: id DESC +Encabezados select: ["id", "producto", "id_cliente"] +Resultado antes del ordenamiento: [["id", "producto", "id_cliente"], ["102", "Teléfono", "2"], ["105", "Mouse", "4"], ["110", "Teléfono", "6"]] id, producto, id_cliente -101, Laptop, 1 +110, Teléfono, 6 +105, Mouse, 4 +102, Teléfono, 2 diff --git a/src/delete.rs b/src/delete.rs index 6694926..ba28d2a 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -17,10 +17,7 @@ impl Delete { let archivo = match File::open(&ruta_archivo) { Ok(file) => file, Err(err) => { - println!( - "Error al abrir el archivo de entrada: {}", - err - ); + println!("Error al abrir el archivo de entrada: {}", err); return Err(ParseError::Error(err.to_string())); } }; @@ -31,10 +28,7 @@ impl Delete { let mut archivo_temporal = match File::create(&ruta_temporal) { Ok(file) => file, Err(err) => { - println!( - "Error al crear el archivo temporal: {}", - err - ); + println!("Error al crear el archivo temporal: {}", err); return Err(ParseError::Error(err.to_string())); } }; @@ -80,26 +74,18 @@ impl Delete { let linea = match linea { Ok(l) => l, Err(err) => { - println!( - "Error al leer una línea del archivo: {}", - err - ); + println!("Error al leer una línea del archivo: {}", err); return Err(ParseError::Error(err.to_string())); } }; - let fila: Vec = linea.split(',') - .map(|s| s.trim().to_string()) - .collect(); + let fila: Vec = linea.split(',').map(|s| s.trim().to_string()).collect(); if self.restricciones.is_none() || !self.aplicar_restricciones(&fila, &encabezados)? { // Si no hay restricciones o si la fila no cumple con las restricciones, // la escribimos en el archivo temporal if let Err(err) = writeln!(archivo_temporal, "{}", linea) { - println!( - "Error al escribir en el archivo temporal: {}", - err - ); + println!("Error al escribir en el archivo temporal: {}", err); return Err(ParseError::Error(err.to_string())); } } else { @@ -120,7 +106,6 @@ impl Delete { Ok(filas_resultantes) } - pub fn aplicar_restricciones( &self, registro: &[String], @@ -172,7 +157,7 @@ impl Delete { //Tokeniza las restricciones fn tokenizar(&self, restricciones: &str) -> Vec { - let restricciones_limpio = self.sacar_espacios_alrededor_operadores(restricciones); + let restricciones_limpio = self.sacar_espacios_alrededor_operadores(restricciones); let mut chars = restricciones_limpio.chars().peekable(); let mut tokens = Vec::new(); @@ -194,7 +179,12 @@ impl Delete { tokens } - fn loop_tokenizar(tokens: &mut Vec, token_actual: &mut String, keywords: [&str; 5], chars: &mut Peekable) { + fn loop_tokenizar( + tokens: &mut Vec, + token_actual: &mut String, + keywords: [&str; 5], + chars: &mut Peekable, + ) { while let Some(ch) = chars.next() { match ch { ' ' if !token_actual.is_empty() => { @@ -222,10 +212,7 @@ impl Delete { //Convierte la notación infix (con paréntesis) a notación postfija (sin parentesis) //infix: (A OR B) AND (C OR D) ====> postfix: A B OR C D OR AND - fn infix_a_postfix( - &self, - tokens: &[String] - ) -> Result, ParseError> { + fn infix_a_postfix(&self, tokens: &[String]) -> Result, ParseError> { let mut resultado = Vec::new(); let mut operadores = Vec::new(); @@ -277,7 +264,6 @@ impl Delete { Ok(resultado) } - //Evalúo la expresión en notación postfija fn evaluar_postfix( &self, @@ -288,27 +274,39 @@ impl Delete { let mut stack = Vec::new(); for token in tokens.iter() { - println!("tokenn: {:?}",token); + println!("tokenn: {:?}", token); match token.as_str() { "AND" | "OR" | "NOT" => { - println!("token en evaluate postfix {:?}",token); - let derecha = stack.pop().ok_or(ParseError::Error("expresión invalida".into()))?; + println!("token en evaluate postfix {:?}", token); + let derecha = stack + .pop() + .ok_or(ParseError::Error("expresión invalida".into()))?; let izquierda = if token != "NOT" { - stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? + stack + .pop() + .ok_or(ParseError::Error("expresión invalida".into()))? } else { String::new() }; - println!("left: {:?}",&derecha); - println!("right: {:?}",&derecha); + println!("left: {:?}", &derecha); + println!("right: {:?}", &derecha); let resultado = match token.as_str() { - "AND" => self.aplicar_condicion(&izquierda, registro, encabezados)? && self.aplicar_condicion(&derecha, registro, encabezados)?, - "OR" => self.aplicar_condicion(&izquierda, registro, encabezados)? || self.aplicar_condicion(&derecha, registro, encabezados)?, + "AND" => { + self.aplicar_condicion(&izquierda, registro, encabezados)? + && self.aplicar_condicion(&derecha, registro, encabezados)? + } + "OR" => { + self.aplicar_condicion(&izquierda, registro, encabezados)? + || self.aplicar_condicion(&derecha, registro, encabezados)? + } "NOT" => !self.aplicar_condicion(&derecha, registro, encabezados)?, _ => false, }; stack.push(resultado.to_string()); } - _ => //Proceso la condición directa + _ => + //Proceso la condición directa + { if stack.is_empty() && tokens.len() == 1 { // Si el primer token es la única condición, evalúo directamente let result = self.aplicar_condicion(token, registro, encabezados)?; @@ -317,13 +315,16 @@ impl Delete { // Para otros tokens, los mete al stack para ser procesados con operadores stack.push(token.to_string()); } + } } } //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) - Ok(stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? == "true") + Ok(stack + .pop() + .ok_or(ParseError::Error("expresión invalida".into()))? + == "true") } - //Aplico condición simple (comparaciones > < =) fn aplicar_condicion( &self, @@ -366,8 +367,10 @@ impl Delete { Ok(resultado) } else { - Err(ParseError::Error(format!("La Columna '{}' no esta en los encabezados", columna))) + Err(ParseError::Error(format!( + "La Columna '{}' no esta en los encabezados", + columna + ))) } } - } diff --git a/src/insert.rs b/src/insert.rs index 25d016c..f9192d3 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -1,8 +1,7 @@ use crate::errores::ParseError; +use std::fs::{File, OpenOptions}; use std::io::{BufRead, BufReader, BufWriter, Write}; use std::path::Path; -use std::fs::{File, OpenOptions}; - pub struct Insert { pub tabla: String, diff --git a/src/select.rs b/src/select.rs index e5768a7..1db5481 100644 --- a/src/select.rs +++ b/src/select.rs @@ -46,7 +46,11 @@ impl Select { ruta_archivo ))); } - let vector_encabezados: Vec = linea_encabezados.trim_end().split(',').map(|s| s.to_string()).collect(); + let vector_encabezados: Vec = linea_encabezados + .trim_end() + .split(',') + .map(|s| s.to_string()) + .collect(); let mut resultado = Vec::new(); @@ -74,7 +78,10 @@ impl Select { for col in &self.columnas { // Encuentro índice de la columna en los encabezados - match vector_encabezados.iter().position(|encabezado| encabezado == col) { + match vector_encabezados + .iter() + .position(|encabezado| encabezado == col) + { Some(index) => { // Obtener el valor de la columna, si existe if let Some(value) = registro.get(index) { @@ -101,7 +108,6 @@ impl Select { Ok(resultado) } - fn aplicar_ordenamiento( &self, resultado: &mut Vec>, @@ -154,7 +160,6 @@ impl Select { resultado: &mut Vec>, encabezados_select: &mut Vec, ) { - // Genera los encabezados seleccionados for col in &self.columnas { if vector_encabezados .iter() @@ -166,21 +171,17 @@ impl Select { } } - // Reemplaza o agrega los encabezados al resultado if resultado.is_empty() { resultado.push(Vec::with_capacity(encabezados_select.len())); } - // Limpia el primer vector en el resultado resultado[0].clear(); - // Copia los encabezados seleccionados al resultado for encabezado in encabezados_select.iter() { - resultado[0].push(encabezado.to_string()); // Copia los encabezados sin usar `clone()` + resultado[0].push(encabezado.to_string()); } } - pub fn aplicar_restricciones( &self, registro: &[String], @@ -232,7 +233,7 @@ impl Select { //Tokeniza las restricciones fn tokenizar(&self, restricciones: &str) -> Vec { - let restricciones_limpio = self.sacar_espacios_alrededor_operadores(restricciones); + let restricciones_limpio = self.sacar_espacios_alrededor_operadores(restricciones); let mut chars = restricciones_limpio.chars().peekable(); let mut tokens = Vec::new(); @@ -254,7 +255,12 @@ impl Select { tokens } - fn loop_tokenizar(tokens: &mut Vec, token_actual: &mut String, keywords: [&str; 5], chars: &mut Peekable) { + fn loop_tokenizar( + tokens: &mut Vec, + token_actual: &mut String, + keywords: [&str; 5], + chars: &mut Peekable, + ) { while let Some(ch) = chars.next() { match ch { ' ' if !token_actual.is_empty() => { @@ -282,10 +288,7 @@ impl Select { //Convierte la notación infix (con paréntesis) a notación postfija (sin parentesis) //infix: (A OR B) AND (C OR D) ====> postfix: A B OR C D OR AND - fn infix_a_postfix( - &self, - tokens: &[String] - ) -> Result, ParseError> { + fn infix_a_postfix(&self, tokens: &[String]) -> Result, ParseError> { let mut resultado = Vec::new(); let mut operadores = Vec::new(); @@ -337,7 +340,6 @@ impl Select { Ok(resultado) } - //Evalúo la expresión en notación postfija fn evaluar_postfix( &self, @@ -348,27 +350,39 @@ impl Select { let mut stack = Vec::new(); for token in tokens.iter() { - println!("tokenn: {:?}",token); + println!("tokenn: {:?}", token); match token.as_str() { "AND" | "OR" | "NOT" => { - println!("token en evaluate postfix {:?}",token); - let derecha = stack.pop().ok_or(ParseError::Error("expresión invalida".into()))?; + println!("token en evaluate postfix {:?}", token); + let derecha = stack + .pop() + .ok_or(ParseError::Error("expresión invalida".into()))?; let izquierda = if token != "NOT" { - stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? + stack + .pop() + .ok_or(ParseError::Error("expresión invalida".into()))? } else { String::new() }; - println!("left: {:?}",&derecha); - println!("right: {:?}",&derecha); + println!("left: {:?}", &derecha); + println!("right: {:?}", &derecha); let resultado = match token.as_str() { - "AND" => self.aplicar_condicion(&izquierda, registro, encabezados)? && self.aplicar_condicion(&derecha, registro, encabezados)?, - "OR" => self.aplicar_condicion(&izquierda, registro, encabezados)? || self.aplicar_condicion(&derecha, registro, encabezados)?, + "AND" => { + self.aplicar_condicion(&izquierda, registro, encabezados)? + && self.aplicar_condicion(&derecha, registro, encabezados)? + } + "OR" => { + self.aplicar_condicion(&izquierda, registro, encabezados)? + || self.aplicar_condicion(&derecha, registro, encabezados)? + } "NOT" => !self.aplicar_condicion(&derecha, registro, encabezados)?, _ => false, }; stack.push(resultado.to_string()); } - _ => //Proceso la condición directa + _ => + //Proceso la condición directa + { if stack.is_empty() && tokens.len() == 1 { // Si el primer token es la única condición, evalúo directamente let result = self.aplicar_condicion(token, registro, encabezados)?; @@ -377,13 +391,16 @@ impl Select { // Para otros tokens, los mete al stack para ser procesados con operadores stack.push(token.to_string()); } + } } } //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) - Ok(stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? == "true") + Ok(stack + .pop() + .ok_or(ParseError::Error("expresión invalida".into()))? + == "true") } - //Aplico condición simple (comparaciones > < =) fn aplicar_condicion( &self, @@ -426,7 +443,10 @@ impl Select { Ok(resultado) } else { - Err(ParseError::Error(format!("La Columna '{}' no esta en los encabezados", columna))) + Err(ParseError::Error(format!( + "La Columna '{}' no esta en los encabezados", + columna + ))) } } } diff --git a/src/update.rs b/src/update.rs index 3902bbd..caef962 100644 --- a/src/update.rs +++ b/src/update.rs @@ -201,7 +201,7 @@ impl Update { //Tokeniza las restricciones fn tokenizar(&self, restricciones: &str) -> Vec { - let restricciones_limpio = self.sacar_espacios_alrededor_operadores(restricciones); + let restricciones_limpio = self.sacar_espacios_alrededor_operadores(restricciones); let mut chars = restricciones_limpio.chars().peekable(); let mut tokens = Vec::new(); @@ -223,7 +223,12 @@ impl Update { tokens } - fn loop_tokenizar(tokens: &mut Vec, token_actual: &mut String, keywords: [&str; 5], chars: &mut Peekable) { + fn loop_tokenizar( + tokens: &mut Vec, + token_actual: &mut String, + keywords: [&str; 5], + chars: &mut Peekable, + ) { while let Some(ch) = chars.next() { match ch { ' ' if !token_actual.is_empty() => { @@ -251,10 +256,7 @@ impl Update { //Convierte la notación infix (con paréntesis) a notación postfija (sin parentesis) //infix: (A OR B) AND (C OR D) ====> postfix: A B OR C D OR AND - fn infix_a_postfix( - &self, - tokens: &[String] - ) -> Result, ParseError> { + fn infix_a_postfix(&self, tokens: &[String]) -> Result, ParseError> { let mut resultado = Vec::new(); let mut operadores = Vec::new(); @@ -306,7 +308,6 @@ impl Update { Ok(resultado) } - //Evalúo la expresión en notación postfija fn evaluar_postfix( &self, @@ -317,27 +318,39 @@ impl Update { let mut stack = Vec::new(); for token in tokens.iter() { - println!("tokenn: {:?}",token); + println!("tokenn: {:?}", token); match token.as_str() { "AND" | "OR" | "NOT" => { - println!("token en evaluate postfix {:?}",token); - let derecha = stack.pop().ok_or(ParseError::Error("expresión invalida".into()))?; + println!("token en evaluate postfix {:?}", token); + let derecha = stack + .pop() + .ok_or(ParseError::Error("expresión invalida".into()))?; let izquierda = if token != "NOT" { - stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? + stack + .pop() + .ok_or(ParseError::Error("expresión invalida".into()))? } else { String::new() }; - println!("left: {:?}",&derecha); - println!("right: {:?}",&derecha); + println!("left: {:?}", &derecha); + println!("right: {:?}", &derecha); let resultado = match token.as_str() { - "AND" => self.aplicar_condicion(&izquierda, registro, encabezados)? && self.aplicar_condicion(&derecha, registro, encabezados)?, - "OR" => self.aplicar_condicion(&izquierda, registro, encabezados)? || self.aplicar_condicion(&derecha, registro, encabezados)?, + "AND" => { + self.aplicar_condicion(&izquierda, registro, encabezados)? + && self.aplicar_condicion(&derecha, registro, encabezados)? + } + "OR" => { + self.aplicar_condicion(&izquierda, registro, encabezados)? + || self.aplicar_condicion(&derecha, registro, encabezados)? + } "NOT" => !self.aplicar_condicion(&derecha, registro, encabezados)?, _ => false, }; stack.push(resultado.to_string()); } - _ => //Proceso la condición directa + _ => + //Proceso la condición directa + { if stack.is_empty() && tokens.len() == 1 { // Si el primer token es la única condición, evalúo directamente let result = self.aplicar_condicion(token, registro, encabezados)?; @@ -346,13 +359,16 @@ impl Update { // Para otros tokens, los mete al stack para ser procesados con operadores stack.push(token.to_string()); } + } } } //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) - Ok(stack.pop().ok_or(ParseError::Error("expresión invalida".into()))? == "true") + Ok(stack + .pop() + .ok_or(ParseError::Error("expresión invalida".into()))? + == "true") } - //Aplico condición simple (comparaciones > < =) fn aplicar_condicion( &self, @@ -395,7 +411,10 @@ impl Update { Ok(resultado) } else { - Err(ParseError::Error(format!("La Columna '{}' no esta en los encabezados", columna))) + Err(ParseError::Error(format!( + "La Columna '{}' no esta en los encabezados", + columna + ))) } } } From b3c9dd380a3b529b37fad028f122392138fd304e Mon Sep 17 00:00:00 2001 From: German Douce Date: Fri, 6 Sep 2024 01:11:01 -0300 Subject: [PATCH 28/54] agrego soporte para SELECT * FROM y levanto error si se selecciona una col que no existe --- output.csv | 60 +++++++++------------------------------------------ src/select.rs | 34 +++++++++++++++-------------- 2 files changed, 28 insertions(+), 66 deletions(-) diff --git a/output.csv b/output.csv index cba8c7a..95c1e04 100644 --- a/output.csv +++ b/output.csv @@ -1,58 +1,18 @@ Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas Consulta sql: SELECT id, producto, id_cliente -FROM ordenes -WHERE cantidad > 1 ORDER BY id DESC; +FROM ordenes Ejecutar consulta en la tabla: "ordenes" Columnas a seleccionar: ["id", "producto", "id_cliente"] -Restricciones: "cantidad > 1" -Order by: "id DESC" Encabezados antes de agregar: ["id", "id_cliente", "producto", "cantidad"] Encabezados select: ["id", "producto", "id_cliente"] -Final tokens: ["cantidad>1"] -token: cantidad>1 -tokenn: "cantidad>1" -Evaluando condición: "cantidad>1" -Final tokens: ["cantidad>1"] -token: cantidad>1 -tokenn: "cantidad>1" -Evaluando condición: "cantidad>1" -Final tokens: ["cantidad>1"] -token: cantidad>1 -tokenn: "cantidad>1" -Evaluando condición: "cantidad>1" -Final tokens: ["cantidad>1"] -token: cantidad>1 -tokenn: "cantidad>1" -Evaluando condición: "cantidad>1" -Final tokens: ["cantidad>1"] -token: cantidad>1 -tokenn: "cantidad>1" -Evaluando condición: "cantidad>1" -Final tokens: ["cantidad>1"] -token: cantidad>1 -tokenn: "cantidad>1" -Evaluando condición: "cantidad>1" -Final tokens: ["cantidad>1"] -token: cantidad>1 -tokenn: "cantidad>1" -Evaluando condición: "cantidad>1" -Final tokens: ["cantidad>1"] -token: cantidad>1 -tokenn: "cantidad>1" -Evaluando condición: "cantidad>1" -Final tokens: ["cantidad>1"] -token: cantidad>1 -tokenn: "cantidad>1" -Evaluando condición: "cantidad>1" -Final tokens: ["cantidad>1"] -token: cantidad>1 -tokenn: "cantidad>1" -Evaluando condición: "cantidad>1" -Estado inicial: -Ordenamiento: id DESC -Encabezados select: ["id", "producto", "id_cliente"] -Resultado antes del ordenamiento: [["id", "producto", "id_cliente"], ["102", "Teléfono", "2"], ["105", "Mouse", "4"], ["110", "Teléfono", "6"]] id, producto, id_cliente -110, Teléfono, 6 -105, Mouse, 4 +101, Laptop, 1 +103, Monitor, 1 102, Teléfono, 2 +104, Teclado, 3 +105, Mouse, 4 +106, Impresora, 5 +107, Altavoces, 6 +108, Auriculares, 4 +109, Laptop, 5 +110, Teléfono, 6 diff --git a/src/select.rs b/src/select.rs index 1db5481..0d7ee18 100644 --- a/src/select.rs +++ b/src/select.rs @@ -57,7 +57,8 @@ impl Select { println!("Encabezados antes de agregar: {:?}", vector_encabezados); let mut encabezados_select = Vec::new(); - self.agregar_encabezados(&vector_encabezados, &mut resultado, &mut encabezados_select); + + self.agregar_encabezados(&vector_encabezados, &mut resultado, &mut encabezados_select)?; println!("Encabezados select: {:?}", encabezados_select); @@ -76,7 +77,7 @@ impl Select { // Crear un vector para las columnas seleccionadas let mut columnas_select = Vec::new(); - for col in &self.columnas { + for col in &encabezados_select { // Encuentro índice de la columna en los encabezados match vector_encabezados .iter() @@ -159,27 +160,28 @@ impl Select { vector_encabezados: &Vec, resultado: &mut Vec>, encabezados_select: &mut Vec, - ) { - for col in &self.columnas { - if vector_encabezados - .iter() - .any(|encabezado| encabezado == col) - { - encabezados_select.push(col.to_string()); - } else { - encabezados_select.push(String::new()); + ) -> Result<(), ParseError> { + if self.columnas.len() == 1 && self.columnas[0] == "*" { + // Selecciono todas si es * + encabezados_select.extend(vector_encabezados.iter().map(|s| s.as_str().to_string())); + } else { + encabezados_select.clear(); + for col in &self.columnas { + if let Some(encabezado) = vector_encabezados.iter().find(|&encabezado| encabezado == col) { + encabezados_select.push(encabezado.to_string()); + } else { + return Err(ParseError::Error(format!("Columna '{}' no encontrada", col))); + } } } - if resultado.is_empty() { - resultado.push(Vec::with_capacity(encabezados_select.len())); - } - - resultado[0].clear(); + resultado.push(Vec::with_capacity(encabezados_select.len())); for encabezado in encabezados_select.iter() { resultado[0].push(encabezado.to_string()); } + + Ok(()) } pub fn aplicar_restricciones( From 340a2e49b78597f5680d5bf09953b04f2c0a586d Mon Sep 17 00:00:00 2001 From: German Douce Date: Fri, 6 Sep 2024 20:04:51 -0300 Subject: [PATCH 29/54] corrijo parse_select paar que soporte que no haya WHERE pero si ORDER BY --- output.csv | 34 +++++++++++++------------- src/parser.rs | 66 +++++++++++++++++++++------------------------------ 2 files changed, 44 insertions(+), 56 deletions(-) diff --git a/output.csv b/output.csv index 95c1e04..25bc3a2 100644 --- a/output.csv +++ b/output.csv @@ -1,18 +1,18 @@ Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas -Consulta sql: SELECT id, producto, id_cliente -FROM ordenes -Ejecutar consulta en la tabla: "ordenes" -Columnas a seleccionar: ["id", "producto", "id_cliente"] -Encabezados antes de agregar: ["id", "id_cliente", "producto", "cantidad"] -Encabezados select: ["id", "producto", "id_cliente"] -id, producto, id_cliente -101, Laptop, 1 -103, Monitor, 1 -102, Teléfono, 2 -104, Teclado, 3 -105, Mouse, 4 -106, Impresora, 5 -107, Altavoces, 6 -108, Auriculares, 4 -109, Laptop, 5 -110, Teléfono, 6 +Consulta sql: SELECT * FROM clientes ORDER BY id DESC +Ejecutar consulta en la tabla: "clientes" +Columnas a seleccionar: ["*"] +Order by: "id DESC" +Encabezados antes de agregar: ["id", "nombre", "apellido", "email"] +Encabezados select: ["id", "nombre", "apellido", "email"] +Estado inicial: +Ordenamiento: id DESC +Encabezados select: ["id", "nombre", "apellido", "email"] +Resultado antes del ordenamiento: [["id", "nombre", "apellido", "email"], ["1", "Juan", "Pérez", "juan.perez@email.com"], ["2", "Ana", "López", "ana.lopez@email.com"], ["3", "Carlos", "Gómez", "carlos.gomez@email.com"], ["4", "María", "Rodríguez", "maria.rodriguez@email.com"], ["5", "José", "López", "jose.lopez@email.com"], ["6", "Laura", "Fernández", "laura.fernandez@email.com"]] +id, nombre, apellido, email +6, Laura, Fernández, laura.fernandez@email.com +5, José, López, jose.lopez@email.com +4, María, Rodríguez, maria.rodriguez@email.com +3, Carlos, Gómez, carlos.gomez@email.com +2, Ana, López, ana.lopez@email.com +1, Juan, Pérez, juan.perez@email.com diff --git a/src/parser.rs b/src/parser.rs index eb8d612..74cd075 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -60,64 +60,52 @@ pub fn parsear_consulta(consulta: &str) -> Result { fn parse_select(resto: &str) -> Result { let resto = resto.trim_end_matches(';').trim(); - // Separo la consulta en la parte de las columnas y la parte de las restricciones (si existen) - // resto = SELECT id, producto, id_cliente FROM ordenes WHERE cantidad > 1 ORDER BY email DESC; - // columnas_y_tabla = "SELECT id, producto, id_cliente FROM ordenes" - // restricciones = cantidad > 1 ORDER BY email DESC; - let (columnas_y_tabla, restricciones) = - if let Some((partes, restricciones)) = resto.split_once("WHERE") { - (partes.trim(), Some(restricciones.trim())) // elimina espacios en blanco x las dudas - } else { - (resto, None) // "SELECT id, producto, id_cliente FROM ordenes" (no habia where) - }; - - // Separo la parte de las columnas y la tabla usando "from" - // columnas_y_tabla = "SELECT id, producto, id_cliente FROM ordenes" - // columnas = "id, producto, id_cliente" - // tabla_y_ordenamiento = "ordenes" - let (columnas, tabla_y_ordenamiento) = - if let Some((columnas, tabla)) = columnas_y_tabla.split_once("FROM") { - (columnas.trim_start_matches("SELECT").trim(), tabla.trim()) //saco select - } else { - return Err(ParseError::ErrorDeSintaxis); - }; + //Separo la parte de las columnas y la tabla usando "FROM" + let (columnas, resto) = if let Some((columnas, resto)) = resto.split_once("FROM") { + (columnas.trim(), resto.trim()) + } else { + return Err(ParseError::ErrorDeSintaxis); + }; - // Convierto la parte de las columnas en un vector de strings - //columnas =["id", "producto", "id_cliente"] + //Convierto la parte de las columnas en un vector de strings let columnas: Vec = columnas .split(',') - .map(|columna| columna.trim().to_string()) // Limpia los espacios en blanco + .map(|columna| columna.trim().to_string()) .collect(); - // Separar las restricciones y el ordenamiento si existen - // restricciones = cantidad > 1 ORDER BY email DESC; - // restricciones = cantidad > 1 - // ordenamiento = email DESC - let (restricciones, ordenamiento) = if let Some(restricciones) = restricciones { - // Intento separar las restricciones y el ordenamiento usando "order by" - if let Some((restricciones, orden)) = restricciones.split_once("ORDER BY") { + //Separo la tabla y manejar posibles restricciones y ordenamiento + let (tabla, restricciones, ordenamiento) = if let Some((tabla, resto)) = resto.split_once("WHERE") { + // Si hay WHERE, manejamos restricciones y ordenamiento + let (restricciones, ordenamiento) = if let Some((restricciones, orden)) = resto.split_once("ORDER BY") { ( Some(restricciones.trim().to_string()), Some(orden.trim().to_string()), ) } else { - (Some(restricciones.to_string()), None) // No había "order by", solo restricciones - } + (Some(resto.trim().to_string()), None) + }; + (tabla.trim().to_string(), restricciones, ordenamiento) + } else if let Some((tabla, orden)) = resto.split_once("ORDER BY") { + // Si no hay WHERE pero sí ORDER BY + ( + tabla.trim().to_string(), + None, + Some(orden.trim().to_string()), + ) } else { - (None, None) // No había restricciones ni ordenamiento + // No hay ni WHERE ni ORDER BY + (resto.trim().to_string(), None, None) }; - let tabla = tabla_y_ordenamiento.trim().to_string(); - - // Devolver un struct Select con las columnas, restricciones, y ordenamiento encontrados Ok(Select { columnas, tabla, - restricciones: restricciones.map(String::from), - ordenamiento: ordenamiento.map(String::from), + restricciones, + ordenamiento, }) } + fn parse_update(resto: &str) -> Result { // saco ; y saltos de linea let resto = resto.trim_end_matches(';').trim(); From 10a6eb9cbe681f8b6547293e2d843ff60f20b4f6 Mon Sep 17 00:00:00 2001 From: German Douce Date: Fri, 6 Sep 2024 21:12:54 -0300 Subject: [PATCH 30/54] corrijo ordenamiento para que soporte ordeanr por varias columnas --- output.csv | 40 +++++++++++++++++----- src/select.rs | 82 ++++++++++++++++++++++++++++----------------- tablas/clientes.csv | 4 +-- 3 files changed, 86 insertions(+), 40 deletions(-) diff --git a/output.csv b/output.csv index 25bc3a2..23f9746 100644 --- a/output.csv +++ b/output.csv @@ -1,18 +1,42 @@ Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas -Consulta sql: SELECT * FROM clientes ORDER BY id DESC +Consulta sql: SELECT * FROM clientes WHERE id>1 ORDER BY apellido, id DESC Ejecutar consulta en la tabla: "clientes" Columnas a seleccionar: ["*"] -Order by: "id DESC" +Restricciones: "id>1" +Order by: "apellido, id DESC" Encabezados antes de agregar: ["id", "nombre", "apellido", "email"] Encabezados select: ["id", "nombre", "apellido", "email"] +Final tokens: ["id>1"] +token: id>1 +tokenn: "id>1" +Evaluando condición: "id>1" +Final tokens: ["id>1"] +token: id>1 +tokenn: "id>1" +Evaluando condición: "id>1" +Final tokens: ["id>1"] +token: id>1 +tokenn: "id>1" +Evaluando condición: "id>1" +Final tokens: ["id>1"] +token: id>1 +tokenn: "id>1" +Evaluando condición: "id>1" +Final tokens: ["id>1"] +token: id>1 +tokenn: "id>1" +Evaluando condición: "id>1" +Final tokens: ["id>1"] +token: id>1 +tokenn: "id>1" +Evaluando condición: "id>1" Estado inicial: -Ordenamiento: id DESC +Ordenamiento: apellido, id DESC Encabezados select: ["id", "nombre", "apellido", "email"] -Resultado antes del ordenamiento: [["id", "nombre", "apellido", "email"], ["1", "Juan", "Pérez", "juan.perez@email.com"], ["2", "Ana", "López", "ana.lopez@email.com"], ["3", "Carlos", "Gómez", "carlos.gomez@email.com"], ["4", "María", "Rodríguez", "maria.rodriguez@email.com"], ["5", "José", "López", "jose.lopez@email.com"], ["6", "Laura", "Fernández", "laura.fernandez@email.com"]] +Resultado antes del ordenamiento: [["id", "nombre", "apellido", "email"], ["2", "Ana", "López", "ana.lopez@email.com"], ["3", "Carlos", "Gómez", "carlos.gomez@email.com"], ["4", "María", "Rodríguez", "maria.rodriguez@email.com"], ["2", "José", "López", "jose.lopez@email.com"], ["6", "Laura", "López", "laura.fernandez@email.com"]] id, nombre, apellido, email -6, Laura, Fernández, laura.fernandez@email.com -5, José, López, jose.lopez@email.com -4, María, Rodríguez, maria.rodriguez@email.com 3, Carlos, Gómez, carlos.gomez@email.com +6, Laura, López, laura.fernandez@email.com 2, Ana, López, ana.lopez@email.com -1, Juan, Pérez, juan.perez@email.com +2, José, López, jose.lopez@email.com +4, María, Rodríguez, maria.rodriguez@email.com diff --git a/src/select.rs b/src/select.rs index 0d7ee18..f218d3d 100644 --- a/src/select.rs +++ b/src/select.rs @@ -120,39 +120,61 @@ impl Select { println!("Encabezados select: {:?}", encabezados_select); println!("Resultado antes del ordenamiento: {:?}", resultado); - // Encuentro el índice de la columna para el ordenamiento - let (columna, direccion) = if let Some((col, dir)) = ordenamiento.split_once(' ') { - (col.trim(), dir.trim().to_uppercase()) - } else { - (ordenamiento.trim(), "ASC".to_string()) - }; + //Parseo los criterios de ordenamiento + let criterios: Vec<(String, String)> = ordenamiento + .split(',') + .map(|criterio| { + let (col, dir) = if let Some((col, dir)) = criterio.trim().split_once(' ') { + (col.trim().to_string(), dir.trim().to_uppercase()) + } else { + (criterio.trim().to_string(), "ASC".to_string()) + }; + (col, dir) + }) + .collect(); - // Encuentro el índice en encabezados_select, si existe - if let Some(index) = encabezados_select + //Me Aseguro que todas las columnas de los criterios existen en encabezados_select + //y mapeo los indices + let indice_direccion_criterios: Vec<(usize, String)> = criterios .iter() - .position(|encabezado| encabezado == columna) - { - // Ordeno las filas menos la primera fila que es la que tiene los encabezaods - resultado[1..].sort_by(|a, b| { - // Obtiene los valores para la columna especificada, o cadena vacía si no está presente - let a_value = a.get(index).map_or("", |v| v.as_str()); - let b_value = b.get(index).map_or("", |v| v.as_str()); - - //comparo - let ord = a_value.cmp(b_value); - if direccion == "DESC" { - ord.reverse() - } else { - ord + .map(|(columna, direccion)| { + encabezados_select + .iter() + .position(|encabezado| encabezado == columna) + .ok_or_else(|| { + ParseError::Error(format!( + "La columna '{}' no existe en los encabezados", + columna + )) + }) + .map(|indice| (indice, direccion.to_string())) + }) + .collect::, ParseError>>()?; + + //Ordeno las filas usando los criterios + resultado[1..].sort_by(|fila_1, fila_2| { + for &(indice_columna, ref direccion) in &indice_direccion_criterios { + + let valor_1 = fila_1.get(indice_columna).map_or("", |v| v.as_str()); + let valor_2 = fila_2.get(indice_columna).map_or("", |v| v.as_str()); + + let resultado_comparacion = valor_1.cmp(valor_2); + + // Si los valores no son iguales, hago el ordenamiento + if resultado_comparacion != std::cmp::Ordering::Equal { + return if direccion == "DESC" { + resultado_comparacion.reverse() + } else { + resultado_comparacion + }; } - }); - Ok(()) - } else { - Err(ParseError::Error(format!( - "La columna '{}' no existe en los encabezados", - columna - ))) - } + } + // Si todos los criterios son iguales, no se cambia el orden + std::cmp::Ordering::Equal + }); + + + Ok(()) } fn agregar_encabezados( diff --git a/tablas/clientes.csv b/tablas/clientes.csv index 85310a6..19b476c 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -3,5 +3,5 @@ id,nombre,apellido,email 2,Ana,López,ana.lopez@email.com 3,Carlos,Gómez,carlos.gomez@email.com 4,María,Rodríguez,maria.rodriguez@email.com -5,José,López,jose.lopez@email.com -6,Laura,Fernández,laura.fernandez@email.com +2,José,López,jose.lopez@email.com +6,Laura,López,laura.fernandez@email.com From b3e2356e3b2c26ea9e003e1c331325d53a95ea9b Mon Sep 17 00:00:00 2001 From: German Douce Date: Fri, 6 Sep 2024 21:30:31 -0300 Subject: [PATCH 31/54] agrego operadores >= y <= que me habia olvidado --- output.csv | 118 +++++++++++++++++++++++++++++--------------- src/delete.rs | 12 +++-- src/select.rs | 12 +++-- src/update.rs | 10 +++- tablas/clientes.csv | 2 +- 5 files changed, 106 insertions(+), 48 deletions(-) diff --git a/output.csv b/output.csv index 23f9746..51dbebd 100644 --- a/output.csv +++ b/output.csv @@ -1,42 +1,82 @@ +bbbbbbbbbbb: Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas -Consulta sql: SELECT * FROM clientes WHERE id>1 ORDER BY apellido, id DESC +Consulta sql: UPDATE clientes SET nombre='aaaa' WHERE id <=3 AND nombre >= 'Ana' Ejecutar consulta en la tabla: "clientes" -Columnas a seleccionar: ["*"] -Restricciones: "id>1" -Order by: "apellido, id DESC" -Encabezados antes de agregar: ["id", "nombre", "apellido", "email"] -Encabezados select: ["id", "nombre", "apellido", "email"] -Final tokens: ["id>1"] -token: id>1 -tokenn: "id>1" -Evaluando condición: "id>1" -Final tokens: ["id>1"] -token: id>1 -tokenn: "id>1" -Evaluando condición: "id>1" -Final tokens: ["id>1"] -token: id>1 -tokenn: "id>1" -Evaluando condición: "id>1" -Final tokens: ["id>1"] -token: id>1 -tokenn: "id>1" -Evaluando condición: "id>1" -Final tokens: ["id>1"] -token: id>1 -tokenn: "id>1" -Evaluando condición: "id>1" -Final tokens: ["id>1"] -token: id>1 -tokenn: "id>1" -Evaluando condición: "id>1" -Estado inicial: -Ordenamiento: apellido, id DESC -Encabezados select: ["id", "nombre", "apellido", "email"] -Resultado antes del ordenamiento: [["id", "nombre", "apellido", "email"], ["2", "Ana", "López", "ana.lopez@email.com"], ["3", "Carlos", "Gómez", "carlos.gomez@email.com"], ["4", "María", "Rodríguez", "maria.rodriguez@email.com"], ["2", "José", "López", "jose.lopez@email.com"], ["6", "Laura", "López", "laura.fernandez@email.com"]] -id, nombre, apellido, email -3, Carlos, Gómez, carlos.gomez@email.com -6, Laura, López, laura.fernandez@email.com -2, Ana, López, ana.lopez@email.com -2, José, López, jose.lopez@email.com +Columnas a actualizar: ["nombre"] +Valores a asignar: ["aaaa"] +Restricciones: "id <=3 AND nombre >= 'Ana'" +Final tokens: ["id<=3", "AND", "nombre>='Ana'"] +token: id<=3 +token: AND +token: nombre>='Ana' +tokenn: "id<=3" +tokenn: "nombre>='Ana'" +tokenn: "AND" +token en evaluate postfix "AND" +left: "nombre>='Ana'" +right: "nombre>='Ana'" +Evaluando condición: "id<=3" +Evaluando condición: "nombre>='Ana'" +Final tokens: ["id<=3", "AND", "nombre>='Ana'"] +token: id<=3 +token: AND +token: nombre>='Ana' +tokenn: "id<=3" +tokenn: "nombre>='Ana'" +tokenn: "AND" +token en evaluate postfix "AND" +left: "nombre>='Ana'" +right: "nombre>='Ana'" +Evaluando condición: "id<=3" +Evaluando condición: "nombre>='Ana'" +Final tokens: ["id<=3", "AND", "nombre>='Ana'"] +token: id<=3 +token: AND +token: nombre>='Ana' +tokenn: "id<=3" +tokenn: "nombre>='Ana'" +tokenn: "AND" +token en evaluate postfix "AND" +left: "nombre>='Ana'" +right: "nombre>='Ana'" +Evaluando condición: "id<=3" +Evaluando condición: "nombre>='Ana'" +Final tokens: ["id<=3", "AND", "nombre>='Ana'"] +token: id<=3 +token: AND +token: nombre>='Ana' +tokenn: "id<=3" +tokenn: "nombre>='Ana'" +tokenn: "AND" +token en evaluate postfix "AND" +left: "nombre>='Ana'" +right: "nombre>='Ana'" +Evaluando condición: "id<=3" +Final tokens: ["id<=3", "AND", "nombre>='Ana'"] +token: id<=3 +token: AND +token: nombre>='Ana' +tokenn: "id<=3" +tokenn: "nombre>='Ana'" +tokenn: "AND" +token en evaluate postfix "AND" +left: "nombre>='Ana'" +right: "nombre>='Ana'" +Evaluando condición: "id<=3" +Final tokens: ["id<=3", "AND", "nombre>='Ana'"] +token: id<=3 +token: AND +token: nombre>='Ana' +tokenn: "id<=3" +tokenn: "nombre>='Ana'" +tokenn: "AND" +token en evaluate postfix "AND" +left: "nombre>='Ana'" +right: "nombre>='Ana'" +Evaluando condición: "id<=3" +1, aaaa, Pérez, juan.perez@email.com +2, aaaa, López, ana.lopez@email.com +3, aaaa, Gómez, carlos.gomez@email.com 4, María, Rodríguez, maria.rodriguez@email.com +5, José, López, jose.lopez@email.com +6, Laura, López, laura.fernandez@email.com diff --git a/src/delete.rs b/src/delete.rs index ba28d2a..3838508 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -325,7 +325,7 @@ impl Delete { == "true") } - //Aplico condición simple (comparaciones > < =) + //Aplico condición simple (comparaciones > < = <= >=) fn aplicar_condicion( &self, condicion: &str, @@ -341,12 +341,16 @@ impl Delete { } // Parseo la condición para encontrar operadores de comparación - let (columna, operador, valor) = if let Some(pos) = condicion.find('=') { - (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) + let (columna, operador, valor) = if let Some(pos) = condicion.find(">=") { + (&condicion[..pos].trim(), ">=", &condicion[pos + 2..].trim()) + } else if let Some(pos) = condicion.find("<=") { + (&condicion[..pos].trim(), "<=", &condicion[pos + 2..].trim()) } else if let Some(pos) = condicion.find('>') { (&condicion[..pos].trim(), ">", &condicion[pos + 1..].trim()) } else if let Some(pos) = condicion.find('<') { (&condicion[..pos].trim(), "<", &condicion[pos + 1..].trim()) + } else if let Some(pos) = condicion.find('=') { + (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) } else { return Err(ParseError::Error( "Operador no válido en la condición".to_string(), @@ -362,6 +366,8 @@ impl Delete { "=" => valor_registro.as_str() == valor, ">" => valor_registro.as_str() > valor, "<" => valor_registro.as_str() < valor, + "<=" => valor_registro.as_str() <= valor, + ">=" => valor_registro.as_str() >= valor, _ => false, }; diff --git a/src/select.rs b/src/select.rs index f218d3d..a5f0621 100644 --- a/src/select.rs +++ b/src/select.rs @@ -425,7 +425,7 @@ impl Select { == "true") } - //Aplico condición simple (comparaciones > < =) + //Aplico condición simple (comparaciones > < = <= >=) fn aplicar_condicion( &self, condicion: &str, @@ -441,12 +441,16 @@ impl Select { } // Parseo la condición para encontrar operadores de comparación - let (columna, operador, valor) = if let Some(pos) = condicion.find('=') { - (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) + let (columna, operador, valor) = if let Some(pos) = condicion.find(">=") { + (&condicion[..pos].trim(), ">=", &condicion[pos + 2..].trim()) + } else if let Some(pos) = condicion.find("<=") { + (&condicion[..pos].trim(), "<=", &condicion[pos + 2..].trim()) } else if let Some(pos) = condicion.find('>') { (&condicion[..pos].trim(), ">", &condicion[pos + 1..].trim()) } else if let Some(pos) = condicion.find('<') { (&condicion[..pos].trim(), "<", &condicion[pos + 1..].trim()) + } else if let Some(pos) = condicion.find('=') { + (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) } else { return Err(ParseError::Error( "Operador no válido en la condición".to_string(), @@ -462,6 +466,8 @@ impl Select { "=" => valor_registro.as_str() == valor, ">" => valor_registro.as_str() > valor, "<" => valor_registro.as_str() < valor, + "<=" => valor_registro.as_str() <= valor, + ">=" => valor_registro.as_str() >= valor, _ => false, }; diff --git a/src/update.rs b/src/update.rs index caef962..4cee8b0 100644 --- a/src/update.rs +++ b/src/update.rs @@ -385,12 +385,16 @@ impl Update { } // Parseo la condición para encontrar operadores de comparación - let (columna, operador, valor) = if let Some(pos) = condicion.find('=') { - (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) + let (columna, operador, valor) = if let Some(pos) = condicion.find(">=") { + (&condicion[..pos].trim(), ">=", &condicion[pos + 2..].trim()) + } else if let Some(pos) = condicion.find("<=") { + (&condicion[..pos].trim(), "<=", &condicion[pos + 2..].trim()) } else if let Some(pos) = condicion.find('>') { (&condicion[..pos].trim(), ">", &condicion[pos + 1..].trim()) } else if let Some(pos) = condicion.find('<') { (&condicion[..pos].trim(), "<", &condicion[pos + 1..].trim()) + } else if let Some(pos) = condicion.find('=') { + (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) } else { return Err(ParseError::Error( "Operador no válido en la condición".to_string(), @@ -406,6 +410,8 @@ impl Update { "=" => valor_registro.as_str() == valor, ">" => valor_registro.as_str() > valor, "<" => valor_registro.as_str() < valor, + "<=" => valor_registro.as_str() <= valor, + ">=" => valor_registro.as_str() >= valor, _ => false, }; diff --git a/tablas/clientes.csv b/tablas/clientes.csv index 19b476c..ba85e77 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -3,5 +3,5 @@ id,nombre,apellido,email 2,Ana,López,ana.lopez@email.com 3,Carlos,Gómez,carlos.gomez@email.com 4,María,Rodríguez,maria.rodriguez@email.com -2,José,López,jose.lopez@email.com +5,José,López,jose.lopez@email.com 6,Laura,López,laura.fernandez@email.com From 7b43d07d0ed0c742bd6f622731a9e5012f8d86ce Mon Sep 17 00:00:00 2001 From: German Douce Date: Sat, 7 Sep 2024 00:17:49 -0300 Subject: [PATCH 32/54] agrego tests a a update y corrijo warns de clippy --- output.csv | 121 +++++++++++++++---------------------------- src/delete.rs | 4 +- src/lib.rs | 2 +- src/select.rs | 6 +-- src/update.rs | 4 +- tablas/clientes.csv | 2 +- tests/delete_test.rs | 3 +- tests/update_test.rs | 48 +++++++++++++++++ 8 files changed, 99 insertions(+), 91 deletions(-) create mode 100644 tests/update_test.rs diff --git a/output.csv b/output.csv index 51dbebd..c0a50cd 100644 --- a/output.csv +++ b/output.csv @@ -1,82 +1,43 @@ -bbbbbbbbbbb: Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas -Consulta sql: UPDATE clientes SET nombre='aaaa' WHERE id <=3 AND nombre >= 'Ana' +Consulta sql: SELECT id, nombre, email +FROM clientes +WHERE apellido = 'López' +ORDER BY email DESC; + Ejecutar consulta en la tabla: "clientes" -Columnas a actualizar: ["nombre"] -Valores a asignar: ["aaaa"] -Restricciones: "id <=3 AND nombre >= 'Ana'" -Final tokens: ["id<=3", "AND", "nombre>='Ana'"] -token: id<=3 -token: AND -token: nombre>='Ana' -tokenn: "id<=3" -tokenn: "nombre>='Ana'" -tokenn: "AND" -token en evaluate postfix "AND" -left: "nombre>='Ana'" -right: "nombre>='Ana'" -Evaluando condición: "id<=3" -Evaluando condición: "nombre>='Ana'" -Final tokens: ["id<=3", "AND", "nombre>='Ana'"] -token: id<=3 -token: AND -token: nombre>='Ana' -tokenn: "id<=3" -tokenn: "nombre>='Ana'" -tokenn: "AND" -token en evaluate postfix "AND" -left: "nombre>='Ana'" -right: "nombre>='Ana'" -Evaluando condición: "id<=3" -Evaluando condición: "nombre>='Ana'" -Final tokens: ["id<=3", "AND", "nombre>='Ana'"] -token: id<=3 -token: AND -token: nombre>='Ana' -tokenn: "id<=3" -tokenn: "nombre>='Ana'" -tokenn: "AND" -token en evaluate postfix "AND" -left: "nombre>='Ana'" -right: "nombre>='Ana'" -Evaluando condición: "id<=3" -Evaluando condición: "nombre>='Ana'" -Final tokens: ["id<=3", "AND", "nombre>='Ana'"] -token: id<=3 -token: AND -token: nombre>='Ana' -tokenn: "id<=3" -tokenn: "nombre>='Ana'" -tokenn: "AND" -token en evaluate postfix "AND" -left: "nombre>='Ana'" -right: "nombre>='Ana'" -Evaluando condición: "id<=3" -Final tokens: ["id<=3", "AND", "nombre>='Ana'"] -token: id<=3 -token: AND -token: nombre>='Ana' -tokenn: "id<=3" -tokenn: "nombre>='Ana'" -tokenn: "AND" -token en evaluate postfix "AND" -left: "nombre>='Ana'" -right: "nombre>='Ana'" -Evaluando condición: "id<=3" -Final tokens: ["id<=3", "AND", "nombre>='Ana'"] -token: id<=3 -token: AND -token: nombre>='Ana' -tokenn: "id<=3" -tokenn: "nombre>='Ana'" -tokenn: "AND" -token en evaluate postfix "AND" -left: "nombre>='Ana'" -right: "nombre>='Ana'" -Evaluando condición: "id<=3" -1, aaaa, Pérez, juan.perez@email.com -2, aaaa, López, ana.lopez@email.com -3, aaaa, Gómez, carlos.gomez@email.com -4, María, Rodríguez, maria.rodriguez@email.com -5, José, López, jose.lopez@email.com -6, Laura, López, laura.fernandez@email.com +Columnas a seleccionar: ["id", "nombre", "email"] +Restricciones: "apellido = 'López'" +Order by: "email DESC" +Encabezados antes de agregar: ["id", "nombre", "apellido", "email"] +Encabezados select: ["id", "nombre", "email"] +Final tokens: ["apellido='López'"] +token: apellido='López' +tokenn: "apellido='López'" +Evaluando condición: "apellido='López'" +Final tokens: ["apellido='López'"] +token: apellido='López' +tokenn: "apellido='López'" +Evaluando condición: "apellido='López'" +Final tokens: ["apellido='López'"] +token: apellido='López' +tokenn: "apellido='López'" +Evaluando condición: "apellido='López'" +Final tokens: ["apellido='López'"] +token: apellido='López' +tokenn: "apellido='López'" +Evaluando condición: "apellido='López'" +Final tokens: ["apellido='López'"] +token: apellido='López' +tokenn: "apellido='López'" +Evaluando condición: "apellido='López'" +Final tokens: ["apellido='López'"] +token: apellido='López' +tokenn: "apellido='López'" +Evaluando condición: "apellido='López'" +Estado inicial: +Ordenamiento: email DESC +Encabezados select: ["id", "nombre", "email"] +Resultado antes del ordenamiento: [["id", "nombre", "email"], ["2", "Ana", "ana.lopez@email.com"], ["5", "José", "jose.lopez@email.com"]] +id, nombre, email +5, José, jose.lopez@email.com +2, Ana, ana.lopez@email.com diff --git a/src/delete.rs b/src/delete.rs index 3838508..156f83f 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -185,7 +185,7 @@ impl Delete { keywords: [&str; 5], chars: &mut Peekable, ) { - while let Some(ch) = chars.next() { + for ch in chars.by_ref() { match ch { ' ' if !token_actual.is_empty() => { tokens.push(token_actual.trim().to_string()); @@ -267,7 +267,7 @@ impl Delete { //Evalúo la expresión en notación postfija fn evaluar_postfix( &self, - tokens: &Vec, + tokens: &[String], registro: &[String], encabezados: &[String], ) -> Result { diff --git a/src/lib.rs b/src/lib.rs index 89ad136..865b531 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,4 +4,4 @@ pub mod errores; pub mod insert; pub mod parser; pub mod select; -pub mod update; +pub mod update; \ No newline at end of file diff --git a/src/select.rs b/src/select.rs index a5f0621..9e4a4e2 100644 --- a/src/select.rs +++ b/src/select.rs @@ -179,7 +179,7 @@ impl Select { fn agregar_encabezados( &self, - vector_encabezados: &Vec, + vector_encabezados: &[String], resultado: &mut Vec>, encabezados_select: &mut Vec, ) -> Result<(), ParseError> { @@ -285,7 +285,7 @@ impl Select { keywords: [&str; 5], chars: &mut Peekable, ) { - while let Some(ch) = chars.next() { + for ch in chars.by_ref() { match ch { ' ' if !token_actual.is_empty() => { tokens.push(token_actual.trim().to_string()); @@ -367,7 +367,7 @@ impl Select { //Evalúo la expresión en notación postfija fn evaluar_postfix( &self, - tokens: &Vec, + tokens: &[String], registro: &[String], encabezados: &[String], ) -> Result { diff --git a/src/update.rs b/src/update.rs index 4cee8b0..374e515 100644 --- a/src/update.rs +++ b/src/update.rs @@ -229,7 +229,7 @@ impl Update { keywords: [&str; 5], chars: &mut Peekable, ) { - while let Some(ch) = chars.next() { + for ch in chars.by_ref() { match ch { ' ' if !token_actual.is_empty() => { tokens.push(token_actual.trim().to_string()); @@ -311,7 +311,7 @@ impl Update { //Evalúo la expresión en notación postfija fn evaluar_postfix( &self, - tokens: &Vec, + tokens: &[String], registro: &[String], encabezados: &[String], ) -> Result { diff --git a/tablas/clientes.csv b/tablas/clientes.csv index ba85e77..85310a6 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -4,4 +4,4 @@ id,nombre,apellido,email 3,Carlos,Gómez,carlos.gomez@email.com 4,María,Rodríguez,maria.rodriguez@email.com 5,José,López,jose.lopez@email.com -6,Laura,López,laura.fernandez@email.com +6,Laura,Fernández,laura.fernandez@email.com diff --git a/tests/delete_test.rs b/tests/delete_test.rs index e9683c1..fa129c5 100644 --- a/tests/delete_test.rs +++ b/tests/delete_test.rs @@ -56,8 +56,7 @@ fn test_comando_delete_parentesis_or_and_or() { let comando_delete = Comando::Delete(Delete { tabla: "clientes".to_string(), - restricciones: Some( - "DELETE FROM clientes WHERE (apellido='López' OR id=2) AND (nombre='Ana' OR id = 5)" + restricciones: Some("(apellido='López' OR id=2) AND (nombre='Ana' OR id = 5)" .to_string(), ), }); diff --git a/tests/update_test.rs b/tests/update_test.rs new file mode 100644 index 0000000..9bfbab9 --- /dev/null +++ b/tests/update_test.rs @@ -0,0 +1,48 @@ +extern crate tp_individual_taller_9508; + +use std::fs; +use std::fs::File; +use std::io::Write; +use tp_individual_taller_9508::comandos::Comando; +use tp_individual_taller_9508::update::Update; + +#[test] +fn test_comando_update_set_tres_campos() { + let ruta_carpeta = "./test_data_3"; + let ruta_archivo = format!("{}/clientes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let mut file = File::create(&ruta_archivo).unwrap(); + writeln!(file, "id,nombre,apellido,email").unwrap(); + writeln!(file, "1,Juan,Pérez,juan.perez@email.com").unwrap(); + writeln!(file, "2,Ana,López,ana.lopez@email.com").unwrap(); + writeln!(file, "3,Carlos,Gómez,carlos.gomez@email.com").unwrap(); + writeln!(file, "4,María,Rodríguez,maria.rodriguez@email.com").unwrap(); + writeln!(file, "5,José,López,jose.lopez@email.com").unwrap(); + writeln!(file, "6,Laura,Fernández,laura.fernandez@email.com").unwrap(); + + let comando_delete = Comando::Update(Update { + columnas: vec!["email".to_string(), "nombre".to_string(), "id".to_string()], + tabla: "clientes".to_string(), + valores: vec!["mrodriguez@hotmail.com".to_string(), "Mariiia".to_string(), "7".to_string()], + restricciones: Some("id = 4".to_string()), + }); + + let resultado = comando_delete.ejecutar(ruta_carpeta); + + assert!(resultado.is_ok()); + + let contenido_actualizado = fs::read_to_string(&ruta_archivo).unwrap(); + let esperado = "id,nombre,apellido,email +1,Juan,Pérez,juan.perez@email.com +2,Ana,López,ana.lopez@email.com +3,Carlos,Gómez,carlos.gomez@email.com +7,Mariiia,Rodríguez,mrodriguez@hotmail.com +5,José,López,jose.lopez@email.com +6,Laura,Fernández,laura.fernandez@email.com\n"; + assert_eq!(contenido_actualizado, esperado); + + fs::remove_file(&ruta_archivo).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} From 44cbb4de03cbae5591ca12448205b02b41e47e88 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sat, 7 Sep 2024 00:32:38 -0300 Subject: [PATCH 33/54] delete sin restricciones borra todos los registros menos encabezados --- output.csv | 43 +------------------------------------------ src/delete.rs | 2 +- tests/delete_test.rs | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 43 deletions(-) diff --git a/output.csv b/output.csv index c0a50cd..6c5456c 100644 --- a/output.csv +++ b/output.csv @@ -1,43 +1,2 @@ Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas -Consulta sql: SELECT id, nombre, email -FROM clientes -WHERE apellido = 'López' -ORDER BY email DESC; - -Ejecutar consulta en la tabla: "clientes" -Columnas a seleccionar: ["id", "nombre", "email"] -Restricciones: "apellido = 'López'" -Order by: "email DESC" -Encabezados antes de agregar: ["id", "nombre", "apellido", "email"] -Encabezados select: ["id", "nombre", "email"] -Final tokens: ["apellido='López'"] -token: apellido='López' -tokenn: "apellido='López'" -Evaluando condición: "apellido='López'" -Final tokens: ["apellido='López'"] -token: apellido='López' -tokenn: "apellido='López'" -Evaluando condición: "apellido='López'" -Final tokens: ["apellido='López'"] -token: apellido='López' -tokenn: "apellido='López'" -Evaluando condición: "apellido='López'" -Final tokens: ["apellido='López'"] -token: apellido='López' -tokenn: "apellido='López'" -Evaluando condición: "apellido='López'" -Final tokens: ["apellido='López'"] -token: apellido='López' -tokenn: "apellido='López'" -Evaluando condición: "apellido='López'" -Final tokens: ["apellido='López'"] -token: apellido='López' -tokenn: "apellido='López'" -Evaluando condición: "apellido='López'" -Estado inicial: -Ordenamiento: email DESC -Encabezados select: ["id", "nombre", "email"] -Resultado antes del ordenamiento: [["id", "nombre", "email"], ["2", "Ana", "ana.lopez@email.com"], ["5", "José", "jose.lopez@email.com"]] -id, nombre, email -5, José, jose.lopez@email.com -2, Ana, ana.lopez@email.com +Consulta sql: DELETE FROM clientes diff --git a/src/delete.rs b/src/delete.rs index 156f83f..fb23166 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -81,7 +81,7 @@ impl Delete { let fila: Vec = linea.split(',').map(|s| s.trim().to_string()).collect(); - if self.restricciones.is_none() || !self.aplicar_restricciones(&fila, &encabezados)? { + if !self.aplicar_restricciones(&fila, &encabezados)? { // Si no hay restricciones o si la fila no cumple con las restricciones, // la escribimos en el archivo temporal if let Err(err) = writeln!(archivo_temporal, "{}", linea) { diff --git a/tests/delete_test.rs b/tests/delete_test.rs index fa129c5..40b0c53 100644 --- a/tests/delete_test.rs +++ b/tests/delete_test.rs @@ -77,3 +77,36 @@ fn test_comando_delete_parentesis_or_and_or() { fs::remove_file(&ruta_archivo).unwrap(); fs::remove_dir_all(ruta_carpeta).unwrap(); } + +#[test] +fn test_comando_delete_sin_where_borra_todo_menos_encabezados() { + let ruta_carpeta = "./test_data_3"; + let ruta_archivo = format!("{}/clientes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let mut file = File::create(&ruta_archivo).unwrap(); + writeln!(file, "id,nombre,apellido,email").unwrap(); + writeln!(file, "1,Juan,Pérez,juan.perez@email.com").unwrap(); + writeln!(file, "2,Ana,López,ana.lopez@email.com").unwrap(); + writeln!(file, "3,Carlos,Gómez,carlos.gomez@email.com").unwrap(); + writeln!(file, "4,María,Rodríguez,maria.rodriguez@email.com").unwrap(); + writeln!(file, "5,José,López,jose.lopez@email.com").unwrap(); + writeln!(file, "6,Laura,Fernández,laura.fernandez@email.com").unwrap(); + + let comando_delete = Comando::Delete(Delete { + tabla: "clientes".to_string(), + restricciones: None, + }); + + let resultado = comando_delete.ejecutar(ruta_carpeta); + + assert!(resultado.is_ok()); + + let contenido_actualizado = fs::read_to_string(&ruta_archivo).unwrap(); + let esperado = "id,nombre,apellido,email\n"; + assert_eq!(contenido_actualizado, esperado); + + fs::remove_file(&ruta_archivo).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} From 4f4f8594d9a85afa456066af72c6703e4ffdecce Mon Sep 17 00:00:00 2001 From: German Douce Date: Sat, 7 Sep 2024 19:28:17 -0300 Subject: [PATCH 34/54] agrego tests a select e insert --- output.csv | 5 +- src/main.rs | 3 -- src/select.rs | 27 +--------- tablas/ordenes.csv | 1 + tests/insert_tests.rs | 114 ++++++++++++++++++++++++++++++++++++++++++ tests/select_test.rs | 91 +++++++++++++++++++++++++++++++++ tests/update_test.rs | 4 +- 7 files changed, 212 insertions(+), 33 deletions(-) create mode 100644 tests/insert_tests.rs create mode 100644 tests/select_test.rs diff --git a/output.csv b/output.csv index 6c5456c..b8a642a 100644 --- a/output.csv +++ b/output.csv @@ -1,2 +1,3 @@ -Ruta a las tablas: /home/linuxlite/Documents/taller/tp-indiv/tablas -Consulta sql: DELETE FROM clientes +Ejecutar consulta en la tabla: "ordenes" +Columnas a insertar: ["id", "id_cliente", "producto", "cantidad"] +Valores a insertar: [["111", "6", "Laptop", "3"]] diff --git a/src/main.rs b/src/main.rs index 76118f7..6ee92cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,9 +30,6 @@ fn main() { } }; - println!("Ruta a las tablas: {}", ruta_carpeta_tablas); - println!("Consulta sql: {}", consulta); - match comando.ejecutar(ruta_carpeta_tablas) { Ok(results) => { // Imprimir los resultados diff --git a/src/select.rs b/src/select.rs index 9e4a4e2..3a3a0a9 100644 --- a/src/select.rs +++ b/src/select.rs @@ -13,15 +13,6 @@ pub struct Select { impl Select { pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, ParseError> { - println!("Ejecutar consulta en la tabla: {:?}", self.tabla); - println!("Columnas a seleccionar: {:?}", self.columnas); - if let Some(ref restricciones) = self.restricciones { - println!("Restricciones: {:?}", restricciones); - } - if let Some(ref ordenamiento) = self.ordenamiento { - println!("Order by: {:?}", ordenamiento); - } - // Abro el archivo CSV let ruta_archivo = format!("{}/{}.csv", ruta_carpeta_tablas, self.tabla); let file = File::open(&ruta_archivo).map_err(|err| { @@ -54,14 +45,10 @@ impl Select { let mut resultado = Vec::new(); - println!("Encabezados antes de agregar: {:?}", vector_encabezados); - let mut encabezados_select = Vec::new(); self.agregar_encabezados(&vector_encabezados, &mut resultado, &mut encabezados_select)?; - println!("Encabezados select: {:?}", encabezados_select); - // leo linea a linea for linea in reader.lines() { let line = linea.map_err(|err| { @@ -115,11 +102,6 @@ impl Select { ordenamiento: &str, encabezados_select: &[String], ) -> Result<(), ParseError> { - println!("Estado inicial:"); - println!("Ordenamiento: {}", ordenamiento); - println!("Encabezados select: {:?}", encabezados_select); - println!("Resultado antes del ordenamiento: {:?}", resultado); - //Parseo los criterios de ordenamiento let criterios: Vec<(String, String)> = ordenamiento .split(',') @@ -273,9 +255,6 @@ impl Select { tokens.retain(|token| !token.is_empty()); - // Debug: ver el estado final de los tokens - println!("Final tokens: {:?}", tokens); - tokens } @@ -326,7 +305,6 @@ impl Select { }; for token in tokens.iter() { - println!("token: {}", token); match token.as_str() { "(" => operadores.push(token.to_string()), ")" => { @@ -374,7 +352,6 @@ impl Select { let mut stack = Vec::new(); for token in tokens.iter() { - println!("tokenn: {:?}", token); match token.as_str() { "AND" | "OR" | "NOT" => { println!("token en evaluate postfix {:?}", token); @@ -432,7 +409,6 @@ impl Select { registro: &[String], encabezados: &[String], ) -> Result { - println!("Evaluando condición: {:?}", condicion); if condicion == "true" { return Ok(true); @@ -484,8 +460,7 @@ impl Select { impl Select { pub fn imprimir_resultados(results: &[Vec]) { for row in results { - // Imprimir cada fila - let row_string = row.join(", "); + let row_string = row.join(","); println!("{}", row_string); } } diff --git a/tablas/ordenes.csv b/tablas/ordenes.csv index bf38946..e8d32be 100644 --- a/tablas/ordenes.csv +++ b/tablas/ordenes.csv @@ -9,3 +9,4 @@ id,id_cliente,producto,cantidad 108,4,Auriculares,1 109,5,Laptop,1 110,6,Teléfono,2 +111,6,Laptop,3 diff --git a/tests/insert_tests.rs b/tests/insert_tests.rs new file mode 100644 index 0000000..20fc8a1 --- /dev/null +++ b/tests/insert_tests.rs @@ -0,0 +1,114 @@ +extern crate tp_individual_taller_9508; + +use std::fs; +use std::fs::File; +use std::io::Write; +use tp_individual_taller_9508::comandos::Comando; +use tp_individual_taller_9508::insert::Insert; + +#[test] +fn test_insert_una_fila_con_cols_y_valores_ok() { + let ruta_carpeta = "./insert_test1_data"; + let ruta_archivo = format!("{}/ordenes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let mut file = File::create(&ruta_archivo).unwrap(); + writeln!(file, "id,id_cliente,producto,cantidad").unwrap(); + writeln!(file, "101,1,Laptop,1").unwrap(); + writeln!(file, "103,1,Monitor,1").unwrap(); + writeln!(file, "102,2,Teléfono,2").unwrap(); + writeln!(file, "104,3,Teclado,1").unwrap(); + writeln!(file, "105,4,Mouse,2").unwrap(); + writeln!(file, "106,5,Impresora,1").unwrap(); + writeln!(file, "107,6,Altavoces,1").unwrap(); + writeln!(file, "108,4,Auriculares,1").unwrap(); + writeln!(file, "109,5,Laptop,1").unwrap(); + writeln!(file, "110,6,Teléfono,2").unwrap(); + + + let comando_delete = Comando::Insert(Insert { + tabla: "ordenes".to_string(), + columnas: vec!["id".to_string(), "id_cliente".to_string(), "producto".to_string(), "cantidad".to_string()], + valores: vec![vec!["111".to_string(), "6".to_string(), "Laptop".to_string(), "3".to_string()]], + }); + + let resultado = comando_delete.ejecutar(ruta_carpeta); + + assert!(resultado.is_ok()); + + let contenido_actualizado = fs::read_to_string(&ruta_archivo).unwrap(); + let esperado = "id,id_cliente,producto,cantidad +101,1,Laptop,1 +103,1,Monitor,1 +102,2,Teléfono,2 +104,3,Teclado,1 +105,4,Mouse,2 +106,5,Impresora,1 +107,6,Altavoces,1 +108,4,Auriculares,1 +109,5,Laptop,1 +110,6,Teléfono,2 +111,6,Laptop,3\n"; + assert_eq!(contenido_actualizado, esperado); + + fs::remove_file(&ruta_archivo).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} + +#[test] +fn test_insert_3_filas_con_cols_y_valores_ok() { + let ruta_carpeta = "./insert_test2_data"; + let ruta_archivo = format!("{}/ordenes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let mut file = File::create(&ruta_archivo).unwrap(); + writeln!(file, "id,id_cliente,producto,cantidad").unwrap(); + writeln!(file, "101,1,Laptop,1").unwrap(); + writeln!(file, "103,1,Monitor,1").unwrap(); + writeln!(file, "102,2,Teléfono,2").unwrap(); + writeln!(file, "104,3,Teclado,1").unwrap(); + writeln!(file, "105,4,Mouse,2").unwrap(); + writeln!(file, "106,5,Impresora,1").unwrap(); + writeln!(file, "107,6,Altavoces,1").unwrap(); + writeln!(file, "108,4,Auriculares,1").unwrap(); + writeln!(file, "109,5,Laptop,1").unwrap(); + writeln!(file, "110,6,Teléfono,2").unwrap(); + + + let comando_insert = Comando::Insert(Insert { + tabla: "ordenes".to_string(), + columnas: vec!["id".to_string(), "id_cliente".to_string(), "producto".to_string(), "cantidad".to_string()], + valores: vec![ + vec!["111".to_string(), "1".to_string(), "Laptop1".to_string(), "1".to_string()], + vec!["112".to_string(), "1".to_string(), "Laptop2".to_string(), "1".to_string()], + vec!["113".to_string(), "1".to_string(), "Laptop3".to_string(), "1".to_string()], + ], + }); + + + let resultado = comando_insert.ejecutar(ruta_carpeta); + + assert!(resultado.is_ok()); + + let contenido_actualizado = fs::read_to_string(&ruta_archivo).unwrap(); + let esperado = "id,id_cliente,producto,cantidad +101,1,Laptop,1 +103,1,Monitor,1 +102,2,Teléfono,2 +104,3,Teclado,1 +105,4,Mouse,2 +106,5,Impresora,1 +107,6,Altavoces,1 +108,4,Auriculares,1 +109,5,Laptop,1 +110,6,Teléfono,2 +111,1,Laptop1,1 +112,1,Laptop2,1 +113,1,Laptop3,1\n"; + assert_eq!(contenido_actualizado, esperado); + + fs::remove_file(&ruta_archivo).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} \ No newline at end of file diff --git a/tests/select_test.rs b/tests/select_test.rs new file mode 100644 index 0000000..b5fb28d --- /dev/null +++ b/tests/select_test.rs @@ -0,0 +1,91 @@ +extern crate tp_individual_taller_9508; + +use std::fs; +use std::fs::File; +use std::io::Write; +use tp_individual_taller_9508::comandos::Comando; +use tp_individual_taller_9508::select::Select; + +#[test] +fn select_3de4_cols_1_restriccion_simple_con_mayor_ordenamiento_por1_col_desc() { + let ruta_carpeta = "./select_test_1"; + let ruta_archivo = format!("{}/clientes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + let mut file = File::create(&ruta_archivo).unwrap(); + writeln!(file, "id,nombre,apellido,email").unwrap(); + writeln!(file, "1,Juan,Pérez,juan.perez@email.com").unwrap(); + writeln!(file, "2,Ana,López,ana.lopez@email.com").unwrap(); + writeln!(file, "3,Carlos,Gómez,carlos.gomez@email.com").unwrap(); + writeln!(file, "4,María,Rodríguez,maria.rodriguez@email.com").unwrap(); + writeln!(file, "5,José,López,jose.lopez@email.com").unwrap(); + writeln!(file, "6,Laura,Fernández,laura.fernandez@email.com").unwrap(); + + let comando_select = Comando::Select(Select { + columnas: vec!["id".to_string(), "nombre".to_string(), "email".to_string()], + tabla: "clientes".to_string(), + restricciones: Some("apellido = 'López'".to_string()), + ordenamiento: Some("email DESC".to_string()), + }); + + let resultado = comando_select.ejecutar(ruta_carpeta).unwrap(); + + let mut resultado_string = String::new(); + for row in resultado { + let line = row.join(","); + resultado_string.push_str(&line); + resultado_string.push('\n'); + } + + let salida_esperada = "id,nombre,email\n5,José,jose.lopez@email.com\n2,Ana,ana.lopez@email.com\n"; + + assert_eq!(resultado_string, salida_esperada); + + fs::remove_file(&ruta_archivo).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} +#[test] +fn select_todo_sin_restricciones_ordenamiento_por_2columnas() { + let ruta_carpeta = "./select_test_2"; + let ruta_archivo = format!("{}/clientes.csv", ruta_carpeta); + + // Crear el archivo de prueba + fs::create_dir_all(ruta_carpeta).unwrap(); + let mut file = File::create(&ruta_archivo).unwrap(); + writeln!(file, "id,nombre,apellido,email").unwrap(); + writeln!(file, "1,Juan,Pérez,juan.perez@email.com").unwrap(); + writeln!(file, "2,Ana,López,ana.lopez@email.com").unwrap(); + writeln!(file, "3,Carlos,Gómez,carlos.gomez@email.com").unwrap(); + writeln!(file, "4,María,Rodríguez,maria.rodriguez@email.com").unwrap(); + writeln!(file, "5,José,López,jose.lopez@email.com").unwrap(); + writeln!(file, "6,Laura,Fernández,laura.fernandez@email.com").unwrap(); + + let comando_select = Comando::Select(Select { + columnas: vec!["*".to_string()], + tabla: "clientes".to_string(), + restricciones: None, + ordenamiento: Some("apellido, nombre DESC".to_string()), + }); + + let resultado = comando_select.ejecutar(ruta_carpeta).unwrap(); + + let mut resultado_string = String::new(); + for row in resultado { + let line = row.join(","); + resultado_string.push_str(&line); + resultado_string.push('\n'); + } + + let salida_esperada = "id,nombre,apellido,email +6,Laura,Fernández,laura.fernandez@email.com +3,Carlos,Gómez,carlos.gomez@email.com +5,José,López,jose.lopez@email.com +2,Ana,López,ana.lopez@email.com +1,Juan,Pérez,juan.perez@email.com +4,María,Rodríguez,maria.rodriguez@email.com\n"; + + assert_eq!(resultado_string, salida_esperada); + + fs::remove_file(&ruta_archivo).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} diff --git a/tests/update_test.rs b/tests/update_test.rs index 9bfbab9..f44a6a2 100644 --- a/tests/update_test.rs +++ b/tests/update_test.rs @@ -22,14 +22,14 @@ fn test_comando_update_set_tres_campos() { writeln!(file, "5,José,López,jose.lopez@email.com").unwrap(); writeln!(file, "6,Laura,Fernández,laura.fernandez@email.com").unwrap(); - let comando_delete = Comando::Update(Update { + let comando_update = Comando::Update(Update { columnas: vec!["email".to_string(), "nombre".to_string(), "id".to_string()], tabla: "clientes".to_string(), valores: vec!["mrodriguez@hotmail.com".to_string(), "Mariiia".to_string(), "7".to_string()], restricciones: Some("id = 4".to_string()), }); - let resultado = comando_delete.ejecutar(ruta_carpeta); + let resultado = comando_update.ejecutar(ruta_carpeta); assert!(resultado.is_ok()); From d9ad324b1f59595bf4d3a764d47839b1d1dbeb88 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sat, 7 Sep 2024 19:32:29 -0300 Subject: [PATCH 35/54] hago un poco mas robusto un test --- tests/delete_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/delete_test.rs b/tests/delete_test.rs index 40b0c53..3241d6f 100644 --- a/tests/delete_test.rs +++ b/tests/delete_test.rs @@ -56,7 +56,7 @@ fn test_comando_delete_parentesis_or_and_or() { let comando_delete = Comando::Delete(Delete { tabla: "clientes".to_string(), - restricciones: Some("(apellido='López' OR id=2) AND (nombre='Ana' OR id = 5)" + restricciones: Some("(id=2 OR apellido='López') AND (nombre='Ana' OR id = 5)" .to_string(), ), }); From e30132489c0de80c2f275a34043237d8725d22cb Mon Sep 17 00:00:00 2001 From: German Douce Date: Sat, 7 Sep 2024 20:34:48 -0300 Subject: [PATCH 36/54] empiezo a modificar errores para q cumplan con consigna --- output.csv | 4 +-- src/comandos.rs | 4 +-- src/delete.rs | 46 ++++++++++++++++----------------- src/errores.rs | 27 +++++++++++++++----- src/insert.rs | 14 +++++----- src/main.rs | 3 +-- src/parser.rs | 34 ++++++++++++------------- src/select.rs | 68 +++++++++++++++++++++---------------------------- src/update.rs | 54 +++++++++++++++++++-------------------- 9 files changed, 128 insertions(+), 126 deletions(-) diff --git a/output.csv b/output.csv index b8a642a..32cdf30 100644 --- a/output.csv +++ b/output.csv @@ -1,3 +1 @@ -Ejecutar consulta en la tabla: "ordenes" -Columnas a insertar: ["id", "id_cliente", "producto", "cantidad"] -Valores a insertar: [["111", "6", "Laptop", "3"]] +InvalidTable: Ocurrio un error al procesar la tabla. diff --git a/src/comandos.rs b/src/comandos.rs index 5fef1c9..336b47a 100644 --- a/src/comandos.rs +++ b/src/comandos.rs @@ -1,5 +1,5 @@ use crate::delete::Delete; -use crate::errores::ParseError; +use crate::errores::SqlError; use crate::insert::Insert; use crate::select::Select; use crate::update::Update; @@ -12,7 +12,7 @@ pub enum Comando { } impl Comando { - pub fn ejecutar(&self, ruta_carpeta: &str) -> Result>, ParseError> { + pub fn ejecutar(&self, ruta_carpeta: &str) -> Result>, SqlError> { match self { Comando::Select(select) => select.ejecutar(ruta_carpeta), Comando::Update(update) => update.ejecutar(ruta_carpeta), diff --git a/src/delete.rs b/src/delete.rs index fb23166..ed35304 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -1,4 +1,4 @@ -use crate::errores::ParseError; +use crate::errores::SqlError; use std::fs::{self, File}; use std::io::{BufRead, BufReader, Write}; use std::iter::Peekable; @@ -9,7 +9,7 @@ pub struct Delete { pub restricciones: Option, } impl Delete { - pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, ParseError> { + pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, SqlError> { let ruta_archivo = format!("{}/{}.csv", ruta_carpeta_tablas, self.tabla); let ruta_temporal = format!("{}/{}.tmp", ruta_carpeta_tablas, self.tabla); @@ -18,7 +18,7 @@ impl Delete { Ok(file) => file, Err(err) => { println!("Error al abrir el archivo de entrada: {}", err); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } }; @@ -29,7 +29,7 @@ impl Delete { Ok(file) => file, Err(err) => { println!("Error al crear el archivo temporal: {}", err); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } }; @@ -40,16 +40,16 @@ impl Delete { Some(Err(err)) => { println!( "Error al leer la primera línea del archivo: {}", - ParseError::Error(err.to_string()).to_string() + SqlError::Error(err.to_string()).to_string() ); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } None => { println!( "{}", - ParseError::Error("Archivo vacío".to_string()).to_string() + SqlError::Error("Archivo vacío".to_string()).to_string() ); - return Err(ParseError::Error("Archivo vacío".to_string())); + return Err(SqlError::Error("Archivo vacío".to_string())); } }; @@ -62,9 +62,9 @@ impl Delete { if let Err(err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { println!( "Error al escribir encabezados en el archivo temporal: {}", - ParseError::Error(err.to_string()).to_string() + SqlError::Error(err.to_string()).to_string() ); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } let mut filas_resultantes = Vec::new(); @@ -75,7 +75,7 @@ impl Delete { Ok(l) => l, Err(err) => { println!("Error al leer una línea del archivo: {}", err); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } }; @@ -86,7 +86,7 @@ impl Delete { // la escribimos en el archivo temporal if let Err(err) = writeln!(archivo_temporal, "{}", linea) { println!("Error al escribir en el archivo temporal: {}", err); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } } else { // Fila que cumple con las restricciones y se elimina @@ -100,7 +100,7 @@ impl Delete { "Error al reemplazar el archivo original con el archivo temporal: {}", err ); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } Ok(filas_resultantes) @@ -110,7 +110,7 @@ impl Delete { &self, registro: &[String], encabezados: &[String], - ) -> Result { + ) -> Result { if let Some(restricciones) = &self.restricciones { let tokens = self.tokenizar(restricciones); let postfix = self.infix_a_postfix(&tokens)?; @@ -212,7 +212,7 @@ impl Delete { //Convierte la notación infix (con paréntesis) a notación postfija (sin parentesis) //infix: (A OR B) AND (C OR D) ====> postfix: A B OR C D OR AND - fn infix_a_postfix(&self, tokens: &[String]) -> Result, ParseError> { + fn infix_a_postfix(&self, tokens: &[String]) -> Result, SqlError> { let mut resultado = Vec::new(); let mut operadores = Vec::new(); @@ -245,7 +245,7 @@ impl Delete { if let Some(op) = operadores.pop() { resultado.push(op); //saco } else { - return Err(ParseError::Error("restricciones invalidas".into())); + return Err(SqlError::Error("restricciones invalidas".into())); } } else { break; @@ -270,7 +270,7 @@ impl Delete { tokens: &[String], registro: &[String], encabezados: &[String], - ) -> Result { + ) -> Result { let mut stack = Vec::new(); for token in tokens.iter() { @@ -280,11 +280,11 @@ impl Delete { println!("token en evaluate postfix {:?}", token); let derecha = stack .pop() - .ok_or(ParseError::Error("expresión invalida".into()))?; + .ok_or(SqlError::Error("expresión invalida".into()))?; let izquierda = if token != "NOT" { stack .pop() - .ok_or(ParseError::Error("expresión invalida".into()))? + .ok_or(SqlError::Error("expresión invalida".into()))? } else { String::new() }; @@ -321,7 +321,7 @@ impl Delete { //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) Ok(stack .pop() - .ok_or(ParseError::Error("expresión invalida".into()))? + .ok_or(SqlError::Error("expresión invalida".into()))? == "true") } @@ -331,7 +331,7 @@ impl Delete { condicion: &str, registro: &[String], encabezados: &[String], - ) -> Result { + ) -> Result { println!("Evaluando condición: {:?}", condicion); if condicion == "true" { @@ -352,7 +352,7 @@ impl Delete { } else if let Some(pos) = condicion.find('=') { (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) } else { - return Err(ParseError::Error( + return Err(SqlError::Error( "Operador no válido en la condición".to_string(), )); }; @@ -373,7 +373,7 @@ impl Delete { Ok(resultado) } else { - Err(ParseError::Error(format!( + Err(SqlError::Error(format!( "La Columna '{}' no esta en los encabezados", columna ))) diff --git a/src/errores.rs b/src/errores.rs index 7c1d7a9..ba0e7d3 100644 --- a/src/errores.rs +++ b/src/errores.rs @@ -1,15 +1,30 @@ +use std::fmt; + #[derive(Debug)] -pub enum ParseError { +pub enum SqlError { + InvalidTable, ComandoNoReconocido, - ErrorDeSintaxis, + InvalidSintax, Error(String), } -impl ParseError { +impl SqlError { pub fn to_string(&self) -> &str { match self { - ParseError::ComandoNoReconocido => "Comando no reconocido", - ParseError::ErrorDeSintaxis => "Error de sintaxis", - ParseError::Error(msg) => msg, + SqlError::InvalidTable => "Hubo un error al procesar la tabla", + SqlError::ComandoNoReconocido => "Comando no reconocido", + SqlError::InvalidSintax => "Hay un error en la sintaxis del comando", + SqlError::Error(msg) => msg, + } + } +} + +impl fmt::Display for SqlError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SqlError::InvalidTable => write!(f, "InvalidTable: Ocurrio un error al procesar la tabla."), + SqlError::ComandoNoReconocido => write!(f, "ComandoNoReconocido: El comando no es reconocido."), + SqlError::InvalidSintax => write!(f, "InvalidSintax: Hay un error en la sintaxis de la consulta"), + SqlError::Error(msg) => write!(f, "Error: {}", msg), } } } diff --git a/src/insert.rs b/src/insert.rs index f9192d3..be1a37a 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -1,4 +1,4 @@ -use crate::errores::ParseError; +use crate::errores::SqlError; use std::fs::{File, OpenOptions}; use std::io::{BufRead, BufReader, BufWriter, Write}; use std::path::Path; @@ -10,7 +10,7 @@ pub struct Insert { } impl Insert { - pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, ParseError> { + pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, SqlError> { println!("Ejecutar consulta en la tabla: {:?}", self.tabla); println!("Columnas a insertar: {:?}", self.columnas); println!("Valores a insertar: {:?}", self.valores); @@ -23,7 +23,7 @@ impl Insert { Err(err) => { let err_msg = format!("Error al abrir el archivo: {}", err); println!("Error: {}", err_msg); - return Err(ParseError::Error(err_msg)); + return Err(SqlError::Error(err_msg)); } }; @@ -33,7 +33,7 @@ impl Insert { if let Err(err) = reader.read_line(&mut encabezados) { let err_msg = format!("Error al leer los encabezados: {}", err); println!("Error: {}", err_msg); - return Err(ParseError::Error(err_msg)); + return Err(SqlError::Error(err_msg)); } let encabezados: Vec = encabezados @@ -50,7 +50,7 @@ impl Insert { None => { let err_msg = format!("Columna '{}' no existe en la tabla", col); println!("ERROR: {}", err_msg); - return Err(ParseError::Error(err_msg)); + return Err(SqlError::Error(err_msg)); } } } @@ -61,7 +61,7 @@ impl Insert { Err(err) => { let err_msg = format!("Error al abrir el archivo para escritura: {}", err); println!("Error: {}", err_msg); - return Err(ParseError::Error(err_msg)); + return Err(SqlError::Error(err_msg)); } }; @@ -83,7 +83,7 @@ impl Insert { if let Err(err) = writeln!(writer, "{}", fila_nueva.join(",")) { let err_msg = format!("Error al escribir en el archivo: {}", err); println!("Error: {}", err_msg); - return Err(ParseError::Error(err_msg)); + return Err(SqlError::Error(err_msg)); } filas_insertadas.push(fila_nueva); diff --git a/src/main.rs b/src/main.rs index 6ee92cf..b90fef9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,11 +32,10 @@ fn main() { match comando.ejecutar(ruta_carpeta_tablas) { Ok(results) => { - // Imprimir los resultados comando.imprimir_resultados(&results); } Err(err) => { - println!("{:?}", err); + println!("{}", err); } } } diff --git a/src/parser.rs b/src/parser.rs index 74cd075..928aa50 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ // use std::io::{BufRead, BufReader}; use crate::comandos::Comando; use crate::delete::Delete; -use crate::errores::ParseError; +use crate::errores::SqlError; use crate::insert::Insert; use crate::select::Select; use crate::update::Update; @@ -13,7 +13,7 @@ use crate::update::Update; // } // leo la consulta SQL y devuelve un comando específico. -pub fn parsear_consulta(consulta: &str) -> Result { +pub fn parsear_consulta(consulta: &str) -> Result { //Si pongo toda la consulta enn minusucula, poerdo las mayuduclas de las tablas, columnas y APELLIDOCS //INTENMTE CAMBIAR LA LOGICA PERO SE ME HIZO MUUUY COMPLICADO SOLO COMPARAR ESAS PALABRAS //ASUMO Q LAS CALVES VIENEN EN MAYSUCULA YA QUE EN LOS EJEMPLOS ESTA ASI @@ -29,7 +29,7 @@ pub fn parsear_consulta(consulta: &str) -> Result { } else if let Some(resto) = consulta.strip_prefix("DELETE FROM") { ("delete", resto.trim()) } else { - return Err(ParseError::ComandoNoReconocido); + return Err(SqlError::ComandoNoReconocido); }; // Crear el comando basado en el tipo de comando detectado @@ -51,20 +51,20 @@ pub fn parsear_consulta(consulta: &str) -> Result { let delete = parse_delete(resto)?; Comando::Delete(delete) } - _ => return Err(ParseError::ComandoNoReconocido), + _ => return Err(SqlError::ComandoNoReconocido), }; Ok(comando) // Devolvemos el comando directamente, no una tupla } //praseo el select -fn parse_select(resto: &str) -> Result { +fn parse_select(resto: &str) -> Result { let resto = resto.trim_end_matches(';').trim(); //Separo la parte de las columnas y la tabla usando "FROM" let (columnas, resto) = if let Some((columnas, resto)) = resto.split_once("FROM") { (columnas.trim(), resto.trim()) } else { - return Err(ParseError::ErrorDeSintaxis); + return Err(SqlError::InvalidSintax); }; //Convierto la parte de las columnas en un vector de strings @@ -106,7 +106,7 @@ fn parse_select(resto: &str) -> Result { } -fn parse_update(resto: &str) -> Result { +fn parse_update(resto: &str) -> Result { // saco ; y saltos de linea let resto = resto.trim_end_matches(';').trim(); let resto = resto @@ -118,7 +118,7 @@ fn parse_update(resto: &str) -> Result { // Separo SET y WHERE let partes: Vec<&str> = resto.splitn(2, " SET ").collect(); if partes.len() != 2 { - return Err(ParseError::ErrorDeSintaxis); + return Err(SqlError::InvalidSintax); } //tabla @@ -137,13 +137,13 @@ fn parse_update(resto: &str) -> Result { let columna = parts .next() - .ok_or(ParseError::ErrorDeSintaxis)? + .ok_or(SqlError::InvalidSintax)? .trim() .to_string(); let valor = parts .next() - .ok_or(ParseError::ErrorDeSintaxis)? + .ok_or(SqlError::InvalidSintax)? .trim() .to_string(); @@ -152,7 +152,7 @@ fn parse_update(resto: &str) -> Result { // Verificar si la columna o valor están vacíos if columna.is_empty() || valor.is_empty() { - return Err(ParseError::ErrorDeSintaxis); + return Err(SqlError::InvalidSintax); } columnas.push(columna); @@ -173,7 +173,7 @@ fn parse_update(resto: &str) -> Result { restricciones, }) } -fn parse_insert(resto: &str) -> Result { +fn parse_insert(resto: &str) -> Result { let resto = resto.trim(); let pos_values = match resto.find("VALUES") { @@ -181,7 +181,7 @@ fn parse_insert(resto: &str) -> Result { None => { let err_msg = "Falta la palabra clave 'VALUES'"; println!("Error: {}", err_msg); - return Err(ParseError::Error(err_msg.to_string())); + return Err(SqlError::Error(err_msg.to_string())); } }; @@ -192,7 +192,7 @@ fn parse_insert(resto: &str) -> Result { None => { let err_msg = "Falta el '(' después del nombre de la tabla"; println!("Error: {}", err_msg); - return Err(ParseError::Error(err_msg.to_string())); + return Err(SqlError::Error(err_msg.to_string())); } }; @@ -201,7 +201,7 @@ fn parse_insert(resto: &str) -> Result { None => { let err_msg = "Falta el ')' después de la lista de columnas"; println!("Error: {}", err_msg); - return Err(ParseError::Error(err_msg.to_string())); + return Err(SqlError::Error(err_msg.to_string())); } }; @@ -246,7 +246,7 @@ fn parse_insert(resto: &str) -> Result { }) } -fn parse_delete(resto: &str) -> Result { +fn parse_delete(resto: &str) -> Result { // saco todos los ; y saltos de linea let resto = resto .replace(";", "") @@ -266,7 +266,7 @@ fn parse_delete(resto: &str) -> Result { }; if tabla.is_empty() { - return Err(ParseError::Error("Nombre de tabla faltante".to_string())); + return Err(SqlError::Error("Nombre de tabla faltante".to_string())); } Ok(Delete { diff --git a/src/select.rs b/src/select.rs index 3a3a0a9..6db7684 100644 --- a/src/select.rs +++ b/src/select.rs @@ -1,4 +1,4 @@ -use crate::errores::ParseError; +use crate::errores::SqlError; use std::fs::File; use std::io::{BufRead, BufReader}; use std::iter::Peekable; @@ -12,30 +12,23 @@ pub struct Select { } impl Select { - pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, ParseError> { + pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, SqlError> { // Abro el archivo CSV let ruta_archivo = format!("{}/{}.csv", ruta_carpeta_tablas, self.tabla); - let file = File::open(&ruta_archivo).map_err(|err| { - ParseError::Error(format!( - "Error al abrir el archivo '{}': {}", - ruta_archivo, err - )) + let file = File::open(&ruta_archivo).map_err(|_err| { + SqlError::InvalidTable })?; let mut reader = BufReader::new(file); // Leo la primera línea con las columnas let mut linea_encabezados = String::new(); - if reader.read_line(&mut linea_encabezados).map_err(|err| { - ParseError::Error(format!( - "Error al leer el archivo '{}': {}", - ruta_archivo, err - )) - })? == 0 - { - return Err(ParseError::Error(format!( - "El archivo '{}' está vacío", - ruta_archivo - ))); + let bytes_leidos = reader.read_line(&mut linea_encabezados).map_err(|_err| { + SqlError::InvalidTable + })?; + + if bytes_leidos == 0 { + //archivo vacio + return Err(SqlError::InvalidTable); } let vector_encabezados: Vec = linea_encabezados .trim_end() @@ -51,11 +44,8 @@ impl Select { // leo linea a linea for linea in reader.lines() { - let line = linea.map_err(|err| { - ParseError::Error(format!( - "Error al leer una línea del archivo '{}': {}", - ruta_archivo, err - )) + let line = linea.map_err(|_err| { + SqlError::InvalidTable })?; let registro: Vec = line.split(',').map(|s| s.to_string()).collect(); @@ -98,10 +88,10 @@ impl Select { fn aplicar_ordenamiento( &self, - resultado: &mut Vec>, + resultado: &mut [Vec], ordenamiento: &str, encabezados_select: &[String], - ) -> Result<(), ParseError> { + ) -> Result<(), SqlError> { //Parseo los criterios de ordenamiento let criterios: Vec<(String, String)> = ordenamiento .split(',') @@ -124,14 +114,14 @@ impl Select { .iter() .position(|encabezado| encabezado == columna) .ok_or_else(|| { - ParseError::Error(format!( + SqlError::Error(format!( "La columna '{}' no existe en los encabezados", columna )) }) .map(|indice| (indice, direccion.to_string())) }) - .collect::, ParseError>>()?; + .collect::, SqlError>>()?; //Ordeno las filas usando los criterios resultado[1..].sort_by(|fila_1, fila_2| { @@ -164,7 +154,7 @@ impl Select { vector_encabezados: &[String], resultado: &mut Vec>, encabezados_select: &mut Vec, - ) -> Result<(), ParseError> { + ) -> Result<(), SqlError> { if self.columnas.len() == 1 && self.columnas[0] == "*" { // Selecciono todas si es * encabezados_select.extend(vector_encabezados.iter().map(|s| s.as_str().to_string())); @@ -174,7 +164,7 @@ impl Select { if let Some(encabezado) = vector_encabezados.iter().find(|&encabezado| encabezado == col) { encabezados_select.push(encabezado.to_string()); } else { - return Err(ParseError::Error(format!("Columna '{}' no encontrada", col))); + return Err(SqlError::Error(format!("Columna '{}' no encontrada", col))); } } } @@ -192,7 +182,7 @@ impl Select { &self, registro: &[String], encabezados: &[String], - ) -> Result { + ) -> Result { if let Some(restricciones) = &self.restricciones { let tokens = self.tokenizar(restricciones); let postfix = self.infix_a_postfix(&tokens)?; @@ -291,7 +281,7 @@ impl Select { //Convierte la notación infix (con paréntesis) a notación postfija (sin parentesis) //infix: (A OR B) AND (C OR D) ====> postfix: A B OR C D OR AND - fn infix_a_postfix(&self, tokens: &[String]) -> Result, ParseError> { + fn infix_a_postfix(&self, tokens: &[String]) -> Result, SqlError> { let mut resultado = Vec::new(); let mut operadores = Vec::new(); @@ -323,7 +313,7 @@ impl Select { if let Some(op) = operadores.pop() { resultado.push(op); //saco } else { - return Err(ParseError::Error("restricciones invalidas".into())); + return Err(SqlError::Error("restricciones invalidas".into())); } } else { break; @@ -348,7 +338,7 @@ impl Select { tokens: &[String], registro: &[String], encabezados: &[String], - ) -> Result { + ) -> Result { let mut stack = Vec::new(); for token in tokens.iter() { @@ -357,11 +347,11 @@ impl Select { println!("token en evaluate postfix {:?}", token); let derecha = stack .pop() - .ok_or(ParseError::Error("expresión invalida".into()))?; + .ok_or(SqlError::Error("expresión invalida".into()))?; let izquierda = if token != "NOT" { stack .pop() - .ok_or(ParseError::Error("expresión invalida".into()))? + .ok_or(SqlError::Error("expresión invalida".into()))? } else { String::new() }; @@ -398,7 +388,7 @@ impl Select { //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) Ok(stack .pop() - .ok_or(ParseError::Error("expresión invalida".into()))? + .ok_or(SqlError::Error("expresión invalida".into()))? == "true") } @@ -408,7 +398,7 @@ impl Select { condicion: &str, registro: &[String], encabezados: &[String], - ) -> Result { + ) -> Result { if condicion == "true" { return Ok(true); @@ -428,7 +418,7 @@ impl Select { } else if let Some(pos) = condicion.find('=') { (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) } else { - return Err(ParseError::Error( + return Err(SqlError::Error( "Operador no válido en la condición".to_string(), )); }; @@ -449,7 +439,7 @@ impl Select { Ok(resultado) } else { - Err(ParseError::Error(format!( + Err(SqlError::Error(format!( "La Columna '{}' no esta en los encabezados", columna ))) diff --git a/src/update.rs b/src/update.rs index 374e515..069c14f 100644 --- a/src/update.rs +++ b/src/update.rs @@ -1,4 +1,4 @@ -use crate::errores::ParseError; +use crate::errores::SqlError; use std::fs::File; use std::io::{BufRead, BufReader, Write}; use std::iter::Peekable; @@ -13,7 +13,7 @@ pub struct Update { } impl Update { - pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, ParseError> { + pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, SqlError> { println!("Ejecutar consulta en la tabla: {:?}", self.tabla); println!("Columnas a actualizar: {:?}", self.columnas); println!("Valores a asignar: {:?}", self.valores); @@ -33,9 +33,9 @@ impl Update { Err(err) => { println!( "Error al abrir el archivo de entrada: {}", - ParseError::Error(err.to_string()).to_string() + SqlError::Error(err.to_string()).to_string() ); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } }; @@ -47,9 +47,9 @@ impl Update { Err(err) => { println!( "Error al crear el archivo temporal: {}", - ParseError::Error(err.to_string()).to_string() + SqlError::Error(err.to_string()).to_string() ); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } }; @@ -60,16 +60,16 @@ impl Update { Some(Err(err)) => { println!( "Error al leer la primera línea del archivo: {}", - ParseError::Error(err.to_string()).to_string() + SqlError::Error(err.to_string()).to_string() ); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } None => { println!( "{}", - ParseError::Error("Archivo vacío".to_string()).to_string() + SqlError::Error("Archivo vacío".to_string()).to_string() ); - return Err(ParseError::Error("Archivo vacío".to_string())); + return Err(SqlError::Error("Archivo vacío".to_string())); } }; @@ -82,9 +82,9 @@ impl Update { if let Err(err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { println!( "Error al escribir encabezados en el archivo temporal: {}", - ParseError::Error(err.to_string()).to_string() + SqlError::Error(err.to_string()).to_string() ); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } let mut filas_actualizadas = Vec::new(); @@ -96,7 +96,7 @@ impl Update { Err(err) => { println!( "Error al leer una línea del archivo: {}", - ParseError::Error(err.to_string()).to_string() + SqlError::Error(err.to_string()).to_string() ); continue; } @@ -130,9 +130,9 @@ impl Update { if let Err(err) = writeln!(archivo_temporal, "{}", registro.join(",")) { println!( "Error al escribir el registro actualizada en el archivo temporal: {}", - ParseError::Error(err.to_string()).to_string() + SqlError::Error(err.to_string()).to_string() ); - return Err(ParseError::Error(err.to_string())); + return Err(SqlError::Error(err.to_string())); } filas_actualizadas.push(registro); @@ -144,9 +144,9 @@ impl Update { Err(err) => { println!( "Error al reemplazar el archivo original: {}", - ParseError::Error(err.to_string()).to_string() + SqlError::Error(err.to_string()).to_string() ); - Err(ParseError::Error(err.to_string())) + Err(SqlError::Error(err.to_string())) } } } @@ -154,7 +154,7 @@ impl Update { &self, registro: &[String], encabezados: &[String], - ) -> Result { + ) -> Result { if let Some(restricciones) = &self.restricciones { let tokens = self.tokenizar(restricciones); let postfix = self.infix_a_postfix(&tokens)?; @@ -256,7 +256,7 @@ impl Update { //Convierte la notación infix (con paréntesis) a notación postfija (sin parentesis) //infix: (A OR B) AND (C OR D) ====> postfix: A B OR C D OR AND - fn infix_a_postfix(&self, tokens: &[String]) -> Result, ParseError> { + fn infix_a_postfix(&self, tokens: &[String]) -> Result, SqlError> { let mut resultado = Vec::new(); let mut operadores = Vec::new(); @@ -289,7 +289,7 @@ impl Update { if let Some(op) = operadores.pop() { resultado.push(op); //saco } else { - return Err(ParseError::Error("restricciones invalidas".into())); + return Err(SqlError::Error("restricciones invalidas".into())); } } else { break; @@ -314,7 +314,7 @@ impl Update { tokens: &[String], registro: &[String], encabezados: &[String], - ) -> Result { + ) -> Result { let mut stack = Vec::new(); for token in tokens.iter() { @@ -324,11 +324,11 @@ impl Update { println!("token en evaluate postfix {:?}", token); let derecha = stack .pop() - .ok_or(ParseError::Error("expresión invalida".into()))?; + .ok_or(SqlError::Error("expresión invalida".into()))?; let izquierda = if token != "NOT" { stack .pop() - .ok_or(ParseError::Error("expresión invalida".into()))? + .ok_or(SqlError::Error("expresión invalida".into()))? } else { String::new() }; @@ -365,7 +365,7 @@ impl Update { //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) Ok(stack .pop() - .ok_or(ParseError::Error("expresión invalida".into()))? + .ok_or(SqlError::Error("expresión invalida".into()))? == "true") } @@ -375,7 +375,7 @@ impl Update { condicion: &str, registro: &[String], encabezados: &[String], - ) -> Result { + ) -> Result { println!("Evaluando condición: {:?}", condicion); if condicion == "true" { @@ -396,7 +396,7 @@ impl Update { } else if let Some(pos) = condicion.find('=') { (&condicion[..pos].trim(), "=", &condicion[pos + 1..].trim()) } else { - return Err(ParseError::Error( + return Err(SqlError::Error( "Operador no válido en la condición".to_string(), )); }; @@ -417,7 +417,7 @@ impl Update { Ok(resultado) } else { - Err(ParseError::Error(format!( + Err(SqlError::Error(format!( "La Columna '{}' no esta en los encabezados", columna ))) From 138ef5ca089a4bb69fb4363fb38e898856d1381f Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 01:00:47 -0300 Subject: [PATCH 37/54] adapto errores para que cumplan con consigna. --- src/errores.rs | 8 +-- src/main.rs | 6 +- src/parser.rs | 20 ++----- src/select.rs | 38 ++++--------- src/update.rs | 112 +++++++++++-------------------------- tablas/clientes.csv | 1 + tests/tests_integracion.rs | 66 ++++++++++++++++++++++ 7 files changed, 126 insertions(+), 125 deletions(-) create mode 100644 tests/tests_integracion.rs diff --git a/src/errores.rs b/src/errores.rs index ba0e7d3..5ad378b 100644 --- a/src/errores.rs +++ b/src/errores.rs @@ -3,7 +3,7 @@ use std::fmt; #[derive(Debug)] pub enum SqlError { InvalidTable, - ComandoNoReconocido, + InvalidColumn, InvalidSintax, Error(String), } @@ -11,7 +11,7 @@ impl SqlError { pub fn to_string(&self) -> &str { match self { SqlError::InvalidTable => "Hubo un error al procesar la tabla", - SqlError::ComandoNoReconocido => "Comando no reconocido", + SqlError::InvalidColumn => "Hubo un error al procesar alguna columna", SqlError::InvalidSintax => "Hay un error en la sintaxis del comando", SqlError::Error(msg) => msg, } @@ -21,8 +21,8 @@ impl SqlError { impl fmt::Display for SqlError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - SqlError::InvalidTable => write!(f, "InvalidTable: Ocurrio un error al procesar la tabla."), - SqlError::ComandoNoReconocido => write!(f, "ComandoNoReconocido: El comando no es reconocido."), + SqlError::InvalidTable => write!(f, "InvalidTable: Ocurrió un error al procesar la tabla."), + SqlError::InvalidColumn => write!(f, "InvalidColumn: Ocurrió un error al procesar alguna columna."), SqlError::InvalidSintax => write!(f, "InvalidSintax: Hay un error en la sintaxis de la consulta"), SqlError::Error(msg) => write!(f, "Error: {}", msg), } diff --git a/src/main.rs b/src/main.rs index b90fef9..2a3dedb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,9 +24,9 @@ fn main() { // Parsear la consulta let comando = match parsear_consulta(consulta) { Ok(result) => result, - Err(_e) => { - println!("Error al parsear la consulta:"); - std::process::exit(1); + Err(err) => { + println!("{}", err); + return } }; diff --git a/src/parser.rs b/src/parser.rs index 928aa50..ec0849e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -29,7 +29,7 @@ pub fn parsear_consulta(consulta: &str) -> Result { } else if let Some(resto) = consulta.strip_prefix("DELETE FROM") { ("delete", resto.trim()) } else { - return Err(SqlError::ComandoNoReconocido); + return Err(SqlError::InvalidSintax); }; // Crear el comando basado en el tipo de comando detectado @@ -44,14 +44,13 @@ pub fn parsear_consulta(consulta: &str) -> Result { } "update" => { let update = parse_update(resto)?; - println!("bbbbbbbbbbb:"); Comando::Update(update) } "delete" => { let delete = parse_delete(resto)?; Comando::Delete(delete) } - _ => return Err(SqlError::ComandoNoReconocido), + _ => return Err(SqlError::InvalidSintax), }; Ok(comando) // Devolvemos el comando directamente, no una tupla } @@ -179,9 +178,7 @@ fn parse_insert(resto: &str) -> Result { let pos_values = match resto.find("VALUES") { Some(pos) => pos, None => { - let err_msg = "Falta la palabra clave 'VALUES'"; - println!("Error: {}", err_msg); - return Err(SqlError::Error(err_msg.to_string())); + return Err(SqlError::InvalidSintax); } }; @@ -190,18 +187,13 @@ fn parse_insert(resto: &str) -> Result { let paos_parentesis_abre = match tabla_y_columnas.find('(') { Some(pos) => pos, None => { - let err_msg = "Falta el '(' después del nombre de la tabla"; - println!("Error: {}", err_msg); - return Err(SqlError::Error(err_msg.to_string())); + return Err(SqlError::InvalidSintax); } }; let pos_parentesis_cierra = match tabla_y_columnas.find(')') { Some(pos) => pos, - None => { - let err_msg = "Falta el ')' después de la lista de columnas"; - println!("Error: {}", err_msg); - return Err(SqlError::Error(err_msg.to_string())); + None => { return Err(SqlError::InvalidSintax); } }; @@ -266,7 +258,7 @@ fn parse_delete(resto: &str) -> Result { }; if tabla.is_empty() { - return Err(SqlError::Error("Nombre de tabla faltante".to_string())); + return Err(SqlError::InvalidTable); } Ok(Delete { diff --git a/src/select.rs b/src/select.rs index 6db7684..63935b4 100644 --- a/src/select.rs +++ b/src/select.rs @@ -110,16 +110,11 @@ impl Select { let indice_direccion_criterios: Vec<(usize, String)> = criterios .iter() .map(|(columna, direccion)| { - encabezados_select - .iter() - .position(|encabezado| encabezado == columna) - .ok_or_else(|| { - SqlError::Error(format!( - "La columna '{}' no existe en los encabezados", - columna - )) - }) - .map(|indice| (indice, direccion.to_string())) + if let Some(indice) = encabezados_select.iter().position(|encabezado| encabezado == columna) { + Ok((indice, direccion.to_string())) + } else { + Err(SqlError::InvalidColumn) + } }) .collect::, SqlError>>()?; @@ -164,7 +159,7 @@ impl Select { if let Some(encabezado) = vector_encabezados.iter().find(|&encabezado| encabezado == col) { encabezados_select.push(encabezado.to_string()); } else { - return Err(SqlError::Error(format!("Columna '{}' no encontrada", col))); + return Err(SqlError::InvalidColumn); } } } @@ -244,7 +239,6 @@ impl Select { } tokens.retain(|token| !token.is_empty()); - tokens } @@ -340,23 +334,20 @@ impl Select { encabezados: &[String], ) -> Result { let mut stack = Vec::new(); - + println!("{:?}",tokens); for token in tokens.iter() { match token.as_str() { "AND" | "OR" | "NOT" => { - println!("token en evaluate postfix {:?}", token); let derecha = stack .pop() - .ok_or(SqlError::Error("expresión invalida".into()))?; + .ok_or(SqlError::InvalidSintax)?; let izquierda = if token != "NOT" { stack .pop() - .ok_or(SqlError::Error("expresión invalida".into()))? + .ok_or(SqlError::InvalidSintax)? } else { String::new() }; - println!("left: {:?}", &derecha); - println!("right: {:?}", &derecha); let resultado = match token.as_str() { "AND" => { self.aplicar_condicion(&izquierda, registro, encabezados)? @@ -385,10 +376,10 @@ impl Select { } } } - //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) + //si esta vacio el stack, error, sino devuelvo el resultado (ult valor del stack) Ok(stack .pop() - .ok_or(SqlError::Error("expresión invalida".into()))? + .ok_or(SqlError::InvalidSintax)? == "true") } @@ -405,7 +396,6 @@ impl Select { } else if condicion == "false" { return Ok(false); } - // Parseo la condición para encontrar operadores de comparación let (columna, operador, valor) = if let Some(pos) = condicion.find(">=") { (&condicion[..pos].trim(), ">=", &condicion[pos + 2..].trim()) @@ -436,13 +426,9 @@ impl Select { ">=" => valor_registro.as_str() >= valor, _ => false, }; - Ok(resultado) } else { - Err(SqlError::Error(format!( - "La Columna '{}' no esta en los encabezados", - columna - ))) + Err(SqlError::InvalidColumn) } } } diff --git a/src/update.rs b/src/update.rs index 069c14f..9cbec81 100644 --- a/src/update.rs +++ b/src/update.rs @@ -14,14 +14,6 @@ pub struct Update { impl Update { pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, SqlError> { - println!("Ejecutar consulta en la tabla: {:?}", self.tabla); - println!("Columnas a actualizar: {:?}", self.columnas); - println!("Valores a asignar: {:?}", self.valores); - - if let Some(ref restricciones) = self.restricciones { - println!("Restricciones: {:?}", restricciones); - } - let ruta_archivo = Path::new(ruta_carpeta_tablas) .join(&self.tabla) .with_extension("csv"); @@ -30,12 +22,8 @@ impl Update { // Abro archivo tabla let archivo_entrada = match File::open(&ruta_archivo) { Ok(file) => file, - Err(err) => { - println!( - "Error al abrir el archivo de entrada: {}", - SqlError::Error(err.to_string()).to_string() - ); - return Err(SqlError::Error(err.to_string())); + Err(_err) => { + return Err(SqlError::InvalidTable); } }; @@ -44,12 +32,8 @@ impl Update { // Creo un archivo temporal para ir escribiendo los resultados actualizados let mut archivo_temporal = match File::create(&ruta_archivo_temporal) { Ok(file) => file, - Err(err) => { - println!( - "Error al crear el archivo temporal: {}", - SqlError::Error(err.to_string()).to_string() - ); - return Err(SqlError::Error(err.to_string())); + Err(_err) => { + return Err(SqlError::Error("Error al crear el archivo temporal:".into())); } }; @@ -57,19 +41,12 @@ impl Update { let mut lineas = reader.lines(); let encabezados = match lineas.next() { Some(Ok(line)) => line, - Some(Err(err)) => { - println!( - "Error al leer la primera línea del archivo: {}", - SqlError::Error(err.to_string()).to_string() - ); - return Err(SqlError::Error(err.to_string())); + Some(Err(_err)) => { + return Err(SqlError::InvalidTable); } None => { - println!( - "{}", - SqlError::Error("Archivo vacío".to_string()).to_string() - ); - return Err(SqlError::Error("Archivo vacío".to_string())); + // archivo vacío + return Err(SqlError::InvalidTable); } }; @@ -78,13 +55,20 @@ impl Update { .map(|s| s.trim().to_string()) .collect(); + //chequeo columnas existan en tabla + //provisorio + let mut encabezados_update = Vec::new(); + for col in &self.columnas { + if let Some(encabezado) = encabezados.iter().find(|&encabezado| encabezado == col) { + encabezados_update.push(encabezado.to_string()); + } else { + return Err(SqlError::InvalidColumn); + } + } + // Escribo encabezados al archivo de salida - if let Err(err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { - println!( - "Error al escribir encabezados en el archivo temporal: {}", - SqlError::Error(err.to_string()).to_string() - ); - return Err(SqlError::Error(err.to_string())); + if let Err(_err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { + return Err(SqlError::InvalidColumn); } let mut filas_actualizadas = Vec::new(); @@ -93,24 +77,16 @@ impl Update { for linea in lineas { let mut registro: Vec = match linea { Ok(line) => line.split(',').map(|s| s.trim().to_string()).collect(), - Err(err) => { - println!( - "Error al leer una línea del archivo: {}", - SqlError::Error(err.to_string()).to_string() - ); + Err(_err) => { + drop(SqlError::InvalidTable); continue; } }; // Aplico restricciones - let registro_valido = if let Some(ref _restricciones) = self.restricciones { - match self.aplicar_restricciones(®istro, &encabezados) { - Ok(valido) => valido, - Err(err) => { - println!("Error al aplicar restricciones: {}", err.to_string()); - continue; // Salto al siguiente registro en caso de error en restricciones - } - } + let registro_valido = if let Some(ref _restricciones) = self.restricciones + { + self.aplicar_restricciones(®istro, &encabezados)? } else { true // Si no hay restricciones, el registro es válido }; @@ -127,12 +103,8 @@ impl Update { } // Escribo el registro actualizada al archivo temporal - if let Err(err) = writeln!(archivo_temporal, "{}", registro.join(",")) { - println!( - "Error al escribir el registro actualizada en el archivo temporal: {}", - SqlError::Error(err.to_string()).to_string() - ); - return Err(SqlError::Error(err.to_string())); + if let Err(_err) = writeln!(archivo_temporal, "{}", registro.join(",")) { + return Err(SqlError::Error("Error al escribir el archivo temporal".to_string(),)); } filas_actualizadas.push(registro); @@ -141,12 +113,8 @@ impl Update { // Reemplazo el archivo original con el archivo temporal match std::fs::rename(&ruta_archivo_temporal, &ruta_archivo) { Ok(_) => Ok(filas_actualizadas), - Err(err) => { - println!( - "Error al reemplazar el archivo original: {}", - SqlError::Error(err.to_string()).to_string() - ); - Err(SqlError::Error(err.to_string())) + Err(_err) => { + Err(SqlError::Error("Error al escribir el archivo temporal".to_string(),)) } } } @@ -217,9 +185,6 @@ impl Update { tokens.retain(|token| !token.is_empty()); - // Debug: ver el estado final de los tokens - println!("Final tokens: {:?}", tokens); - tokens } @@ -270,7 +235,6 @@ impl Update { }; for token in tokens.iter() { - println!("token: {}", token); match token.as_str() { "(" => operadores.push(token.to_string()), ")" => { @@ -289,7 +253,7 @@ impl Update { if let Some(op) = operadores.pop() { resultado.push(op); //saco } else { - return Err(SqlError::Error("restricciones invalidas".into())); + return Err(SqlError::InvalidSintax); } } else { break; @@ -318,22 +282,18 @@ impl Update { let mut stack = Vec::new(); for token in tokens.iter() { - println!("tokenn: {:?}", token); match token.as_str() { "AND" | "OR" | "NOT" => { - println!("token en evaluate postfix {:?}", token); let derecha = stack .pop() - .ok_or(SqlError::Error("expresión invalida".into()))?; + .ok_or(SqlError::InvalidSintax)?; let izquierda = if token != "NOT" { stack .pop() - .ok_or(SqlError::Error("expresión invalida".into()))? + .ok_or(SqlError::InvalidSintax)? } else { String::new() }; - println!("left: {:?}", &derecha); - println!("right: {:?}", &derecha); let resultado = match token.as_str() { "AND" => { self.aplicar_condicion(&izquierda, registro, encabezados)? @@ -376,7 +336,6 @@ impl Update { registro: &[String], encabezados: &[String], ) -> Result { - println!("Evaluando condición: {:?}", condicion); if condicion == "true" { return Ok(true); @@ -417,10 +376,7 @@ impl Update { Ok(resultado) } else { - Err(SqlError::Error(format!( - "La Columna '{}' no esta en los encabezados", - columna - ))) + Err(SqlError::InvalidColumn) } } } diff --git a/tablas/clientes.csv b/tablas/clientes.csv index 85310a6..b287063 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -5,3 +5,4 @@ id,nombre,apellido,email 4,María,Rodríguez,maria.rodriguez@email.com 5,José,López,jose.lopez@email.com 6,Laura,Fernández,laura.fernandez@email.com +1,,, diff --git a/tests/tests_integracion.rs b/tests/tests_integracion.rs new file mode 100644 index 0000000..3791b42 --- /dev/null +++ b/tests/tests_integracion.rs @@ -0,0 +1,66 @@ +use std::process::Command; +use std::fs::File; +use std::fs; +use std::io::Write; + + +#[test] +fn select_si_tabla_no_existe_devuelve_invalid_table() { + let ruta_carpeta = "./tests_integracion_1"; + let archivo_tablas = format!("{}/clientes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let _file = File::create(&archivo_tablas).unwrap(); + + let mut file = File::create(&archivo_tablas).unwrap(); + writeln!(file, "id,nombre,apellido,email").unwrap(); + writeln!(file, "1,Juan,Pérez,juan.perez@email.com").unwrap(); + + let consulta = "SELECT * FROM clientesssss;"; + + + let output = Command::new("cargo") + .args(&["run", "--", &ruta_carpeta, consulta]) + .output() + .expect("InvalidTable: Ocurrió un error al procesar la tabla."); + + assert!(output.status.success()); + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("InvalidTable: Ocurrió un error al procesar la tabla.")); + + fs::remove_file(archivo_tablas).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); + +} + +#[test] +fn select_si_no_existe_columna_en_order_by_devuelve_invalidcolumn() { + let ruta_carpeta = "./tests_integracion_3"; + let archivo_tablas = format!("{}/clientes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let _file = File::create(&archivo_tablas).unwrap(); + + let mut file = File::create(&archivo_tablas).unwrap(); + writeln!(file, "id,nombre,apellido,email").unwrap(); + writeln!(file, "1,Juan,Pérez,juan.perez@email.com").unwrap(); + + let consulta = "SELECT * FROM clientes ORDER BY nombressss;"; + + let output = Command::new("cargo") + .args(&["run", "--", &ruta_carpeta, consulta]) + .output() + .expect("InvalidColumn: Ocurrió un error al procesar alguna columna."); + + assert!(output.status.success()); + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("InvalidColumn: Ocurrió un error al procesar alguna columna.")); + + fs::remove_file(archivo_tablas).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); + +} From 3b89427541d106174f7bfa76fb9b6aa02255a9d1 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 01:21:49 -0300 Subject: [PATCH 38/54] corrijo delete xa q borre el archivo temporal si hay error!! --- src/delete.rs | 8 ++++++++ tablas/clientes.csv | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/delete.rs b/src/delete.rs index ed35304..be0dce8 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -81,6 +81,14 @@ impl Delete { let fila: Vec = linea.split(',').map(|s| s.trim().to_string()).collect(); + if let Err(err) = self.aplicar_restricciones(&fila, &encabezados) { + // Si aplicar_restricciones devuelve un error, elimino el archivo temporal + if let Err(_del_err) = std::fs::remove_file(&ruta_temporal) { + return Err(SqlError::Error("Error al eliminar el archivo temporal".to_string(),)); + } + return Err(err); + } + if !self.aplicar_restricciones(&fila, &encabezados)? { // Si no hay restricciones o si la fila no cumple con las restricciones, // la escribimos en el archivo temporal diff --git a/tablas/clientes.csv b/tablas/clientes.csv index b287063..85310a6 100644 --- a/tablas/clientes.csv +++ b/tablas/clientes.csv @@ -5,4 +5,3 @@ id,nombre,apellido,email 4,María,Rodríguez,maria.rodriguez@email.com 5,José,López,jose.lopez@email.com 6,Laura,Fernández,laura.fernandez@email.com -1,,, From 2071be181dcc6cc3871c762e32de35b226737e36 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 01:35:09 -0300 Subject: [PATCH 39/54] corrijo update xa q borre el archivo temporal si hay error!! :)))) --- src/update.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/update.rs b/src/update.rs index 9cbec81..1bf544a 100644 --- a/src/update.rs +++ b/src/update.rs @@ -84,9 +84,18 @@ impl Update { }; // Aplico restricciones - let registro_valido = if let Some(ref _restricciones) = self.restricciones - { - self.aplicar_restricciones(®istro, &encabezados)? + let registro_valido = if let Some(ref _restricciones) = self.restricciones { + if let Err(err) = self.aplicar_restricciones(®istro, &encabezados) { + // Si aplicar_restricciones devuelve un error, elimino el archivo temporal + if let Err(_del_err) = std::fs::remove_file(&ruta_archivo_temporal) { + return Err(SqlError::Error("Error al eliminar el archivo temporal".to_string())); + } + //burbujeo + return Err(err); + } else { + //no hubo error + self.aplicar_restricciones(®istro, &encabezados)? + } } else { true // Si no hay restricciones, el registro es válido }; From aa0fdf96017ccc6c2400efa280f11f322dc21724 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 01:49:16 -0300 Subject: [PATCH 40/54] corrijo update xa q borre el temp si no existe columna del SET y tira error --- src/update.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/update.rs b/src/update.rs index 1bf544a..d554cf5 100644 --- a/src/update.rs +++ b/src/update.rs @@ -56,12 +56,15 @@ impl Update { .collect(); //chequeo columnas existan en tabla - //provisorio let mut encabezados_update = Vec::new(); for col in &self.columnas { if let Some(encabezado) = encabezados.iter().find(|&encabezado| encabezado == col) { encabezados_update.push(encabezado.to_string()); } else { + //borro el temp + if let Err(_del_err) = std::fs::remove_file(&ruta_archivo_temporal) { + return Err(SqlError::Error("Error al eliminar el archivo temporal".to_string())); + } return Err(SqlError::InvalidColumn); } } From d7f777d4c3a6d77ff2a023baf7b4d491214d91d0 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 19:02:40 -0300 Subject: [PATCH 41/54] adapto errores de insert y delete y otros detalles menores --- src/delete.rs | 74 +++++++++++--------------------------- src/errores.rs | 19 +++------- src/insert.rs | 32 +++++------------ src/select.rs | 1 - tests/tests_integracion.rs | 8 ++--- 5 files changed, 38 insertions(+), 96 deletions(-) diff --git a/src/delete.rs b/src/delete.rs index be0dce8..a885469 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -16,9 +16,8 @@ impl Delete { // Abro archivo tabla let archivo = match File::open(&ruta_archivo) { Ok(file) => file, - Err(err) => { - println!("Error al abrir el archivo de entrada: {}", err); - return Err(SqlError::Error(err.to_string())); + Err(_err) => { + return Err(SqlError::InvalidTable); } }; @@ -27,9 +26,8 @@ impl Delete { // Creo un archivo temporal let mut archivo_temporal = match File::create(&ruta_temporal) { Ok(file) => file, - Err(err) => { - println!("Error al crear el archivo temporal: {}", err); - return Err(SqlError::Error(err.to_string())); + Err(_err) => { + return Err(SqlError::Error("Error al crear el archivo temporal:".into())); } }; @@ -37,19 +35,12 @@ impl Delete { let mut lineas = reader.lines(); let encabezados = match lineas.next() { Some(Ok(line)) => line, - Some(Err(err)) => { - println!( - "Error al leer la primera línea del archivo: {}", - SqlError::Error(err.to_string()).to_string() - ); - return Err(SqlError::Error(err.to_string())); + Some(Err(_err)) => { + return Err(SqlError::InvalidTable); } None => { - println!( - "{}", - SqlError::Error("Archivo vacío".to_string()).to_string() - ); - return Err(SqlError::Error("Archivo vacío".to_string())); + // archivo vacío + return Err(SqlError::InvalidTable); } }; @@ -59,12 +50,8 @@ impl Delete { .collect(); // Escribo encabezados al archivo de salida - if let Err(err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { - println!( - "Error al escribir encabezados en el archivo temporal: {}", - SqlError::Error(err.to_string()).to_string() - ); - return Err(SqlError::Error(err.to_string())); + if let Err(_err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { + return Err(SqlError::Error("Error al escribir encabezados en el archivo temporal:".into())); } let mut filas_resultantes = Vec::new(); @@ -73,9 +60,8 @@ impl Delete { for linea in lineas { let linea = match linea { Ok(l) => l, - Err(err) => { - println!("Error al leer una línea del archivo: {}", err); - return Err(SqlError::Error(err.to_string())); + Err(_err) => { + return Err(SqlError::InvalidTable); } }; @@ -92,9 +78,8 @@ impl Delete { if !self.aplicar_restricciones(&fila, &encabezados)? { // Si no hay restricciones o si la fila no cumple con las restricciones, // la escribimos en el archivo temporal - if let Err(err) = writeln!(archivo_temporal, "{}", linea) { - println!("Error al escribir en el archivo temporal: {}", err); - return Err(SqlError::Error(err.to_string())); + if let Err(_err) = writeln!(archivo_temporal, "{}", linea) { + return Err(SqlError::Error("Error al escribir el archivo temporal".to_string(),)) } } else { // Fila que cumple con las restricciones y se elimina @@ -103,12 +88,8 @@ impl Delete { } // Reemplazo el archivo original con el archivo temporal - if let Err(err) = fs::rename(&ruta_temporal, &ruta_archivo) { - println!( - "Error al reemplazar el archivo original con el archivo temporal: {}", - err - ); - return Err(SqlError::Error(err.to_string())); + if let Err(_err) = fs::rename(&ruta_temporal, &ruta_archivo) { + return Err(SqlError::Error("Error al reemplazar el archivo original con el archivo temporal".to_string())) } Ok(filas_resultantes) @@ -181,9 +162,6 @@ impl Delete { tokens.retain(|token| !token.is_empty()); - // Debug: ver el estado final de los tokens - println!("Final tokens: {:?}", tokens); - tokens } @@ -234,7 +212,6 @@ impl Delete { }; for token in tokens.iter() { - println!("token: {}", token); match token.as_str() { "(" => operadores.push(token.to_string()), ")" => { @@ -253,7 +230,7 @@ impl Delete { if let Some(op) = operadores.pop() { resultado.push(op); //saco } else { - return Err(SqlError::Error("restricciones invalidas".into())); + return Err(SqlError::InvalidSintax); } } else { break; @@ -282,22 +259,18 @@ impl Delete { let mut stack = Vec::new(); for token in tokens.iter() { - println!("tokenn: {:?}", token); match token.as_str() { "AND" | "OR" | "NOT" => { - println!("token en evaluate postfix {:?}", token); let derecha = stack .pop() - .ok_or(SqlError::Error("expresión invalida".into()))?; + .ok_or(SqlError::InvalidSintax)?; let izquierda = if token != "NOT" { stack .pop() - .ok_or(SqlError::Error("expresión invalida".into()))? + .ok_or(SqlError::InvalidSintax)? } else { String::new() }; - println!("left: {:?}", &derecha); - println!("right: {:?}", &derecha); let resultado = match token.as_str() { "AND" => { self.aplicar_condicion(&izquierda, registro, encabezados)? @@ -329,7 +302,7 @@ impl Delete { //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) Ok(stack .pop() - .ok_or(SqlError::Error("expresión invalida".into()))? + .ok_or(SqlError::InvalidSintax)? == "true") } @@ -340,8 +313,6 @@ impl Delete { registro: &[String], encabezados: &[String], ) -> Result { - println!("Evaluando condición: {:?}", condicion); - if condicion == "true" { return Ok(true); } else if condicion == "false" { @@ -381,10 +352,7 @@ impl Delete { Ok(resultado) } else { - Err(SqlError::Error(format!( - "La Columna '{}' no esta en los encabezados", - columna - ))) + Err(SqlError::InvalidColumn) } } } diff --git a/src/errores.rs b/src/errores.rs index 5ad378b..204abc6 100644 --- a/src/errores.rs +++ b/src/errores.rs @@ -7,24 +7,13 @@ pub enum SqlError { InvalidSintax, Error(String), } -impl SqlError { - pub fn to_string(&self) -> &str { - match self { - SqlError::InvalidTable => "Hubo un error al procesar la tabla", - SqlError::InvalidColumn => "Hubo un error al procesar alguna columna", - SqlError::InvalidSintax => "Hay un error en la sintaxis del comando", - SqlError::Error(msg) => msg, - } - } -} - impl fmt::Display for SqlError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - SqlError::InvalidTable => write!(f, "InvalidTable: Ocurrió un error al procesar la tabla."), - SqlError::InvalidColumn => write!(f, "InvalidColumn: Ocurrió un error al procesar alguna columna."), - SqlError::InvalidSintax => write!(f, "InvalidSintax: Hay un error en la sintaxis de la consulta"), - SqlError::Error(msg) => write!(f, "Error: {}", msg), + SqlError::InvalidTable => write!(f, "INVALID_TABLE: Ocurrió un error al procesar la tabla."), + SqlError::InvalidColumn => write!(f, "INVALID_COLUMN: Ocurrió un error al procesar alguna columna."), + SqlError::InvalidSintax => write!(f, "INVALID_SYNTAX: Hay un error en la sintaxis de la consulta"), + SqlError::Error(msg) => write!(f, "ERROR: {}", msg), } } } diff --git a/src/insert.rs b/src/insert.rs index be1a37a..bd1f627 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -11,29 +11,21 @@ pub struct Insert { impl Insert { pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, SqlError> { - println!("Ejecutar consulta en la tabla: {:?}", self.tabla); - println!("Columnas a insertar: {:?}", self.columnas); - println!("Valores a insertar: {:?}", self.valores); - let archivo_csv = Path::new(ruta_carpeta_tablas).join(format!("{}.csv", self.tabla)); // Leo archivo para obtener los encabezados let file = match File::open(&archivo_csv) { Ok(file) => file, - Err(err) => { - let err_msg = format!("Error al abrir el archivo: {}", err); - println!("Error: {}", err_msg); - return Err(SqlError::Error(err_msg)); + Err(_err) => { + return Err(SqlError::InvalidTable); } }; let mut reader = BufReader::new(file); let mut encabezados = String::new(); - if let Err(err) = reader.read_line(&mut encabezados) { - let err_msg = format!("Error al leer los encabezados: {}", err); - println!("Error: {}", err_msg); - return Err(SqlError::Error(err_msg)); + if let Err(_err) = reader.read_line(&mut encabezados) { + return Err(SqlError::InvalidTable); } let encabezados: Vec = encabezados @@ -48,9 +40,7 @@ impl Insert { match encabezados.iter().position(|header| header == col) { Some(index) => columnas_indices.push(index), None => { - let err_msg = format!("Columna '{}' no existe en la tabla", col); - println!("ERROR: {}", err_msg); - return Err(SqlError::Error(err_msg)); + return Err(SqlError::InvalidTable); } } } @@ -58,10 +48,8 @@ impl Insert { // Abro el archivo en modo append para agregar los nuevos let file = match OpenOptions::new().append(true).open(&archivo_csv) { Ok(file) => file, - Err(err) => { - let err_msg = format!("Error al abrir el archivo para escritura: {}", err); - println!("Error: {}", err_msg); - return Err(SqlError::Error(err_msg)); + Err(_err) => { + return Err(SqlError::InvalidTable); } }; @@ -80,10 +68,8 @@ impl Insert { } //la agrego - if let Err(err) = writeln!(writer, "{}", fila_nueva.join(",")) { - let err_msg = format!("Error al escribir en el archivo: {}", err); - println!("Error: {}", err_msg); - return Err(SqlError::Error(err_msg)); + if let Err(_err) = writeln!(writer, "{}", fila_nueva.join(",")) { + return Err(SqlError::Error("Error al escribir lineas en el archivo:".into())); } filas_insertadas.push(fila_nueva); diff --git a/src/select.rs b/src/select.rs index 63935b4..0d8777c 100644 --- a/src/select.rs +++ b/src/select.rs @@ -334,7 +334,6 @@ impl Select { encabezados: &[String], ) -> Result { let mut stack = Vec::new(); - println!("{:?}",tokens); for token in tokens.iter() { match token.as_str() { "AND" | "OR" | "NOT" => { diff --git a/tests/tests_integracion.rs b/tests/tests_integracion.rs index 3791b42..e3bab12 100644 --- a/tests/tests_integracion.rs +++ b/tests/tests_integracion.rs @@ -23,12 +23,12 @@ fn select_si_tabla_no_existe_devuelve_invalid_table() { let output = Command::new("cargo") .args(&["run", "--", &ruta_carpeta, consulta]) .output() - .expect("InvalidTable: Ocurrió un error al procesar la tabla."); + .expect("INVALID_TABLE: Ocurrió un error al procesar la tabla."); assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("InvalidTable: Ocurrió un error al procesar la tabla.")); + assert!(stdout.contains("INVALID_TABLE: Ocurrió un error al procesar la tabla.")); fs::remove_file(archivo_tablas).unwrap(); fs::remove_dir_all(ruta_carpeta).unwrap(); @@ -53,12 +53,12 @@ fn select_si_no_existe_columna_en_order_by_devuelve_invalidcolumn() { let output = Command::new("cargo") .args(&["run", "--", &ruta_carpeta, consulta]) .output() - .expect("InvalidColumn: Ocurrió un error al procesar alguna columna."); + .expect("INVALID_COLUMN: Ocurrió un error al procesar alguna columna."); assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("InvalidColumn: Ocurrió un error al procesar alguna columna.")); + assert!(stdout.contains("INVALID_COLUMN: Ocurrió un error al procesar alguna columna.")); fs::remove_file(archivo_tablas).unwrap(); fs::remove_dir_all(ruta_carpeta).unwrap(); From 06310b00b7c50b3904d2fb3bf5a92cac6118e765 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 19:42:47 -0300 Subject: [PATCH 42/54] corrijo insert xa que salte si se mandan columnas de mas o de menos --- src/insert.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/insert.rs b/src/insert.rs index bd1f627..4f4ed1e 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -36,14 +36,17 @@ impl Insert { // Verifio que las columnas de la consulta existan en el archivo let mut columnas_indices = Vec::with_capacity(self.columnas.len()); - for col in &self.columnas { - match encabezados.iter().position(|header| header == col) { - Some(index) => columnas_indices.push(index), - None => { - return Err(SqlError::InvalidTable); - } + for (index, header) in encabezados.iter().enumerate() { + if self.columnas.contains(header) { + columnas_indices.push(index); + } else { + return Err(SqlError::InvalidColumn); } } + //y que no haya columnas de mas ni de menos + if columnas_indices.len() != self.columnas.len() { + return Err(SqlError::InvalidColumn); + } // Abro el archivo en modo append para agregar los nuevos let file = match OpenOptions::new().append(true).open(&archivo_csv) { From 8928e5b85a5106a6418843f9a0cd80cdd204fd05 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 20:00:41 -0300 Subject: [PATCH 43/54] cargo fmt --- src/delete.rs | 33 +++++++++++++++++--------------- src/errores.rs | 14 +++++++++++--- src/insert.rs | 4 +++- src/lib.rs | 2 +- src/main.rs | 5 +++-- src/parser.rs | 51 +++++++++++++++++++++++++------------------------- src/select.rs | 40 ++++++++++++++++----------------------- src/update.rs | 31 ++++++++++++++++-------------- 8 files changed, 94 insertions(+), 86 deletions(-) diff --git a/src/delete.rs b/src/delete.rs index a885469..db5d069 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -27,7 +27,9 @@ impl Delete { let mut archivo_temporal = match File::create(&ruta_temporal) { Ok(file) => file, Err(_err) => { - return Err(SqlError::Error("Error al crear el archivo temporal:".into())); + return Err(SqlError::Error( + "Error al crear el archivo temporal:".into(), + )); } }; @@ -51,7 +53,9 @@ impl Delete { // Escribo encabezados al archivo de salida if let Err(_err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { - return Err(SqlError::Error("Error al escribir encabezados en el archivo temporal:".into())); + return Err(SqlError::Error( + "Error al escribir encabezados en el archivo temporal:".into(), + )); } let mut filas_resultantes = Vec::new(); @@ -70,7 +74,9 @@ impl Delete { if let Err(err) = self.aplicar_restricciones(&fila, &encabezados) { // Si aplicar_restricciones devuelve un error, elimino el archivo temporal if let Err(_del_err) = std::fs::remove_file(&ruta_temporal) { - return Err(SqlError::Error("Error al eliminar el archivo temporal".to_string(),)); + return Err(SqlError::Error( + "Error al eliminar el archivo temporal".to_string(), + )); } return Err(err); } @@ -79,7 +85,9 @@ impl Delete { // Si no hay restricciones o si la fila no cumple con las restricciones, // la escribimos en el archivo temporal if let Err(_err) = writeln!(archivo_temporal, "{}", linea) { - return Err(SqlError::Error("Error al escribir el archivo temporal".to_string(),)) + return Err(SqlError::Error( + "Error al escribir el archivo temporal".to_string(), + )); } } else { // Fila que cumple con las restricciones y se elimina @@ -89,7 +97,9 @@ impl Delete { // Reemplazo el archivo original con el archivo temporal if let Err(_err) = fs::rename(&ruta_temporal, &ruta_archivo) { - return Err(SqlError::Error("Error al reemplazar el archivo original con el archivo temporal".to_string())) + return Err(SqlError::Error( + "Error al reemplazar el archivo original con el archivo temporal".to_string(), + )); } Ok(filas_resultantes) @@ -261,13 +271,9 @@ impl Delete { for token in tokens.iter() { match token.as_str() { "AND" | "OR" | "NOT" => { - let derecha = stack - .pop() - .ok_or(SqlError::InvalidSintax)?; + let derecha = stack.pop().ok_or(SqlError::InvalidSintax)?; let izquierda = if token != "NOT" { - stack - .pop() - .ok_or(SqlError::InvalidSintax)? + stack.pop().ok_or(SqlError::InvalidSintax)? } else { String::new() }; @@ -300,10 +306,7 @@ impl Delete { } } //si esta vacio el stack error, sino devuelvo el resultado (ult valor del stack) - Ok(stack - .pop() - .ok_or(SqlError::InvalidSintax)? - == "true") + Ok(stack.pop().ok_or(SqlError::InvalidSintax)? == "true") } //Aplico condición simple (comparaciones > < = <= >=) diff --git a/src/errores.rs b/src/errores.rs index 204abc6..f1ef303 100644 --- a/src/errores.rs +++ b/src/errores.rs @@ -10,9 +10,17 @@ pub enum SqlError { impl fmt::Display for SqlError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - SqlError::InvalidTable => write!(f, "INVALID_TABLE: Ocurrió un error al procesar la tabla."), - SqlError::InvalidColumn => write!(f, "INVALID_COLUMN: Ocurrió un error al procesar alguna columna."), - SqlError::InvalidSintax => write!(f, "INVALID_SYNTAX: Hay un error en la sintaxis de la consulta"), + SqlError::InvalidTable => { + write!(f, "INVALID_TABLE: Ocurrió un error al procesar la tabla.") + } + SqlError::InvalidColumn => write!( + f, + "INVALID_COLUMN: Ocurrió un error al procesar alguna columna." + ), + SqlError::InvalidSintax => write!( + f, + "INVALID_SYNTAX: Hay un error en la sintaxis de la consulta" + ), SqlError::Error(msg) => write!(f, "ERROR: {}", msg), } } diff --git a/src/insert.rs b/src/insert.rs index 4f4ed1e..ace48f5 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -72,7 +72,9 @@ impl Insert { //la agrego if let Err(_err) = writeln!(writer, "{}", fila_nueva.join(",")) { - return Err(SqlError::Error("Error al escribir lineas en el archivo:".into())); + return Err(SqlError::Error( + "Error al escribir lineas en el archivo:".into(), + )); } filas_insertadas.push(fila_nueva); diff --git a/src/lib.rs b/src/lib.rs index 865b531..89ad136 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,4 +4,4 @@ pub mod errores; pub mod insert; pub mod parser; pub mod select; -pub mod update; \ No newline at end of file +pub mod update; diff --git a/src/main.rs b/src/main.rs index 2a3dedb..fff9789 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,8 +14,9 @@ fn main() { let args: Vec = env::args().collect(); if args.len() < 3 { println!( - "[ERROR]: Por favor, proporcione una ruta a la carpeta de tablas y una consulta SQL." + "ERROR: Por favor, proporcione una ruta a la carpeta de tablas y una consulta SQL." ); + return; } let ruta_carpeta_tablas = &args[1]; @@ -26,7 +27,7 @@ fn main() { Ok(result) => result, Err(err) => { println!("{}", err); - return + return; } }; diff --git a/src/parser.rs b/src/parser.rs index ec0849e..3cd5f97 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,4 +1,3 @@ -// use std::io::{BufRead, BufReader}; use crate::comandos::Comando; use crate::delete::Delete; use crate::errores::SqlError; @@ -6,12 +5,6 @@ use crate::insert::Insert; use crate::select::Select; use crate::update::Update; -// pub struct Update { -// } -// -// pub struct Delete { -// } - // leo la consulta SQL y devuelve un comando específico. pub fn parsear_consulta(consulta: &str) -> Result { //Si pongo toda la consulta enn minusucula, poerdo las mayuduclas de las tablas, columnas y APELLIDOCS @@ -73,16 +66,29 @@ fn parse_select(resto: &str) -> Result { .collect(); //Separo la tabla y manejar posibles restricciones y ordenamiento - let (tabla, restricciones, ordenamiento) = if let Some((tabla, resto)) = resto.split_once("WHERE") { + let (tabla, restricciones, ordenamiento) = + separar_tabla_restricciones_ordenamiento(resto); + + Ok(Select { + columnas, + tabla, + restricciones, + ordenamiento, + }) +} + +fn separar_tabla_restricciones_ordenamiento(resto: &str) -> (String, Option, Option) { + if let Some((tabla, resto)) = resto.split_once("WHERE") { // Si hay WHERE, manejamos restricciones y ordenamiento - let (restricciones, ordenamiento) = if let Some((restricciones, orden)) = resto.split_once("ORDER BY") { - ( - Some(restricciones.trim().to_string()), - Some(orden.trim().to_string()), - ) - } else { - (Some(resto.trim().to_string()), None) - }; + let (restricciones, ordenamiento) = + if let Some((restricciones, orden)) = resto.split_once("ORDER BY") { + ( + Some(restricciones.trim().to_string()), + Some(orden.trim().to_string()), + ) + } else { + (Some(resto.trim().to_string()), None) + }; (tabla.trim().to_string(), restricciones, ordenamiento) } else if let Some((tabla, orden)) = resto.split_once("ORDER BY") { // Si no hay WHERE pero sí ORDER BY @@ -94,17 +100,9 @@ fn parse_select(resto: &str) -> Result { } else { // No hay ni WHERE ni ORDER BY (resto.trim().to_string(), None, None) - }; - - Ok(Select { - columnas, - tabla, - restricciones, - ordenamiento, - }) + } } - fn parse_update(resto: &str) -> Result { // saco ; y saltos de linea let resto = resto.trim_end_matches(';').trim(); @@ -193,7 +191,8 @@ fn parse_insert(resto: &str) -> Result { let pos_parentesis_cierra = match tabla_y_columnas.find(')') { Some(pos) => pos, - None => { return Err(SqlError::InvalidSintax); + None => { + return Err(SqlError::InvalidSintax); } }; diff --git a/src/select.rs b/src/select.rs index 0d8777c..65e8687 100644 --- a/src/select.rs +++ b/src/select.rs @@ -15,16 +15,14 @@ impl Select { pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, SqlError> { // Abro el archivo CSV let ruta_archivo = format!("{}/{}.csv", ruta_carpeta_tablas, self.tabla); - let file = File::open(&ruta_archivo).map_err(|_err| { - SqlError::InvalidTable - })?; + let file = File::open(&ruta_archivo).map_err(|_err| SqlError::InvalidTable)?; let mut reader = BufReader::new(file); // Leo la primera línea con las columnas let mut linea_encabezados = String::new(); - let bytes_leidos = reader.read_line(&mut linea_encabezados).map_err(|_err| { - SqlError::InvalidTable - })?; + let bytes_leidos = reader + .read_line(&mut linea_encabezados) + .map_err(|_err| SqlError::InvalidTable)?; if bytes_leidos == 0 { //archivo vacio @@ -44,9 +42,7 @@ impl Select { // leo linea a linea for linea in reader.lines() { - let line = linea.map_err(|_err| { - SqlError::InvalidTable - })?; + let line = linea.map_err(|_err| SqlError::InvalidTable)?; let registro: Vec = line.split(',').map(|s| s.to_string()).collect(); // Aplico restricciones (WHERE) @@ -110,7 +106,10 @@ impl Select { let indice_direccion_criterios: Vec<(usize, String)> = criterios .iter() .map(|(columna, direccion)| { - if let Some(indice) = encabezados_select.iter().position(|encabezado| encabezado == columna) { + if let Some(indice) = encabezados_select + .iter() + .position(|encabezado| encabezado == columna) + { Ok((indice, direccion.to_string())) } else { Err(SqlError::InvalidColumn) @@ -121,7 +120,6 @@ impl Select { //Ordeno las filas usando los criterios resultado[1..].sort_by(|fila_1, fila_2| { for &(indice_columna, ref direccion) in &indice_direccion_criterios { - let valor_1 = fila_1.get(indice_columna).map_or("", |v| v.as_str()); let valor_2 = fila_2.get(indice_columna).map_or("", |v| v.as_str()); @@ -140,7 +138,6 @@ impl Select { std::cmp::Ordering::Equal }); - Ok(()) } @@ -156,7 +153,10 @@ impl Select { } else { encabezados_select.clear(); for col in &self.columnas { - if let Some(encabezado) = vector_encabezados.iter().find(|&encabezado| encabezado == col) { + if let Some(encabezado) = vector_encabezados + .iter() + .find(|&encabezado| encabezado == col) + { encabezados_select.push(encabezado.to_string()); } else { return Err(SqlError::InvalidColumn); @@ -337,13 +337,9 @@ impl Select { for token in tokens.iter() { match token.as_str() { "AND" | "OR" | "NOT" => { - let derecha = stack - .pop() - .ok_or(SqlError::InvalidSintax)?; + let derecha = stack.pop().ok_or(SqlError::InvalidSintax)?; let izquierda = if token != "NOT" { - stack - .pop() - .ok_or(SqlError::InvalidSintax)? + stack.pop().ok_or(SqlError::InvalidSintax)? } else { String::new() }; @@ -376,10 +372,7 @@ impl Select { } } //si esta vacio el stack, error, sino devuelvo el resultado (ult valor del stack) - Ok(stack - .pop() - .ok_or(SqlError::InvalidSintax)? - == "true") + Ok(stack.pop().ok_or(SqlError::InvalidSintax)? == "true") } //Aplico condición simple (comparaciones > < = <= >=) @@ -389,7 +382,6 @@ impl Select { registro: &[String], encabezados: &[String], ) -> Result { - if condicion == "true" { return Ok(true); } else if condicion == "false" { diff --git a/src/update.rs b/src/update.rs index d554cf5..1f9131b 100644 --- a/src/update.rs +++ b/src/update.rs @@ -33,7 +33,9 @@ impl Update { let mut archivo_temporal = match File::create(&ruta_archivo_temporal) { Ok(file) => file, Err(_err) => { - return Err(SqlError::Error("Error al crear el archivo temporal:".into())); + return Err(SqlError::Error( + "Error al crear el archivo temporal:".into(), + )); } }; @@ -63,7 +65,9 @@ impl Update { } else { //borro el temp if let Err(_del_err) = std::fs::remove_file(&ruta_archivo_temporal) { - return Err(SqlError::Error("Error al eliminar el archivo temporal".to_string())); + return Err(SqlError::Error( + "Error al eliminar el archivo temporal".to_string(), + )); } return Err(SqlError::InvalidColumn); } @@ -91,7 +95,9 @@ impl Update { if let Err(err) = self.aplicar_restricciones(®istro, &encabezados) { // Si aplicar_restricciones devuelve un error, elimino el archivo temporal if let Err(_del_err) = std::fs::remove_file(&ruta_archivo_temporal) { - return Err(SqlError::Error("Error al eliminar el archivo temporal".to_string())); + return Err(SqlError::Error( + "Error al eliminar el archivo temporal".to_string(), + )); } //burbujeo return Err(err); @@ -116,7 +122,9 @@ impl Update { // Escribo el registro actualizada al archivo temporal if let Err(_err) = writeln!(archivo_temporal, "{}", registro.join(",")) { - return Err(SqlError::Error("Error al escribir el archivo temporal".to_string(),)); + return Err(SqlError::Error( + "Error al escribir el archivo temporal".to_string(), + )); } filas_actualizadas.push(registro); @@ -125,9 +133,9 @@ impl Update { // Reemplazo el archivo original con el archivo temporal match std::fs::rename(&ruta_archivo_temporal, &ruta_archivo) { Ok(_) => Ok(filas_actualizadas), - Err(_err) => { - Err(SqlError::Error("Error al escribir el archivo temporal".to_string(),)) - } + Err(_err) => Err(SqlError::Error( + "Error al escribir el archivo temporal".to_string(), + )), } } pub fn aplicar_restricciones( @@ -296,13 +304,9 @@ impl Update { for token in tokens.iter() { match token.as_str() { "AND" | "OR" | "NOT" => { - let derecha = stack - .pop() - .ok_or(SqlError::InvalidSintax)?; + let derecha = stack.pop().ok_or(SqlError::InvalidSintax)?; let izquierda = if token != "NOT" { - stack - .pop() - .ok_or(SqlError::InvalidSintax)? + stack.pop().ok_or(SqlError::InvalidSintax)? } else { String::new() }; @@ -348,7 +352,6 @@ impl Update { registro: &[String], encabezados: &[String], ) -> Result { - if condicion == "true" { return Ok(true); } else if condicion == "false" { From d2f157851db9add8042f7eea0a64568cc2eb0daf Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 20:01:25 -0300 Subject: [PATCH 44/54] cargo fmt --- tests/delete_test.rs | 4 +--- tests/insert_tests.rs | 47 ++++++++++++++++++++++++++++++-------- tests/select_test.rs | 3 ++- tests/tests_integracion.rs | 8 ++----- tests/update_test.rs | 6 ++++- 5 files changed, 47 insertions(+), 21 deletions(-) diff --git a/tests/delete_test.rs b/tests/delete_test.rs index 3241d6f..6392e3b 100644 --- a/tests/delete_test.rs +++ b/tests/delete_test.rs @@ -56,9 +56,7 @@ fn test_comando_delete_parentesis_or_and_or() { let comando_delete = Comando::Delete(Delete { tabla: "clientes".to_string(), - restricciones: Some("(id=2 OR apellido='López') AND (nombre='Ana' OR id = 5)" - .to_string(), - ), + restricciones: Some("(id=2 OR apellido='López') AND (nombre='Ana' OR id = 5)".to_string()), }); let resultado = comando_delete.ejecutar(ruta_carpeta); diff --git a/tests/insert_tests.rs b/tests/insert_tests.rs index 20fc8a1..0c43a3c 100644 --- a/tests/insert_tests.rs +++ b/tests/insert_tests.rs @@ -26,11 +26,20 @@ fn test_insert_una_fila_con_cols_y_valores_ok() { writeln!(file, "109,5,Laptop,1").unwrap(); writeln!(file, "110,6,Teléfono,2").unwrap(); - let comando_delete = Comando::Insert(Insert { tabla: "ordenes".to_string(), - columnas: vec!["id".to_string(), "id_cliente".to_string(), "producto".to_string(), "cantidad".to_string()], - valores: vec![vec!["111".to_string(), "6".to_string(), "Laptop".to_string(), "3".to_string()]], + columnas: vec![ + "id".to_string(), + "id_cliente".to_string(), + "producto".to_string(), + "cantidad".to_string(), + ], + valores: vec![vec![ + "111".to_string(), + "6".to_string(), + "Laptop".to_string(), + "3".to_string(), + ]], }); let resultado = comando_delete.ejecutar(ruta_carpeta); @@ -76,18 +85,36 @@ fn test_insert_3_filas_con_cols_y_valores_ok() { writeln!(file, "109,5,Laptop,1").unwrap(); writeln!(file, "110,6,Teléfono,2").unwrap(); - let comando_insert = Comando::Insert(Insert { tabla: "ordenes".to_string(), - columnas: vec!["id".to_string(), "id_cliente".to_string(), "producto".to_string(), "cantidad".to_string()], + columnas: vec![ + "id".to_string(), + "id_cliente".to_string(), + "producto".to_string(), + "cantidad".to_string(), + ], valores: vec![ - vec!["111".to_string(), "1".to_string(), "Laptop1".to_string(), "1".to_string()], - vec!["112".to_string(), "1".to_string(), "Laptop2".to_string(), "1".to_string()], - vec!["113".to_string(), "1".to_string(), "Laptop3".to_string(), "1".to_string()], + vec![ + "111".to_string(), + "1".to_string(), + "Laptop1".to_string(), + "1".to_string(), + ], + vec![ + "112".to_string(), + "1".to_string(), + "Laptop2".to_string(), + "1".to_string(), + ], + vec![ + "113".to_string(), + "1".to_string(), + "Laptop3".to_string(), + "1".to_string(), + ], ], }); - let resultado = comando_insert.ejecutar(ruta_carpeta); assert!(resultado.is_ok()); @@ -111,4 +138,4 @@ fn test_insert_3_filas_con_cols_y_valores_ok() { fs::remove_file(&ruta_archivo).unwrap(); fs::remove_dir_all(ruta_carpeta).unwrap(); -} \ No newline at end of file +} diff --git a/tests/select_test.rs b/tests/select_test.rs index b5fb28d..42fd61f 100644 --- a/tests/select_test.rs +++ b/tests/select_test.rs @@ -37,7 +37,8 @@ fn select_3de4_cols_1_restriccion_simple_con_mayor_ordenamiento_por1_col_desc() resultado_string.push('\n'); } - let salida_esperada = "id,nombre,email\n5,José,jose.lopez@email.com\n2,Ana,ana.lopez@email.com\n"; + let salida_esperada = + "id,nombre,email\n5,José,jose.lopez@email.com\n2,Ana,ana.lopez@email.com\n"; assert_eq!(resultado_string, salida_esperada); diff --git a/tests/tests_integracion.rs b/tests/tests_integracion.rs index e3bab12..cb8cfd8 100644 --- a/tests/tests_integracion.rs +++ b/tests/tests_integracion.rs @@ -1,8 +1,7 @@ -use std::process::Command; -use std::fs::File; use std::fs; +use std::fs::File; use std::io::Write; - +use std::process::Command; #[test] fn select_si_tabla_no_existe_devuelve_invalid_table() { @@ -19,7 +18,6 @@ fn select_si_tabla_no_existe_devuelve_invalid_table() { let consulta = "SELECT * FROM clientesssss;"; - let output = Command::new("cargo") .args(&["run", "--", &ruta_carpeta, consulta]) .output() @@ -32,7 +30,6 @@ fn select_si_tabla_no_existe_devuelve_invalid_table() { fs::remove_file(archivo_tablas).unwrap(); fs::remove_dir_all(ruta_carpeta).unwrap(); - } #[test] @@ -62,5 +59,4 @@ fn select_si_no_existe_columna_en_order_by_devuelve_invalidcolumn() { fs::remove_file(archivo_tablas).unwrap(); fs::remove_dir_all(ruta_carpeta).unwrap(); - } diff --git a/tests/update_test.rs b/tests/update_test.rs index f44a6a2..d381c48 100644 --- a/tests/update_test.rs +++ b/tests/update_test.rs @@ -25,7 +25,11 @@ fn test_comando_update_set_tres_campos() { let comando_update = Comando::Update(Update { columnas: vec!["email".to_string(), "nombre".to_string(), "id".to_string()], tabla: "clientes".to_string(), - valores: vec!["mrodriguez@hotmail.com".to_string(), "Mariiia".to_string(), "7".to_string()], + valores: vec![ + "mrodriguez@hotmail.com".to_string(), + "Mariiia".to_string(), + "7".to_string(), + ], restricciones: Some("id = 4".to_string()), }); From c92afb3b9ee787bae4ba7f01a84815653fb87879 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 20:21:45 -0300 Subject: [PATCH 45/54] agrego tests de integracion antes de seguir modularizando --- tests/tests_integracion.rs | 166 ++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 1 deletion(-) diff --git a/tests/tests_integracion.rs b/tests/tests_integracion.rs index cb8cfd8..c5ddadc 100644 --- a/tests/tests_integracion.rs +++ b/tests/tests_integracion.rs @@ -34,7 +34,7 @@ fn select_si_tabla_no_existe_devuelve_invalid_table() { #[test] fn select_si_no_existe_columna_en_order_by_devuelve_invalidcolumn() { - let ruta_carpeta = "./tests_integracion_3"; + let ruta_carpeta = "./tests_integracion_2"; let archivo_tablas = format!("{}/clientes.csv", ruta_carpeta); fs::create_dir_all(ruta_carpeta).unwrap(); @@ -60,3 +60,167 @@ fn select_si_no_existe_columna_en_order_by_devuelve_invalidcolumn() { fs::remove_file(archivo_tablas).unwrap(); fs::remove_dir_all(ruta_carpeta).unwrap(); } + +#[test] +fn ejemplo_1_consigna() { + let ruta_carpeta = "./tests_integracion_3"; + let archivo_tablas = format!("{}/ordenes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let _file = File::create(&archivo_tablas).unwrap(); + + let mut file = File::create(&archivo_tablas).unwrap(); + writeln!(file, "id,id_cliente,producto,cantidad").unwrap(); + writeln!(file, "101,1,Laptop,1").unwrap(); + writeln!(file, "103,1,Monitor,1").unwrap(); + writeln!(file, "102,2,Teléfono,2").unwrap(); + writeln!(file, "104,3,Teclado,1").unwrap(); + writeln!(file, "105,4,Mouse,2").unwrap(); + writeln!(file, "106,5,Impresora,1").unwrap(); + writeln!(file, "107,6,Altavoces,1").unwrap(); + writeln!(file, "108,4,Auriculares,1").unwrap(); + writeln!(file, "109,5,Laptop,1").unwrap(); + writeln!(file, "110,6,Teléfono,2").unwrap(); + + let consulta = "SELECT id, producto, id_cliente +FROM ordenes +WHERE cantidad > 1; +"; + + let output = Command::new("cargo") + .args(&["run", "--", &ruta_carpeta, consulta]) + .output() + .expect("un output"); + + assert!(output.status.success()); + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("id,producto,id_cliente\n102,Teléfono,2\n105,Mouse,4\n110,Teléfono,6\n")); + + fs::remove_file(archivo_tablas).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} + +#[test] +fn ejemplo_2_consigna() { + let ruta_carpeta = "./tests_integracion_4"; + let archivo_tablas = format!("{}/clientes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let _file = File::create(&archivo_tablas).unwrap(); + + let mut file = File::create(&archivo_tablas).unwrap(); + writeln!(file, "id,nombre,apellido,email").unwrap(); + writeln!(file, "1,Juan,Pérez,juan.perez@email.com").unwrap(); + writeln!(file, "2,Ana,López,ana.lopez@email.com").unwrap(); + writeln!(file, "3,Carlos,Gómez,carlos.gomez@email.com").unwrap(); + writeln!(file, "4,María,Rodríguez,maria.rodriguez@email.com").unwrap(); + writeln!(file, "5,José,López,jose.lopez@email.com").unwrap(); + writeln!(file, "6,Laura,Fernández,laura.fernandez@email.com").unwrap(); + + let consulta = "SELECT id, nombre, email +FROM clientes +WHERE apellido = 'López' +ORDER BY email DESC; +"; + + let output = Command::new("cargo") + .args(&["run", "--", &ruta_carpeta, consulta]) + .output() + .expect("un output"); + + assert!(output.status.success()); + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("id,nombre,email\n5,José,jose.lopez@email.com\n2,Ana,ana.lopez@email.com\n")); + + fs::remove_file(archivo_tablas).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} + +#[test] +fn ejemplo_3_consigna() { + let ruta_carpeta = "./tests_integracion_5"; + let ruta_archivo = format!("{}/clientes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let mut file = File::create(&ruta_archivo).unwrap(); + writeln!(file, "id,nombre,apellido,email").unwrap(); + writeln!(file, "1,Juan,Pérez,juan.perez@email.com").unwrap(); + writeln!(file, "2,Ana,López,ana.lopez@email.com").unwrap(); + writeln!(file, "3,Carlos,Gómez,carlos.gomez@email.com").unwrap(); + writeln!(file, "4,María,Rodríguez,maria.rodriguez@email.com").unwrap(); + writeln!(file, "5,José,López,jose.lopez@email.com").unwrap(); + writeln!(file, "6,Laura,Fernández,laura.fernandez@email.com").unwrap(); + + let consulta = "UPDATE clientes +SET email = 'mrodriguez@hotmail.com' +WHERE id = 4;"; + + let _output = Command::new("cargo") + .args(&["run", "--", &ruta_carpeta, consulta]) + .output() + .expect("un output"); + + let contenido_actualizado = fs::read_to_string(&ruta_archivo).unwrap(); + let esperado = "id,nombre,apellido,email +1,Juan,Pérez,juan.perez@email.com +2,Ana,López,ana.lopez@email.com +3,Carlos,Gómez,carlos.gomez@email.com +4,María,Rodríguez,mrodriguez@hotmail.com +5,José,López,jose.lopez@email.com +6,Laura,Fernández,laura.fernandez@email.com\n"; + assert_eq!(contenido_actualizado, esperado); + + fs::remove_file(&ruta_archivo).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} + +#[test] +fn ejemplo_4_consigna() { + let ruta_carpeta = "./tests_integracion_6"; + let ruta_archivo = format!("{}/ordenes.csv", ruta_carpeta); + + fs::create_dir_all(ruta_carpeta).unwrap(); + + let mut file = File::create(&ruta_archivo).unwrap(); + writeln!(file, "id,id_cliente,producto,cantidad").unwrap(); + writeln!(file, "101,1,Laptop,1").unwrap(); + writeln!(file, "103,1,Monitor,1").unwrap(); + writeln!(file, "102,2,Teléfono,2").unwrap(); + writeln!(file, "104,3,Teclado,1").unwrap(); + writeln!(file, "105,4,Mouse,2").unwrap(); + writeln!(file, "106,5,Impresora,1").unwrap(); + writeln!(file, "107,6,Altavoces,1").unwrap(); + writeln!(file, "108,4,Auriculares,1").unwrap(); + writeln!(file, "109,5,Laptop,1").unwrap(); + writeln!(file, "110,6,Teléfono,2").unwrap(); + + let consulta = "INSERT INTO ordenes (id, id_cliente, producto, cantidad) VALUES (111, 6, 'Laptop', 3);"; + + let _output = Command::new("cargo") + .args(&["run", "--", &ruta_carpeta, consulta]) + .output() + .expect("un output"); + + let contenido_actualizado = fs::read_to_string(&ruta_archivo).unwrap(); + let esperado = "id,id_cliente,producto,cantidad +101,1,Laptop,1 +103,1,Monitor,1 +102,2,Teléfono,2 +104,3,Teclado,1 +105,4,Mouse,2 +106,5,Impresora,1 +107,6,Altavoces,1 +108,4,Auriculares,1 +109,5,Laptop,1 +110,6,Teléfono,2 +111,6,Laptop,3\n"; + assert_eq!(contenido_actualizado, esperado); + + fs::remove_file(&ruta_archivo).unwrap(); + fs::remove_dir_all(ruta_carpeta).unwrap(); +} \ No newline at end of file From eff4e51ed9f75e8d8da81c99afb4dc45362d6e3e Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 20:57:07 -0300 Subject: [PATCH 46/54] modularizo el parser --- src/parser.rs | 111 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 24 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 3cd5f97..981c552 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,3 +1,19 @@ +//! # Módulo de Parsing de Consultas SQL +//! +//! Este módulo se encarga de analizar (parsear) consultas SQL y convertirlas en estructuras de datos +//! que el programa puede manipular. Admite las operaciones básicas de SQL como `SELECT`, `INSERT`, +//! `UPDATE` y `DELETE`. +//! +//! ## Componentes +//! +//! Este módulo está compuesto por las siguientes funciones principales: +//! +//! - `parsear_consulta`: Función principal que detecta el tipo de comando SQL y delega el parsing +//! al submódulo correspondiente. +//! - `parse_select`: Analiza las consultas `SELECT` y extrae las columnas, tabla y condiciones. +//! - `parse_insert`: Analiza las consultas `INSERT` y extrae la tabla y los valores a insertar. +//! - `parse_update`: Analiza las consultas `UPDATE` y extrae las columnas a actualizar y sus valores. +//! - `parse_delete`: Analiza las consultas `DELETE` y extrae las condiciones para eliminar registros. use crate::comandos::Comando; use crate::delete::Delete; use crate::errores::SqlError; @@ -5,7 +21,33 @@ use crate::insert::Insert; use crate::select::Select; use crate::update::Update; -// leo la consulta SQL y devuelve un comando específico. +/// Parsea una consulta SQL en una estructura `Comando` correspondiente. +/// +/// Esta función toma una consulta SQL en forma de cadena y determina su tipo (SELECT, INSERT, UPDATE, DELETE). +/// Luego, según el tipo de comando detectado, delega el parsing del resto de la consulta a la función correspondiente +/// (`parse_select`, `parse_insert`, `parse_update`, `parse_delete`). +/// +/// # Argumentos +/// +/// * `consulta` - Una cadena que contiene la consulta SQL a parsear. Se asume que las palabras clave SQL +/// (como `SELECT`, `INSERT INTO`, `UPDATE`, `DELETE FROM`) vienen en mayúsculas. +/// +/// # Retornos +/// +/// Retorna un `Result` que contiene un `Comando` si la consulta se parsea correctamente, o un `SqlError` en caso de error. +/// +/// # Errores +/// +/// Esta función retornará un `SqlError::InvalidSintax` si el comando SQL no es reconocido, o si hay algún problema +/// al intentar parsear el resto de la consulta con las funciones específicas. +/// +/// # Ejemplo +/// +/// ``` +/// let consulta = "SELECT id, nombre FROM ordenes WHERE cantidad > 1"; +/// ``` +/// +/// En este ejemplo, `parsear_consulta` devolverá un `Comando::Select` con la estructura `Select` correspondiente. pub fn parsear_consulta(consulta: &str) -> Result { //Si pongo toda la consulta enn minusucula, poerdo las mayuduclas de las tablas, columnas y APELLIDOCS //INTENMTE CAMBIAR LA LOGICA PERO SE ME HIZO MUUUY COMPLICADO SOLO COMPARAR ESAS PALABRAS @@ -104,13 +146,7 @@ fn separar_tabla_restricciones_ordenamiento(resto: &str) -> (String, Option Result { - // saco ; y saltos de linea - let resto = resto.trim_end_matches(';').trim(); - let resto = resto - .replace("\n", " ") - .replace("\r", " ") - .trim() - .to_string(); + let resto = sacar_punto_y_cona_saltos_linea(resto); // Separo SET y WHERE let partes: Vec<&str> = resto.splitn(2, " SET ").collect(); @@ -156,12 +192,7 @@ fn parse_update(resto: &str) -> Result { valores.push(valor); } - // Parseo restricciones, si hay - let restricciones = if set_where_partes.len() == 2 { - Some(set_where_partes[1].trim().to_string()) - } else { - None - }; + let restricciones = parsear_restricciones_si_hay(set_where_partes); Ok(Update { columnas, @@ -170,6 +201,28 @@ fn parse_update(resto: &str) -> Result { restricciones, }) } + +fn parsear_restricciones_si_hay(set_where_partes: Vec<&str>) -> Option { + // Parseo restricciones, si hay + let restricciones = if set_where_partes.len() == 2 { + Some(set_where_partes[1].trim().to_string()) + } else { + None + }; + restricciones +} + +fn sacar_punto_y_cona_saltos_linea(resto: &str) -> String { + // saco ; y saltos de linea + let resto = resto.trim_end_matches(';').trim(); + let resto = resto + .replace("\n", " ") + .replace("\r", " ") + .trim() + .to_string(); + resto +} + fn parse_insert(resto: &str) -> Result { let resto = resto.trim(); @@ -199,15 +252,30 @@ fn parse_insert(resto: &str) -> Result { let tabla = tabla_y_columnas[..paos_parentesis_abre].trim(); let columnas_str = &tabla_y_columnas[paos_parentesis_abre + 1..pos_parentesis_cierra].trim(); - let columnas: Vec = columnas_str - .split(',') - .map(|col| col.trim().to_string()) - .collect(); + let columnas = mapear_columnas(columnas_str); let values_str = &resto[pos_values + 6..].trim(); let values_str = values_str.trim_end_matches(';'); //Divido los valores haciendo que se respeten las comillas simples + let valores = dividir_valores(values_str); + + Ok(Insert { + tabla: tabla.to_string(), + columnas, + valores, + }) +} + +fn mapear_columnas(columnas_str: &&str) -> Vec { + let columnas: Vec = columnas_str + .split(',') + .map(|col| col.trim().to_string()) + .collect(); + columnas +} + +fn dividir_valores(values_str: &str) -> Vec> { let valores: Vec> = values_str .split("),") .map(|val_list| { @@ -229,12 +297,7 @@ fn parse_insert(resto: &str) -> Result { .collect() }) .collect(); - - Ok(Insert { - tabla: tabla.to_string(), - columnas, - valores, - }) + valores } fn parse_delete(resto: &str) -> Result { From 85ca0a8f17eb3b83310ac07d204b5a6ddc2516d1 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 20:57:49 -0300 Subject: [PATCH 47/54] cargo fmt --- src/parser.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 981c552..6d745e6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -108,10 +108,9 @@ fn parse_select(resto: &str) -> Result { .collect(); //Separo la tabla y manejar posibles restricciones y ordenamiento - let (tabla, restricciones, ordenamiento) = - separar_tabla_restricciones_ordenamiento(resto); + let (tabla, restricciones, ordenamiento) = separar_tabla_restricciones_ordenamiento(resto); - Ok(Select { + Ok(Select { columnas, tabla, restricciones, @@ -119,7 +118,9 @@ fn parse_select(resto: &str) -> Result { }) } -fn separar_tabla_restricciones_ordenamiento(resto: &str) -> (String, Option, Option) { +fn separar_tabla_restricciones_ordenamiento( + resto: &str, +) -> (String, Option, Option) { if let Some((tabla, resto)) = resto.split_once("WHERE") { // Si hay WHERE, manejamos restricciones y ordenamiento let (restricciones, ordenamiento) = From a429960e3f653b1c41e367ef7c8a6795c406d94b Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 21:44:37 -0300 Subject: [PATCH 48/54] modularizo y escribo docu de delete y de comnados --- src/comandos.rs | 13 +++ src/delete.rs | 172 ++++++++++++++++++++++++++++--------- tests/tests_integracion.rs | 12 ++- 3 files changed, 151 insertions(+), 46 deletions(-) diff --git a/src/comandos.rs b/src/comandos.rs index 336b47a..122e531 100644 --- a/src/comandos.rs +++ b/src/comandos.rs @@ -1,9 +1,14 @@ +//! Módulo para gestionar la ejecución de comandos SQL. +//! +//! Este módulo define un `enum` llamado `Comando` que puede representar operaciones SQL de tipo `SELECT`, `INSERT`, `UPDATE` y `DELETE`. +//! También proporciona métodos para ejecutar estos comandos y para imprimir los resultados de una consulta `SELECT`. use crate::delete::Delete; use crate::errores::SqlError; use crate::insert::Insert; use crate::select::Select; use crate::update::Update; +/// Enum que representa los diferentes comandos SQL soportados. pub enum Comando { Select(Select), Insert(Insert), @@ -20,6 +25,14 @@ impl Comando { Comando::Delete(delete) => delete.ejecutar(ruta_carpeta), } } + /// Imprime los resultados de una consulta `SELECT`. + /// + /// Esta función imprime los resultados de una consulta si el comando es un `Select`. Para otros tipos de comando (`Insert`, `Update`, `Delete`), + /// la función no hace mnada + /// + /// # Parámetros + /// + /// - `results`: Una referencia a un vector de vectores de `String` que contiene los resultados de la consulta. pub fn imprimir_resultados(&self, results: &[Vec]) { match self { Comando::Select(_select) => Select::imprimir_resultados(results), diff --git a/src/delete.rs b/src/delete.rs index db5d069..16c203f 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -1,26 +1,43 @@ +//! Módulo para gestionar la consulta delete +//! +//! Este módulo define el `struct` `Delete` que representa un comando SQL de eliminación de registros en una tabla CSV. +//! Proporciona métodos para ejecutar la eliminación basada en restricciones y reemplazar el archivo original con una versión modificada. +//! Para ello crea un archivo temporal donde guarda los registros que no deben ser eliminados use crate::errores::SqlError; use std::fs::{self, File}; -use std::io::{BufRead, BufReader, Write}; +use std::io::{BufRead, BufReader, Lines, Write}; use std::iter::Peekable; use std::str::Chars; +/// Estructura que representa un comando SQL de eliminación. +/// Tabla es la tabla con la que se esta trabjando +/// las restricciones del WHERE pub struct Delete { pub tabla: String, pub restricciones: Option, } impl Delete { + /// Ejecuta el comando de eliminación en la tabla especificada. + /// + /// Lee el archivo CSV correspondiente a la tabla, aplica las restricciones para determinar qué registros eliminar, + /// y escribe los registros restantes en un archivo temporal. Luego, reemplaza el archivo original con el archivo temporal. + /// + /// # Parámetros + /// + /// - `ruta_carpeta_tablas`: Ruta del directorio donde se encuentran los archivos CSV de las tablas. + /// + /// # Retorna + /// + /// Un `Result` que contiene un `Vec>` con las filas que cumplieron con las restricciones y fueron eliminadas, + /// o un `SqlError` en caso de error. pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, SqlError> { let ruta_archivo = format!("{}/{}.csv", ruta_carpeta_tablas, self.tabla); let ruta_temporal = format!("{}/{}.tmp", ruta_carpeta_tablas, self.tabla); - // Abro archivo tabla - let archivo = match File::open(&ruta_archivo) { - Ok(file) => file, - Err(_err) => { - return Err(SqlError::InvalidTable); - } + let archivo = match Self::abrir_archivo_tabla(&ruta_archivo) { + Ok(value) => value, + Err(value) => return value, }; - let reader = BufReader::new(archivo); // Creo un archivo temporal @@ -32,30 +49,17 @@ impl Delete { )); } }; - // encabezadosss let mut lineas = reader.lines(); - let encabezados = match lineas.next() { - Some(Ok(line)) => line, - Some(Err(_err)) => { - return Err(SqlError::InvalidTable); - } - None => { - // archivo vacío - return Err(SqlError::InvalidTable); - } + let encabezados = match Self::hallar_encabezados(&mut lineas) { + Ok(value) => value, + Err(value) => return value, }; + let vector_encabezados = Self::armar_vector_encabezados(encabezados); - let encabezados: Vec = encabezados - .split(',') - .map(|s| s.trim().to_string()) - .collect(); - - // Escribo encabezados al archivo de salida - if let Err(_err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { - return Err(SqlError::Error( - "Error al escribir encabezados en el archivo temporal:".into(), - )); + if let Some(value) = Self::escribir_encabezados(&mut archivo_temporal, &vector_encabezados) + { + return value; } let mut filas_resultantes = Vec::new(); @@ -71,17 +75,13 @@ impl Delete { let fila: Vec = linea.split(',').map(|s| s.trim().to_string()).collect(); - if let Err(err) = self.aplicar_restricciones(&fila, &encabezados) { - // Si aplicar_restricciones devuelve un error, elimino el archivo temporal - if let Err(_del_err) = std::fs::remove_file(&ruta_temporal) { - return Err(SqlError::Error( - "Error al eliminar el archivo temporal".to_string(), - )); - } - return Err(err); + if let Some(value) = + self.chequear_restricciones(&ruta_temporal, &vector_encabezados, &fila) + { + return value; } - if !self.aplicar_restricciones(&fila, &encabezados)? { + if !self.aplicar_restricciones(&fila, &vector_encabezados)? { // Si no hay restricciones o si la fila no cumple con las restricciones, // la escribimos en el archivo temporal if let Err(_err) = writeln!(archivo_temporal, "{}", linea) { @@ -95,17 +95,95 @@ impl Delete { } } + if let Some(value) = Self::reemplazar_original_por_temp(&ruta_archivo, &ruta_temporal) { + return value; + } + + Ok(filas_resultantes) + } + + fn hallar_encabezados( + lineas: &mut Lines>, + ) -> Result>, SqlError>> { + let encabezados = match lineas.next() { + Some(Ok(line)) => line, + Some(Err(_err)) => { + return Err(Err(SqlError::InvalidTable)); + } + None => { + // archivo vacío + return Err(Err(SqlError::InvalidTable)); + } + }; + Ok(encabezados) + } + + fn chequear_restricciones( + &self, + ruta_temporal: &String, + encabezados: &[String], + fila: &[String], + ) -> Option>, SqlError>> { + if let Err(err) = self.aplicar_restricciones(fila, encabezados) { + // Si aplicar_restricciones devuelve un error, elimino el archivo temporal + if let Err(_del_err) = std::fs::remove_file(ruta_temporal) { + return Some(Err(SqlError::Error( + "Error al eliminar el archivo temporal".to_string(), + ))); + } + return Some(Err(err)); + } + None + } + + fn reemplazar_original_por_temp( + ruta_archivo: &String, + ruta_temporal: &String, + ) -> Option>, SqlError>> { // Reemplazo el archivo original con el archivo temporal - if let Err(_err) = fs::rename(&ruta_temporal, &ruta_archivo) { - return Err(SqlError::Error( + if let Err(_err) = fs::rename(ruta_temporal, ruta_archivo) { + return Some(Err(SqlError::Error( "Error al reemplazar el archivo original con el archivo temporal".to_string(), - )); + ))); } + None + } - Ok(filas_resultantes) + fn armar_vector_encabezados(encabezados: String) -> Vec { + let encabezados: Vec = encabezados + .split(',') + .map(|s| s.trim().to_string()) + .collect(); + encabezados } - pub fn aplicar_restricciones( + fn escribir_encabezados( + archivo_temporal: &mut File, + encabezados: &[String], + ) -> Option>, SqlError>> { + // Escribo encabezados al archivo de salida + if let Err(_err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { + return Some(Err(SqlError::Error( + "Error al escribir encabezados en el archivo temporal:".into(), + ))); + } + None + } + + fn abrir_archivo_tabla( + ruta_archivo: &String, + ) -> Result>, SqlError>> { + // Abro archivo tabla + let archivo = match File::open(ruta_archivo) { + Ok(file) => file, + Err(_err) => { + return Err(Err(SqlError::InvalidTable)); + } + }; + Ok(archivo) + } + + fn aplicar_restricciones( &self, registro: &[String], encabezados: &[String], @@ -341,6 +419,16 @@ impl Delete { let valor = valor.trim().trim_matches('\''); + Self::ejecutar_comparacion(®istro, encabezados, columna, operador, valor) + } + + fn ejecutar_comparacion( + registro: &&[String], + encabezados: &[String], + columna: &&str, + operador: &str, + valor: &str, + ) -> Result { // Encuentro el índice de la columna, o devuelvo un error si no se encuentra if let Some(indice) = encabezados.iter().position(|enc| enc == columna) { let valor_registro = ®istro[indice]; diff --git a/tests/tests_integracion.rs b/tests/tests_integracion.rs index c5ddadc..cc5b61c 100644 --- a/tests/tests_integracion.rs +++ b/tests/tests_integracion.rs @@ -96,7 +96,9 @@ WHERE cantidad > 1; assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("id,producto,id_cliente\n102,Teléfono,2\n105,Mouse,4\n110,Teléfono,6\n")); + assert!( + stdout.contains("id,producto,id_cliente\n102,Teléfono,2\n105,Mouse,4\n110,Teléfono,6\n") + ); fs::remove_file(archivo_tablas).unwrap(); fs::remove_dir_all(ruta_carpeta).unwrap(); @@ -134,7 +136,8 @@ ORDER BY email DESC; assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("id,nombre,email\n5,José,jose.lopez@email.com\n2,Ana,ana.lopez@email.com\n")); + assert!(stdout + .contains("id,nombre,email\n5,José,jose.lopez@email.com\n2,Ana,ana.lopez@email.com\n")); fs::remove_file(archivo_tablas).unwrap(); fs::remove_dir_all(ruta_carpeta).unwrap(); @@ -199,7 +202,8 @@ fn ejemplo_4_consigna() { writeln!(file, "109,5,Laptop,1").unwrap(); writeln!(file, "110,6,Teléfono,2").unwrap(); - let consulta = "INSERT INTO ordenes (id, id_cliente, producto, cantidad) VALUES (111, 6, 'Laptop', 3);"; + let consulta = + "INSERT INTO ordenes (id, id_cliente, producto, cantidad) VALUES (111, 6, 'Laptop', 3);"; let _output = Command::new("cargo") .args(&["run", "--", &ruta_carpeta, consulta]) @@ -223,4 +227,4 @@ fn ejemplo_4_consigna() { fs::remove_file(&ruta_archivo).unwrap(); fs::remove_dir_all(ruta_carpeta).unwrap(); -} \ No newline at end of file +} From 3c0ce524dcc9510039f5a0a7771b32120b15d3c5 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 21:59:56 -0300 Subject: [PATCH 49/54] modularizo insert y agrego docu --- src/errores.rs | 5 ++ src/insert.rs | 133 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 102 insertions(+), 36 deletions(-) diff --git a/src/errores.rs b/src/errores.rs index f1ef303..46fbffc 100644 --- a/src/errores.rs +++ b/src/errores.rs @@ -1,3 +1,8 @@ +//! Módulo para definir errores específicos +//! +//! Este módulo define el `enum` `SqlError` que representa los diferentes tipos de errores que pueden ocurrir durante +//! el procesamiento de comandos SQL. +//! También implementa el trait `Display` para poder mostrar correctamente estos errores. use std::fmt; #[derive(Debug)] diff --git a/src/insert.rs b/src/insert.rs index ace48f5..6a6e624 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -1,7 +1,12 @@ +//! Módulo para gestionar la consulta insert +//! +//! Este módulo define el `struct` `Insert` Proporciona métodos para ejecutar la inserción de +//! nuevos registros en la tabla, verificando la validez de las columnas +//! y los encabezados del archivo CSV, y agregando las nuevas filas al final del archivo. use crate::errores::SqlError; use std::fs::{File, OpenOptions}; use std::io::{BufRead, BufReader, BufWriter, Write}; -use std::path::Path; +use std::path::{Path, PathBuf}; pub struct Insert { pub tabla: String, @@ -10,15 +15,19 @@ pub struct Insert { } impl Insert { + /// Ejecuta el comando insert + /// + /// Abre el archivo CSV correspondiente a la tabla, verifica que las columnas de la inserción coincidan con las del archivo, + /// y agrega las nuevas filas al final del archivo. Retorna las filas que se han insertado o un error en caso de fallo. + /// # Retorna + /// Un `Result` que contiene un `Vec>` con las filas que han sido insertadas, + /// o un `SqlError` en caso de error. pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, SqlError> { let archivo_csv = Path::new(ruta_carpeta_tablas).join(format!("{}.csv", self.tabla)); - // Leo archivo para obtener los encabezados - let file = match File::open(&archivo_csv) { - Ok(file) => file, - Err(_err) => { - return Err(SqlError::InvalidTable); - } + let file = match Self::abrir_archivo(&archivo_csv) { + Ok(value) => value, + Err(value) => return value, }; let mut reader = BufReader::new(file); @@ -28,25 +37,12 @@ impl Insert { return Err(SqlError::InvalidTable); } - let encabezados: Vec = encabezados - .trim() - .split(',') - .map(|col| col.trim().to_string()) - .collect(); + let vector_encabezados = Self::armar_vector_encabezados(&mut encabezados); - // Verifio que las columnas de la consulta existan en el archivo - let mut columnas_indices = Vec::with_capacity(self.columnas.len()); - for (index, header) in encabezados.iter().enumerate() { - if self.columnas.contains(header) { - columnas_indices.push(index); - } else { - return Err(SqlError::InvalidColumn); - } - } - //y que no haya columnas de mas ni de menos - if columnas_indices.len() != self.columnas.len() { - return Err(SqlError::InvalidColumn); - } + let columnas_indices = match self.chequear_columnas_estan_ok(&vector_encabezados) { + Ok(value) => value, + Err(value) => return value, + }; // Abro el archivo en modo append para agregar los nuevos let file = match OpenOptions::new().append(true).open(&archivo_csv) { @@ -57,29 +53,94 @@ impl Insert { }; let mut writer = BufWriter::new(file); - let mut filas_insertadas = Vec::with_capacity(self.valores.len()); + if let Some(value) = self.escribir_archivo( + vector_encabezados, + columnas_indices, + &mut writer, + &mut filas_insertadas, + ) { + return value; + } + // devuelvo pa q no tire error + Ok(filas_insertadas) + } + + fn abrir_archivo(archivo_csv: &PathBuf) -> Result>, SqlError>> { + // Leo archivo para obtener los encabezados + let file = match File::open(archivo_csv) { + Ok(file) => file, + Err(_err) => { + return Err(Err(SqlError::InvalidTable)); + } + }; + Ok(file) + } + + fn escribir_archivo( + &self, + vector_encabezados: Vec, + columnas_indices: Vec, + writer: &mut BufWriter, + filas_insertadas: &mut Vec>, + ) -> Option>, SqlError>> { // Escribo archivo for fila in &self.valores { - let mut fila_nueva = vec!["".to_string(); encabezados.len()]; + let mut fila_nueva = vec!["".to_string(); vector_encabezados.len()]; for (i, col_index) in columnas_indices.iter().enumerate() { if i < fila.len() { fila_nueva[*col_index] = fila[i].to_string(); } } - - //la agrego - if let Err(_err) = writeln!(writer, "{}", fila_nueva.join(",")) { - return Err(SqlError::Error( - "Error al escribir lineas en el archivo:".into(), - )); + if let Some(value) = Self::escribir_linea_en_archivo(writer, &mut fila_nueva) { + return Some(value); } - filas_insertadas.push(fila_nueva); } - // devuelvo pa q no tire error - Ok(filas_insertadas) + None + } + + fn escribir_linea_en_archivo( + writer: &mut BufWriter, + fila_nueva: &mut [String], + ) -> Option>, SqlError>> { + //la agrego + if let Err(_err) = writeln!(writer, "{}", fila_nueva.join(",")) { + return Some(Err(SqlError::Error( + "Error al escribir lineas en el archivo:".into(), + ))); + } + None + } + + fn chequear_columnas_estan_ok( + &self, + vector_encabezados: &[String], + ) -> Result, Result>, SqlError>> { + // Verifio que las columnas de la consulta existan en el archivo + let mut columnas_indices = Vec::with_capacity(self.columnas.len()); + for (index, header) in vector_encabezados.iter().enumerate() { + if self.columnas.contains(header) { + columnas_indices.push(index); + } else { + return Err(Err(SqlError::InvalidColumn)); + } + } + //y que no haya columnas de mas ni de menos + if columnas_indices.len() != self.columnas.len() { + return Err(Err(SqlError::InvalidColumn)); + } + Ok(columnas_indices) + } + + fn armar_vector_encabezados(encabezados: &mut str) -> Vec { + let encabezados: Vec = encabezados + .trim() + .split(',') + .map(|col| col.trim().to_string()) + .collect(); + encabezados } } From be1e88860c71fb9fdfb9e666e8aee143010ef307 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 22:28:53 -0300 Subject: [PATCH 50/54] modularizo select --- src/delete.rs | 1 + src/select.rs | 113 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 75 insertions(+), 39 deletions(-) diff --git a/src/delete.rs b/src/delete.rs index 16c203f..ab83733 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -38,6 +38,7 @@ impl Delete { Ok(value) => value, Err(value) => return value, }; + let reader = BufReader::new(archivo); // Creo un archivo temporal diff --git a/src/select.rs b/src/select.rs index 65e8687..fc6f002 100644 --- a/src/select.rs +++ b/src/select.rs @@ -28,11 +28,7 @@ impl Select { //archivo vacio return Err(SqlError::InvalidTable); } - let vector_encabezados: Vec = linea_encabezados - .trim_end() - .split(',') - .map(|s| s.to_string()) - .collect(); + let vector_encabezados = Self::armar_vector_encabezados(&mut linea_encabezados); let mut resultado = Vec::new(); @@ -40,17 +36,38 @@ impl Select { self.agregar_encabezados(&vector_encabezados, &mut resultado, &mut encabezados_select)?; + self.leer_linea_a_linea( + &mut reader, + &vector_encabezados, + &mut resultado, + &mut encabezados_select, + )?; + //Ordenamiento (ORDER BY) + if let Some(ref ordenamiento) = self.ordenamiento { + self.aplicar_ordenamiento(&mut resultado, ordenamiento, &encabezados_select)?; + }; + + Ok(resultado) + } + + fn leer_linea_a_linea( + &self, + reader: &mut BufReader, + vector_encabezados: &[String], + resultado: &mut Vec>, + encabezados_select: &mut Vec, + ) -> Result<(), SqlError> { // leo linea a linea for linea in reader.lines() { let line = linea.map_err(|_err| SqlError::InvalidTable)?; let registro: Vec = line.split(',').map(|s| s.to_string()).collect(); // Aplico restricciones (WHERE) - if self.aplicar_restricciones(®istro, &vector_encabezados)? { + if self.aplicar_restricciones(®istro, vector_encabezados)? { // Crear un vector para las columnas seleccionadas let mut columnas_select = Vec::new(); - for col in &encabezados_select { + for col in &mut *encabezados_select { // Encuentro índice de la columna en los encabezados match vector_encabezados .iter() @@ -74,12 +91,16 @@ impl Select { resultado.push(columnas_select); } } - //Ordenamiento (ORDER BY) - if let Some(ref ordenamiento) = self.ordenamiento { - self.aplicar_ordenamiento(&mut resultado, ordenamiento, &encabezados_select)?; - }; + Ok(()) + } - Ok(resultado) + fn armar_vector_encabezados(linea_encabezados: &mut str) -> Vec { + let vector_encabezados: Vec = linea_encabezados + .trim_end() + .split(',') + .map(|s| s.to_string()) + .collect(); + vector_encabezados } fn aplicar_ordenamiento( @@ -88,34 +109,10 @@ impl Select { ordenamiento: &str, encabezados_select: &[String], ) -> Result<(), SqlError> { - //Parseo los criterios de ordenamiento - let criterios: Vec<(String, String)> = ordenamiento - .split(',') - .map(|criterio| { - let (col, dir) = if let Some((col, dir)) = criterio.trim().split_once(' ') { - (col.trim().to_string(), dir.trim().to_uppercase()) - } else { - (criterio.trim().to_string(), "ASC".to_string()) - }; - (col, dir) - }) - .collect(); + let criterios = Self::parsear_criterios_ordenamiento(ordenamiento); - //Me Aseguro que todas las columnas de los criterios existen en encabezados_select - //y mapeo los indices - let indice_direccion_criterios: Vec<(usize, String)> = criterios - .iter() - .map(|(columna, direccion)| { - if let Some(indice) = encabezados_select - .iter() - .position(|encabezado| encabezado == columna) - { - Ok((indice, direccion.to_string())) - } else { - Err(SqlError::InvalidColumn) - } - }) - .collect::, SqlError>>()?; + let indice_direccion_criterios = + Self::chequear_columnas_y_mapear_indices(encabezados_select, criterios)?; //Ordeno las filas usando los criterios resultado[1..].sort_by(|fila_1, fila_2| { @@ -141,6 +138,44 @@ impl Select { Ok(()) } + fn chequear_columnas_y_mapear_indices( + encabezados_select: &[String], + criterios: Vec<(String, String)>, + ) -> Result, SqlError> { + //Me Aseguro que todas las columnas de los criterios existen en encabezados_select + //y mapeo los indices + let indice_direccion_criterios: Vec<(usize, String)> = criterios + .iter() + .map(|(columna, direccion)| { + if let Some(indice) = encabezados_select + .iter() + .position(|encabezado| encabezado == columna) + { + Ok((indice, direccion.to_string())) + } else { + Err(SqlError::InvalidColumn) + } + }) + .collect::, SqlError>>()?; + Ok(indice_direccion_criterios) + } + + fn parsear_criterios_ordenamiento(ordenamiento: &str) -> Vec<(String, String)> { + //Parseo los criterios de ordenamiento + let criterios: Vec<(String, String)> = ordenamiento + .split(',') + .map(|criterio| { + let (col, dir) = if let Some((col, dir)) = criterio.trim().split_once(' ') { + (col.trim().to_string(), dir.trim().to_uppercase()) + } else { + (criterio.trim().to_string(), "ASC".to_string()) + }; + (col, dir) + }) + .collect(); + criterios + } + fn agregar_encabezados( &self, vector_encabezados: &[String], From a48de5b378aa527ec5fed8c344e2e2f90debd864 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 23:24:35 -0300 Subject: [PATCH 51/54] modularizo update y agrego docu --- src/insert.rs | 5 +- src/update.rs | 299 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 215 insertions(+), 89 deletions(-) diff --git a/src/insert.rs b/src/insert.rs index 6a6e624..65b59d7 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -1,6 +1,6 @@ //! Módulo para gestionar la consulta insert //! -//! Este módulo define el `struct` `Insert` Proporciona métodos para ejecutar la inserción de +//! Este módulo define el `struct` `Insert` y roporciona métodos para ejecutar la inserción de //! nuevos registros en la tabla, verificando la validez de las columnas //! y los encabezados del archivo CSV, y agregando las nuevas filas al final del archivo. use crate::errores::SqlError; @@ -8,6 +8,9 @@ use std::fs::{File, OpenOptions}; use std::io::{BufRead, BufReader, BufWriter, Write}; use std::path::{Path, PathBuf}; +///Columnas: representa las columnas de los valores que se desean insertar +/// Valores: Listado de registros a insertar +///Tabla: tabla pub struct Insert { pub tabla: String, pub columnas: Vec, diff --git a/src/update.rs b/src/update.rs index 1f9131b..d5aa068 100644 --- a/src/update.rs +++ b/src/update.rs @@ -1,10 +1,19 @@ +/// Módulo para gestionar el comando update +/// Este módulo proporciona una estructura y métodos para realizar la operacio update +/// Lee el archivo aplica las restricciones y utilizando un arhcivo temporal modifica +/// la tabla original use crate::errores::SqlError; use std::fs::File; -use std::io::{BufRead, BufReader, Write}; +use std::io::{BufRead, BufReader, Lines, Write}; use std::iter::Peekable; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::str::Chars; +/// `Update` incluye la información necesaria para identificar el archivo CSV, +/// Columans: columnas que se desean actualizar +/// Valores: los nuevos valores para esas columnas +/// Restricciones: restricciones que determinan qué registros deben ser actualizados. Lo +/// que viene dsps del Where pub struct Update { pub columnas: Vec, pub tabla: String, @@ -13,132 +22,246 @@ pub struct Update { } impl Update { + /// Ejecuta update + /// Lee el archivo CSV, aplica las restricciones, actualiza los registros que cumplen con + /// las restricciones, y guarda los cambios en el archivo. Los encabezados del archivo + /// se conservan y se escribe un archivo temporal que reemplaza al archivo original al + /// final del proceso. + /// + /// # Retorna + /// + /// * `Result>, SqlError>` - Un `Result` que contiene un `Vec` de registros actualizados + /// en caso de éxito, o un `SqlError` si ocurre algún error durante el proceso. pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, SqlError> { - let ruta_archivo = Path::new(ruta_carpeta_tablas) - .join(&self.tabla) - .with_extension("csv"); - let ruta_archivo_temporal = ruta_archivo.with_extension("tmp"); + let (ruta_archivo, ruta_archivo_temporal) = self.armar_rutas(ruta_carpeta_tablas); - // Abro archivo tabla - let archivo_entrada = match File::open(&ruta_archivo) { - Ok(file) => file, - Err(_err) => { - return Err(SqlError::InvalidTable); - } + let archivo_entrada = match Self::abrir_archivo_tabla(&ruta_archivo) { + Ok(value) => value, + Err(value) => return value, }; let reader = BufReader::new(archivo_entrada); - // Creo un archivo temporal para ir escribiendo los resultados actualizados - let mut archivo_temporal = match File::create(&ruta_archivo_temporal) { - Ok(file) => file, - Err(_err) => { - return Err(SqlError::Error( - "Error al crear el archivo temporal:".into(), - )); - } + let mut archivo_temporal = match Self::crear_archivo_temporal(&ruta_archivo_temporal) { + Ok(value) => value, + Err(value) => return value, }; - // encabezadosss let mut lineas = reader.lines(); - let encabezados = match lineas.next() { - Some(Ok(line)) => line, - Some(Err(_err)) => { - return Err(SqlError::InvalidTable); - } - None => { - // archivo vacío - return Err(SqlError::InvalidTable); - } + let vector_encabezados = match Self::armar_vector_encabezados(&mut lineas) { + Ok(value) => value, + Err(value) => return value, }; - let encabezados: Vec = encabezados - .split(',') - .map(|s| s.trim().to_string()) - .collect(); - - //chequeo columnas existan en tabla - let mut encabezados_update = Vec::new(); - for col in &self.columnas { - if let Some(encabezado) = encabezados.iter().find(|&encabezado| encabezado == col) { - encabezados_update.push(encabezado.to_string()); - } else { - //borro el temp - if let Err(_del_err) = std::fs::remove_file(&ruta_archivo_temporal) { - return Err(SqlError::Error( - "Error al eliminar el archivo temporal".to_string(), - )); - } - return Err(SqlError::InvalidColumn); - } + if let Some(value) = self.chequear_columnas(&ruta_archivo_temporal, &vector_encabezados) { + return value; } - // Escribo encabezados al archivo de salida - if let Err(_err) = writeln!(archivo_temporal, "{}", encabezados.join(",")) { + if let Err(_err) = writeln!(archivo_temporal, "{}", vector_encabezados.join(",")) { return Err(SqlError::InvalidColumn); } let mut filas_actualizadas = Vec::new(); - // leo linea a linea el archivo de entrada + self.lectura_linea_a_linea( + &ruta_archivo_temporal, + &mut archivo_temporal, + &mut lineas, + &vector_encabezados, + &mut filas_actualizadas, + )?; + + Self::reemplazar_orig_por_temp(&ruta_archivo, &ruta_archivo_temporal, filas_actualizadas) + } + + fn reemplazar_orig_por_temp( + ruta_archivo: &PathBuf, + ruta_archivo_temporal: &PathBuf, + filas_actualizadas: Vec>, + ) -> Result>, SqlError> { + // Reemplazo el archivo original con el archivo temporal + match std::fs::rename(ruta_archivo_temporal, ruta_archivo) { + Ok(_) => Ok(filas_actualizadas), + Err(_err) => Err(SqlError::Error( + "Error al escribir el archivo temporal".to_string(), + )), + } + } + + fn lectura_linea_a_linea( + &self, + ruta_archivo_temporal: &PathBuf, + archivo_temporal: &mut File, + lineas: &mut Lines>, + vector_encabezados: &[String], + filas_actualizadas: &mut Vec>, + ) -> Result<(), SqlError> { for linea in lineas { let mut registro: Vec = match linea { Ok(line) => line.split(',').map(|s| s.trim().to_string()).collect(), Err(_err) => { - drop(SqlError::InvalidTable); continue; } }; + let registro_valido = match self.chequear_registro( + ruta_archivo_temporal, + vector_encabezados, + &mut registro, + ) { + Ok(valido) => valido, + Err(err) => { + return Err(err); + } + }; + if registro_valido { + self.modificar_registro(vector_encabezados, &mut registro); + } + + if let Some(value) = Self::escribir_registro(archivo_temporal, &mut registro) { + return value; + } + + filas_actualizadas.push(registro); + } + + Ok(()) + } + + fn modificar_registro(&self, vector_encabezados: &[String], registro: &mut [String]) { + for (columna, valor) in self.columnas.iter().zip(&self.valores) { + if let Some(indice) = vector_encabezados.iter().position(|h| h == columna) { + if indice < registro.len() { + registro[indice] = valor.to_string(); + } + } + } + } + + fn escribir_registro( + archivo_temporal: &mut File, + registro: &mut [String], + ) -> Option> { + // Escribo el registro actualizado al archivo temporal + if let Err(_err) = writeln!(archivo_temporal, "{}", registro.join(",")) { + return Some(Err(SqlError::Error( + "Error al escribir en el archivo temporal".to_string(), + ))); + } + None + } - // Aplico restricciones - let registro_valido = if let Some(ref _restricciones) = self.restricciones { - if let Err(err) = self.aplicar_restricciones(®istro, &encabezados) { - // Si aplicar_restricciones devuelve un error, elimino el archivo temporal - if let Err(_del_err) = std::fs::remove_file(&ruta_archivo_temporal) { + fn chequear_registro( + &self, + ruta_archivo_temporal: &PathBuf, + vector_encabezados: &[String], + registro: &mut [String], + ) -> Result { + // Aplico restricciones + let registro_valido = if let Some(ref _restricciones) = self.restricciones { + match self.aplicar_restricciones(registro, vector_encabezados) { + Ok(valido) => valido, + Err(err) => { + // Si `aplicar_restricciones` devuelve un error, elimino el archivo temporal + if let Err(_del_err) = std::fs::remove_file(ruta_archivo_temporal) { return Err(SqlError::Error( "Error al eliminar el archivo temporal".to_string(), )); } - //burbujeo + // Burbujeo del error return Err(err); - } else { - //no hubo error - self.aplicar_restricciones(®istro, &encabezados)? } - } else { - true // Si no hay restricciones, el registro es válido - }; + } + } else { + true // Si no hay restricciones, el registro es válido + }; - if registro_valido { - // Modifico el registro que cumple con las restricciones - for (columna, valor) in self.columnas.iter().zip(&self.valores) { - if let Some(indice) = encabezados.iter().position(|h| h == columna) { - if indice < registro.len() { - registro[indice] = valor.to_string(); - } - } + Ok(registro_valido) + } + + fn chequear_columnas( + &self, + ruta_archivo_temporal: &PathBuf, + vector_encabezados: &[String], + ) -> Option>, SqlError>> { + //chequeo columnas existan en tabla + let mut encabezados_update = Vec::new(); + for col in &self.columnas { + if let Some(encabezado) = vector_encabezados + .iter() + .find(|&encabezado| encabezado == col) + { + encabezados_update.push(encabezado.to_string()); + } else { + //borro el temp + if let Err(_del_err) = std::fs::remove_file(ruta_archivo_temporal) { + return Some(Err(SqlError::Error( + "Error al eliminar el archivo temporal".to_string(), + ))); } + return Some(Err(SqlError::InvalidColumn)); } + } + None + } - // Escribo el registro actualizada al archivo temporal - if let Err(_err) = writeln!(archivo_temporal, "{}", registro.join(",")) { - return Err(SqlError::Error( - "Error al escribir el archivo temporal".to_string(), - )); + fn armar_vector_encabezados( + lineas: &mut Lines>, + ) -> Result, Result>, SqlError>> { + let encabezados = match lineas.next() { + Some(Ok(line)) => line, + Some(Err(_err)) => { + return Err(Err(SqlError::InvalidTable)); + } + None => { + // archivo vacío + return Err(Err(SqlError::InvalidTable)); } + }; - filas_actualizadas.push(registro); - } + let encabezados: Vec = encabezados + .split(',') + .map(|s| s.trim().to_string()) + .collect(); + Ok(encabezados) + } - // Reemplazo el archivo original con el archivo temporal - match std::fs::rename(&ruta_archivo_temporal, &ruta_archivo) { - Ok(_) => Ok(filas_actualizadas), - Err(_err) => Err(SqlError::Error( - "Error al escribir el archivo temporal".to_string(), - )), - } + fn abrir_archivo_tabla( + ruta_archivo: &PathBuf, + ) -> Result>, SqlError>> { + // Abro archivo tabla + let archivo_entrada = match File::open(ruta_archivo) { + Ok(file) => file, + Err(_err) => { + return Err(Err(SqlError::InvalidTable)); + } + }; + Ok(archivo_entrada) + } + + fn crear_archivo_temporal( + ruta_archivo_temporal: &PathBuf, + ) -> Result>, SqlError>> { + // Creo un archivo temporal para ir escribiendo los resultados actualizados + let archivo_temporal = match File::create(ruta_archivo_temporal) { + Ok(file) => file, + Err(_err) => { + return Err(Err(SqlError::Error( + "Error al crear el archivo temporal:".into(), + ))); + } + }; + Ok(archivo_temporal) + } + + fn armar_rutas(&self, ruta_carpeta_tablas: &str) -> (PathBuf, PathBuf) { + let ruta_archivo = Path::new(ruta_carpeta_tablas) + .join(&self.tabla) + .with_extension("csv"); + let ruta_archivo_temporal = ruta_archivo.with_extension("tmp"); + (ruta_archivo, ruta_archivo_temporal) } - pub fn aplicar_restricciones( + + fn aplicar_restricciones( &self, registro: &[String], encabezados: &[String], From e7124f28006bfddcf2da106a1254ee26fec866a0 Mon Sep 17 00:00:00 2001 From: German Douce Date: Sun, 8 Sep 2024 23:57:22 -0300 Subject: [PATCH 52/54] modularizo un poco mas delete y corrijo paarq ue se borre el temporal (lo habia borrado) --- README.md | 2 +- src/delete.rs | 85 +++++++++++++++++++++++++-------------------------- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 4a2d124..743e30f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ I Crear un nuevo proyecto en un directorio existente: cargo init I Compilar el proyecto: cargo build -I Compilar el proyecto en modo release: cargo build –release +I Compilar el proyecto en modo release: cargo build –-release I Ejecutar el proyecto: cargo run diff --git a/src/delete.rs b/src/delete.rs index ab83733..fcfffc5 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -41,15 +41,11 @@ impl Delete { let reader = BufReader::new(archivo); - // Creo un archivo temporal - let mut archivo_temporal = match File::create(&ruta_temporal) { - Ok(file) => file, - Err(_err) => { - return Err(SqlError::Error( - "Error al crear el archivo temporal:".into(), - )); - } + let mut archivo_temporal = match Self::crear_archivo_temporal(&ruta_temporal) { + Ok(value) => value, + Err(value) => return value, }; + // encabezadosss let mut lineas = reader.lines(); let encabezados = match Self::hallar_encabezados(&mut lineas) { @@ -65,26 +61,38 @@ impl Delete { let mut filas_resultantes = Vec::new(); - // Leo el resto del archivo y veo con cuales me quedo + self.borrar_linea_a_linea(&ruta_temporal, &mut archivo_temporal, &mut lineas, &vector_encabezados, &mut filas_resultantes)?; + + if let Some(value) = Self::reemplazar_original_por_temp(&ruta_archivo, &ruta_temporal) { + return value; + } + + Ok(filas_resultantes) + } + + fn borrar_linea_a_linea( + &self, + ruta_temporal: &String, + archivo_temporal: &mut File, + lineas: &mut Lines>, + vector_encabezados: &[String], + filas_resultantes: &mut Vec>, + ) -> Result<(), SqlError> { + // Leo el resto del archivo y veo con cuáles me quedo for linea in lineas { let linea = match linea { Ok(l) => l, - Err(_err) => { - return Err(SqlError::InvalidTable); - } + Err(_err) => return Err(SqlError::InvalidTable), }; let fila: Vec = linea.split(',').map(|s| s.trim().to_string()).collect(); - if let Some(value) = - self.chequear_restricciones(&ruta_temporal, &vector_encabezados, &fila) - { - return value; - } - - if !self.aplicar_restricciones(&fila, &vector_encabezados)? { - // Si no hay restricciones o si la fila no cumple con las restricciones, - // la escribimos en el archivo temporal + if let Err(err) = self.aplicar_restricciones(&fila, vector_encabezados) { + //borro el archivo temporal si hay error en las restricciones y devuelvo error + let _ = std::fs::remove_file(ruta_temporal); + return Err(err); + } else if !self.aplicar_restricciones(&fila, vector_encabezados)? { + // Si la fila no cumple con las restricciones, la escribimos en el archivo temporal if let Err(_err) = writeln!(archivo_temporal, "{}", linea) { return Err(SqlError::Error( "Error al escribir el archivo temporal".to_string(), @@ -94,13 +102,22 @@ impl Delete { // Fila que cumple con las restricciones y se elimina filas_resultantes.push(fila); } - } - if let Some(value) = Self::reemplazar_original_por_temp(&ruta_archivo, &ruta_temporal) { - return value; } + Ok(()) + } - Ok(filas_resultantes) + fn crear_archivo_temporal(ruta_temporal: &String) -> Result>, SqlError>> { + // Creo un archivo temporal + let archivo_temporal = match File::create(ruta_temporal) { + Ok(file) => file, + Err(_err) => { + return Err(Err(SqlError::Error( + "Error al crear el archivo temporal:".into(), + ))); + } + }; + Ok(archivo_temporal) } fn hallar_encabezados( @@ -119,24 +136,6 @@ impl Delete { Ok(encabezados) } - fn chequear_restricciones( - &self, - ruta_temporal: &String, - encabezados: &[String], - fila: &[String], - ) -> Option>, SqlError>> { - if let Err(err) = self.aplicar_restricciones(fila, encabezados) { - // Si aplicar_restricciones devuelve un error, elimino el archivo temporal - if let Err(_del_err) = std::fs::remove_file(ruta_temporal) { - return Some(Err(SqlError::Error( - "Error al eliminar el archivo temporal".to_string(), - ))); - } - return Some(Err(err)); - } - None - } - fn reemplazar_original_por_temp( ruta_archivo: &String, ruta_temporal: &String, From 086e989099686ae4d0f0e73e45ceba35ee8849c1 Mon Sep 17 00:00:00 2001 From: German Douce Date: Mon, 9 Sep 2024 00:11:48 -0300 Subject: [PATCH 53/54] corrijo comando xa q no imprmia cunaod es update --- src/comandos.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/comandos.rs b/src/comandos.rs index 122e531..c4a0e89 100644 --- a/src/comandos.rs +++ b/src/comandos.rs @@ -37,7 +37,6 @@ impl Comando { match self { Comando::Select(_select) => Select::imprimir_resultados(results), Comando::Update(_update) => { - Select::imprimir_resultados(results) //nadaaa } // } From 11016615d2ac39d1bf51b52590560ee5cdfbf8e6 Mon Sep 17 00:00:00 2001 From: German Douce Date: Mon, 9 Sep 2024 00:31:31 -0300 Subject: [PATCH 54/54] deatlles --- src/delete.rs | 22 ++++++++++++---------- src/insert.rs | 2 +- src/parser.rs | 8 -------- src/select.rs | 19 +++++++++++++++++++ src/update.rs | 12 ++++++------ 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/delete.rs b/src/delete.rs index fcfffc5..b82e828 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -9,9 +9,8 @@ use std::io::{BufRead, BufReader, Lines, Write}; use std::iter::Peekable; use std::str::Chars; -/// Estructura que representa un comando SQL de eliminación. -/// Tabla es la tabla con la que se esta trabjando -/// las restricciones del WHERE +/// tabla: Tabla es la tabla con la que se esta trabjando +/// resstricciones: las restricciones del WHERE pub struct Delete { pub tabla: String, pub restricciones: Option, @@ -22,10 +21,6 @@ impl Delete { /// Lee el archivo CSV correspondiente a la tabla, aplica las restricciones para determinar qué registros eliminar, /// y escribe los registros restantes en un archivo temporal. Luego, reemplaza el archivo original con el archivo temporal. /// - /// # Parámetros - /// - /// - `ruta_carpeta_tablas`: Ruta del directorio donde se encuentran los archivos CSV de las tablas. - /// /// # Retorna /// /// Un `Result` que contiene un `Vec>` con las filas que cumplieron con las restricciones y fueron eliminadas, @@ -61,7 +56,13 @@ impl Delete { let mut filas_resultantes = Vec::new(); - self.borrar_linea_a_linea(&ruta_temporal, &mut archivo_temporal, &mut lineas, &vector_encabezados, &mut filas_resultantes)?; + self.borrar_linea_a_linea( + &ruta_temporal, + &mut archivo_temporal, + &mut lineas, + &vector_encabezados, + &mut filas_resultantes, + )?; if let Some(value) = Self::reemplazar_original_por_temp(&ruta_archivo, &ruta_temporal) { return value; @@ -102,12 +103,13 @@ impl Delete { // Fila que cumple con las restricciones y se elimina filas_resultantes.push(fila); } - } Ok(()) } - fn crear_archivo_temporal(ruta_temporal: &String) -> Result>, SqlError>> { + fn crear_archivo_temporal( + ruta_temporal: &String, + ) -> Result>, SqlError>> { // Creo un archivo temporal let archivo_temporal = match File::create(ruta_temporal) { Ok(file) => file, diff --git a/src/insert.rs b/src/insert.rs index 65b59d7..fb1851c 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -9,7 +9,7 @@ use std::io::{BufRead, BufReader, BufWriter, Write}; use std::path::{Path, PathBuf}; ///Columnas: representa las columnas de los valores que se desean insertar -/// Valores: Listado de registros a insertar +///Valores: Listado de registros a insertar ///Tabla: tabla pub struct Insert { pub tabla: String, diff --git a/src/parser.rs b/src/parser.rs index 6d745e6..6812c6d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -40,14 +40,6 @@ use crate::update::Update; /// /// Esta función retornará un `SqlError::InvalidSintax` si el comando SQL no es reconocido, o si hay algún problema /// al intentar parsear el resto de la consulta con las funciones específicas. -/// -/// # Ejemplo -/// -/// ``` -/// let consulta = "SELECT id, nombre FROM ordenes WHERE cantidad > 1"; -/// ``` -/// -/// En este ejemplo, `parsear_consulta` devolverá un `Comando::Select` con la estructura `Select` correspondiente. pub fn parsear_consulta(consulta: &str) -> Result { //Si pongo toda la consulta enn minusucula, poerdo las mayuduclas de las tablas, columnas y APELLIDOCS //INTENMTE CAMBIAR LA LOGICA PERO SE ME HIZO MUUUY COMPLICADO SOLO COMPARAR ESAS PALABRAS diff --git a/src/select.rs b/src/select.rs index fc6f002..ba3712b 100644 --- a/src/select.rs +++ b/src/select.rs @@ -1,9 +1,21 @@ +//! Módulo para gestionar la consulta `SELECT` +//! +//! El módulo incluye la estructura `Select` que representa +//! un metodo para ejecutar la consulta y otros métodos asociados para poder +//! aplicar restricciones (WHERE), y ordenar los resultados (ORDER BY). +//! use crate::errores::SqlError; use std::fs::File; use std::io::{BufRead, BufReader}; use std::iter::Peekable; use std::str::Chars; +/// Columnas: representa las columnas de los valores que se desean seleccionar +/// Tabla: tabla +/// Valores: Listado de registros a insertar +/// Restricciones: restricciones que determinan qué registros deben ser actualizados. (Lo +/// que viene dsps del WHERE) +/// Ordenamiento: El ordenamiento elejido pub struct Select { pub columnas: Vec, pub tabla: String, @@ -12,6 +24,13 @@ pub struct Select { } impl Select { + /// Ejecuta la consulta SQL `SELECT` + /// Este método abre el archivo CSV correspondiente a la tabla especificada, + /// aplica las restricciones y el ordenamiento si están definidos, y devuelve + /// el resultado de la consulta como un vector de vectores de `String`. + /// # Retorna + /// `Result>, SqlError>` - Devuelve un vector con los resultados de la consulta si es exitosa, + /// o un error de tipo `SqlError` si ocurre algún problema durante la ejecución. pub fn ejecutar(&self, ruta_carpeta_tablas: &str) -> Result>, SqlError> { // Abro el archivo CSV let ruta_archivo = format!("{}/{}.csv", ruta_carpeta_tablas, self.tabla); diff --git a/src/update.rs b/src/update.rs index d5aa068..2233a23 100644 --- a/src/update.rs +++ b/src/update.rs @@ -1,7 +1,7 @@ -/// Módulo para gestionar el comando update -/// Este módulo proporciona una estructura y métodos para realizar la operacio update -/// Lee el archivo aplica las restricciones y utilizando un arhcivo temporal modifica -/// la tabla original +//! Módulo para gestionar el comando update +//! Este módulo proporciona una estructura y métodos para realizar la operacion update +//! Lee el archivo aplica las restricciones y utilizando un arhcivo temporal modifica +//! la tabla original use crate::errores::SqlError; use std::fs::File; use std::io::{BufRead, BufReader, Lines, Write}; @@ -12,8 +12,8 @@ use std::str::Chars; /// `Update` incluye la información necesaria para identificar el archivo CSV, /// Columans: columnas que se desean actualizar /// Valores: los nuevos valores para esas columnas -/// Restricciones: restricciones que determinan qué registros deben ser actualizados. Lo -/// que viene dsps del Where +/// Restricciones: restricciones que determinan qué registros deben ser actualizados. (Lo +/// que viene dsps del WHERE) pub struct Update { pub columnas: Vec, pub tabla: String,