diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..c7cfaa8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,6 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+/inspectionProfiles/Project_Default.xml
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..bbe0a70
--- /dev/null
+++ b/.idea/tp-individual-taller-9508.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ 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/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..48d8d5c
--- /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..662d1d1
--- /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
new file mode 100644
index 0000000..743e30f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+# 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/output.csv b/output.csv
new file mode 100644
index 0000000..32cdf30
--- /dev/null
+++ b/output.csv
@@ -0,0 +1 @@
+InvalidTable: Ocurrio un error al procesar la tabla.
diff --git a/src/comandos.rs b/src/comandos.rs
new file mode 100644
index 0000000..c4a0e89
--- /dev/null
+++ b/src/comandos.rs
@@ -0,0 +1,51 @@
+//! 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),
+ Update(Update),
+ Delete(Delete),
+}
+
+impl Comando {
+ 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),
+ Comando::Insert(insert) => insert.ejecutar(ruta_carpeta),
+ 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),
+ Comando::Update(_update) => {
+ //nadaaa
+ }
+ // }
+ Comando::Insert(_insert) => {
+ // nadaaa
+ }
+ Comando::Delete(_delete) => {
+ // nadaaa
+ }
+ }
+ }
+}
diff --git a/src/delete.rs b/src/delete.rs
new file mode 100644
index 0000000..b82e828
--- /dev/null
+++ b/src/delete.rs
@@ -0,0 +1,451 @@
+//! 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, Lines, Write};
+use std::iter::Peekable;
+use std::str::Chars;
+
+/// 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,
+}
+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.
+ ///
+ /// # 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);
+
+ let archivo = match Self::abrir_archivo_tabla(&ruta_archivo) {
+ Ok(value) => value,
+ Err(value) => return value,
+ };
+
+ let reader = BufReader::new(archivo);
+
+ 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) {
+ Ok(value) => value,
+ Err(value) => return value,
+ };
+ let vector_encabezados = Self::armar_vector_encabezados(encabezados);
+
+ if let Some(value) = Self::escribir_encabezados(&mut archivo_temporal, &vector_encabezados)
+ {
+ return value;
+ }
+
+ let mut filas_resultantes = Vec::new();
+
+ 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),
+ };
+
+ let fila: Vec = linea.split(',').map(|s| s.trim().to_string()).collect();
+
+ 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(),
+ ));
+ }
+ } else {
+ // Fila que cumple con las restricciones y se elimina
+ filas_resultantes.push(fila);
+ }
+ }
+ Ok(())
+ }
+
+ 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(
+ 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 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 Some(Err(SqlError::Error(
+ "Error al reemplazar el archivo original con el archivo temporal".to_string(),
+ )));
+ }
+ None
+ }
+
+ fn armar_vector_encabezados(encabezados: String) -> Vec {
+ let encabezados: Vec = encabezados
+ .split(',')
+ .map(|s| s.trim().to_string())
+ .collect();
+ encabezados
+ }
+
+ 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],
+ ) -> Result {
+ 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);
+
+ //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);
+ }
+ }
+ }
+
+ 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());
+
+ tokens
+ }
+
+ fn loop_tokenizar(
+ tokens: &mut Vec,
+ token_actual: &mut String,
+ keywords: [&str; 5],
+ chars: &mut Peekable,
+ ) {
+ for ch in chars.by_ref() {
+ 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, SqlError> {
+ 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() {
+ 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(SqlError::InvalidSintax);
+ }
+ } 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: &[String],
+ registro: &[String],
+ encabezados: &[String],
+ ) -> Result {
+ let mut stack = Vec::new();
+
+ for token in tokens.iter() {
+ match token.as_str() {
+ "AND" | "OR" | "NOT" => {
+ let derecha = stack.pop().ok_or(SqlError::InvalidSintax)?;
+ let izquierda = if token != "NOT" {
+ stack.pop().ok_or(SqlError::InvalidSintax)?
+ } else {
+ String::new()
+ };
+ 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(SqlError::InvalidSintax)? == "true")
+ }
+
+ //Aplico condición simple (comparaciones > < = <= >=)
+ fn aplicar_condicion(
+ &self,
+ condicion: &str,
+ registro: &[String],
+ encabezados: &[String],
+ ) -> Result {
+ 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 + 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(SqlError::Error(
+ "Operador no válido en la condición".to_string(),
+ ));
+ };
+
+ 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];
+ let resultado = match operador {
+ "=" => 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,
+ };
+
+ Ok(resultado)
+ } else {
+ Err(SqlError::InvalidColumn)
+ }
+ }
+}
diff --git a/src/errores.rs b/src/errores.rs
new file mode 100644
index 0000000..46fbffc
--- /dev/null
+++ b/src/errores.rs
@@ -0,0 +1,32 @@
+//! 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)]
+pub enum SqlError {
+ InvalidTable,
+ InvalidColumn,
+ InvalidSintax,
+ Error(String),
+}
+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::Error(msg) => write!(f, "ERROR: {}", msg),
+ }
+ }
+}
diff --git a/src/insert.rs b/src/insert.rs
new file mode 100644
index 0000000..fb1851c
--- /dev/null
+++ b/src/insert.rs
@@ -0,0 +1,149 @@
+//! Módulo para gestionar la consulta insert
+//!
+//! 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;
+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,
+ pub valores: Vec>,
+}
+
+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));
+
+ let file = match Self::abrir_archivo(&archivo_csv) {
+ Ok(value) => value,
+ Err(value) => return value,
+ };
+
+ let mut reader = BufReader::new(file);
+ let mut encabezados = String::new();
+
+ if let Err(_err) = reader.read_line(&mut encabezados) {
+ return Err(SqlError::InvalidTable);
+ }
+
+ let vector_encabezados = Self::armar_vector_encabezados(&mut encabezados);
+
+ 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) {
+ Ok(file) => file,
+ Err(_err) => {
+ return Err(SqlError::InvalidTable);
+ }
+ };
+
+ 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(); vector_encabezados.len()];
+
+ for (i, col_index) in columnas_indices.iter().enumerate() {
+ if i < fila.len() {
+ fila_nueva[*col_index] = fila[i].to_string();
+ }
+ }
+ if let Some(value) = Self::escribir_linea_en_archivo(writer, &mut fila_nueva) {
+ return Some(value);
+ }
+ filas_insertadas.push(fila_nueva);
+ }
+ 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
+ }
+}
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/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..fff9789
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,42 @@
+mod comandos;
+mod delete;
+mod errores;
+mod insert;
+mod parser;
+mod select;
+mod update;
+
+use parser::parsear_consulta;
+use std::env;
+
+fn main() {
+ // 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];
+
+ // Parsear la consulta
+ let comando = match parsear_consulta(consulta) {
+ Ok(result) => result,
+ Err(err) => {
+ println!("{}", err);
+ return;
+ }
+ };
+
+ match comando.ejecutar(ruta_carpeta_tablas) {
+ Ok(results) => {
+ comando.imprimir_resultados(&results);
+ }
+ Err(err) => {
+ println!("{}", err);
+ }
+ }
+}
diff --git a/src/parser.rs b/src/parser.rs
new file mode 100644
index 0000000..6812c6d
--- /dev/null
+++ b/src/parser.rs
@@ -0,0 +1,323 @@
+//! # 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;
+use crate::insert::Insert;
+use crate::select::Select;
+use crate::update::Update;
+
+/// 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.
+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
+ // 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") {
+ ("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(SqlError::InvalidSintax);
+ };
+
+ // Crear el comando basado en el tipo de comando detectado
+ 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(SqlError::InvalidSintax),
+ };
+ Ok(comando) // Devolvemos el comando directamente, no una tupla
+}
+
+//praseo el select
+fn parse_select(resto: &str) -> Result