diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..2a5ee14
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,66 @@
+name: Docs
+
+on:
+ push:
+ branches: [main]
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: deploy
+ cancel-in-progress: false
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Configure cache
+ uses: Swatinem/rust-cache@v2
+
+ - name: Setup pages
+ id: pages
+ uses: actions/configure-pages@v4
+
+ - name: Clean docs folder
+ run: cargo clean --doc
+
+ - name: Build docs
+ run: cargo doc --document-private-items --no-deps
+
+ - name: Add redirect
+ run: echo '' > target/doc/index.html
+
+ - name: Remove lock file
+ run: rm target/doc/.lock
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: ./target/doc
+
+ deploy:
+ name: Deploy
+
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+
+ runs-on: ubuntu-latest
+
+ needs: build
+
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
\ No newline at end of file
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
new file mode 100644
index 0000000..c8292ff
--- /dev/null
+++ b/.github/workflows/rust.yml
@@ -0,0 +1,22 @@
+name: Rust
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+env:
+ CARGO_TERM_COLOR: always
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Format Code
+ run: cargo fmt -- --check
+ - name: Build
+ run: cargo build --verbose
+ - name: Run Clippy
+ run: cargo clippy --all-targets --all-features -- -D warnings
+ - name: Run tests
+ run: cargo test --verbose
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3a8cabc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+.idea
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..1de2011
--- /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 = "rustic-sql"
+version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..3ea88fa
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,4 @@
+[package]
+name = "rustic-sql"
+version = "0.1.0"
+edition = "2021"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9846f89
--- /dev/null
+++ b/README.md
@@ -0,0 +1,25 @@
+# rustic-sql 🦀 
+
+> [!IMPORTANT]
+> ¿Como correr la app?
+> - Para las queries de tipo SELECT puedes redirigir la salida de STDOUT a un archivo o simplemente ver la salida por pantalla.
+> ```BASH
+>cargo run -- ruta/a/tablas "SELECT * FROM table" > output.csv
+>```
+> - Para las otras queries, simplemente se aplican los cambios sobre los archivos.
+> ```BASH
+>cargo run -- ruta/a/tablas "INSERT INTO table (id, name) VALUES (1, 'gabriel');"
+>```
+___
+> [!TIP]
+> ¿Como testear la app?
+>```BASH
+>cargo test --all
+>```
+___
+> [!NOTE]
+> ¿Como levantar la documentación?
+>```BASH
+>cargo doc --open
+>```
+> Alternativamente se puede visualizar por [github!](https://gabokatta.github.io/rustic-sql)
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..8b75a6f
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,63 @@
+//! # RusticSQL 🦀
+//!
+//! ### Clon de SQL, en Rust.
+//!
+//! Permite al usuario ejecutar consultas SQL mediante la linea de comandos.
+//!
+//! Las operaciones se realizan sobre "tablas" (archivos csv).
+//!
+//!
+//! Consultas Permitidas: [SELECT, INSERT, UPDATE, DELETE]
+//!
+//! Operadores Disponibles: [AND, OR, NOT y comparadores simples (>, <, =, etc..)]
+//!
+//!
+//! Estructura del Proyecto:
+//! - Tokenizador: Recibe un String y te devuelve tokens.
+//! - Constructor: Recibe tokens y los transforma en consultas validas.
+//! - Ejecutor: Recibe consultas y las ejecuta sobre las tablas.
+//!
+//! # Usar RusticSQL:
+//!
+//! > ```BASH
+//! > cargo run -- ruta/a/tablas "SELECT * FROM table" > output.csv
+//! > ```
+//!
+//! # Testea RusticSQL:
+//!
+//! >```BASH
+//! >cargo test --all
+//! >```
+//!
+//!
+#![doc(
+ html_logo_url = "https://cdn-icons-png.flaticon.com/512/4726/4726022.png",
+ html_favicon_url = "https://cdn-icons-png.flaticon.com/512/4726/4726022.png"
+)]
+
+use crate::query::executor::Executor;
+use crate::query::structs::query::Query;
+use crate::query::tokenizer::Tokenizer;
+use crate::query::validate_query_string;
+use crate::utils::files::validate_path;
+use std::error::Error;
+
+pub mod query;
+pub mod utils;
+
+pub fn run(args: Vec) -> Result<(), Box> {
+ if args.len() != 3 {
+ println!("invalid usage of rustic-sql");
+ return Err("usage: cargo run -- ".into());
+ }
+
+ let path: &String = &args[1];
+ let query: &String = &args[2];
+ validate_path(path)?;
+ validate_query_string(query)?;
+
+ let tokens = Tokenizer::new().tokenize(query)?;
+ let query = Query::from(tokens)?;
+ Executor::run(path, query)?;
+ Ok(())
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..a343add
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,9 @@
+use rustic_sql::run;
+use std::env;
+
+fn main() {
+ let args = env::args().collect();
+ if let Err(e) = run(args) {
+ println!("{}", e);
+ }
+}
diff --git a/src/query/builder/delete.rs b/src/query/builder/delete.rs
new file mode 100644
index 0000000..a431409
--- /dev/null
+++ b/src/query/builder/delete.rs
@@ -0,0 +1,146 @@
+use crate::query::builder::{validate_keywords, Builder};
+use crate::query::structs::operation::Operation::Delete;
+use crate::query::structs::query::Query;
+use crate::query::structs::token::Token;
+use crate::query::structs::token::TokenKind::Keyword;
+use crate::utils::errors::Errored;
+use std::collections::VecDeque;
+
+const ALLOWED_KEYWORDS: &[&str] = &["FROM", "WHERE", "AND", "OR"];
+
+/// Constructor para consultas de eliminación (`DELETE`).
+///
+/// `DeleteBuilder` se encarga de construir una consulta de eliminación a partir
+/// de una lista de tokens que representan la sintaxis de una consulta SQL.
+pub struct DeleteBuilder {
+ tokens: VecDeque,
+}
+
+impl DeleteBuilder {
+ /// Crea una nueva instancia de `DeleteBuilder`.
+ ///
+ /// Este constructor inicializa el `DeleteBuilder` con una cola de tokens
+ /// que representan una consulta SQL.
+ ///
+ /// # Parámetros
+ ///
+ /// - `tokens`: Cola de tokens (`VecDeque`) que se utilizarán para construir la consulta.
+ pub fn new(tokens: VecDeque) -> Self {
+ Self { tokens }
+ }
+}
+
+impl Builder for DeleteBuilder {
+ /// Construye una consulta `DELETE` a partir de los tokens proporcionados.
+ ///
+ /// Este método analiza los tokens para construir una consulta de eliminación válida.
+ /// Verifica palabras clave permitidas, obtiene la tabla objetivo y, opcionalmente,
+ /// analiza las condiciones de la cláusula `WHERE`.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna un objeto `Query` con la operación de eliminación configurada.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error `Errored` si la consulta contiene errores de sintaxis o palabras clave no permitidas.
+ fn build(&mut self) -> Result {
+ let mut query = Query::default();
+ self.validate_keywords()?;
+ query.operation = Delete;
+ query.table = self.parse_table(Delete)?;
+ match self.peek_expecting("WHERE", Keyword) {
+ Ok(_) => {
+ query.conditions = self.parse_where()?;
+ }
+ Err(_) => self.expect_none()?,
+ }
+ Ok(query)
+ }
+
+ /// Devuelve una referencia mutable a los tokens de la consulta.
+ ///
+ /// Este método es utilizado por otros métodos de construcción para acceder y modificar
+ /// la lista de tokens durante el proceso de análisis.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna una referencia mutable a la cola de tokens (`VecDeque`).
+ fn tokens(&mut self) -> &mut VecDeque {
+ &mut self.tokens
+ }
+
+ /// Valida las palabras clave permitidas en una consulta `DELETE`.
+ ///
+ /// Este método verifica que solo se utilicen las palabras clave permitidas para una
+ /// operación de eliminación. Si encuentra alguna palabra clave no permitida, lanza un error.
+ ///
+ /// Las palabras clave permitidas son: `FROM`, `WHERE`, `AND`, `OR`.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna `Ok(())` si todas las palabras clave son válidas.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error `Errored` si se encuentra una palabra clave no válida.
+ fn validate_keywords(&self) -> Result<(), Errored> {
+ validate_keywords(ALLOWED_KEYWORDS, &self.tokens, Delete)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::query::structs::expression::ExpressionNode::Empty;
+ use crate::query::structs::operation::Operation::Delete;
+ use crate::query::structs::query::Query;
+ use crate::query::structs::token::Token;
+ use crate::query::tokenizer::Tokenizer;
+
+ fn tokenize(sql: &str) -> Vec {
+ let mut tokenizer = Tokenizer::new();
+ tokenizer.tokenize(sql).unwrap()
+ }
+
+ #[test]
+ fn test_delete_simple() {
+ let sql = "DELETE FROM ordenes";
+ let tokens = tokenize(sql);
+ let query = Query::from(tokens).unwrap();
+
+ assert_eq!(query.operation, Delete);
+ assert_eq!(query.table, "ordenes");
+ assert_eq!(query.conditions, Empty);
+ }
+
+ #[test]
+ fn test_delete_with_conditions() {
+ let sql = "DELETE FROM ordenes WHERE id = 1";
+ let tokens = tokenize(sql);
+ let query = Query::from(tokens).unwrap();
+
+ assert_eq!(query.operation, Delete);
+ assert_eq!(query.table, "ordenes");
+ assert_ne!(query.conditions, Empty);
+ }
+
+ #[test]
+ fn test_delete_invalid_keyword() {
+ let sql = "DELETE FROM ordenes ORDER BY id";
+ let tokens = tokenize(sql);
+ let result = Query::from(tokens);
+
+ assert!(result.is_err());
+ assert!(result.unwrap_err().to_string().contains("ORDER BY"));
+ }
+
+ #[test]
+ fn test_delete_missing_table() {
+ let sql = "DELETE WHERE id = 1";
+ let tokens = tokenize(sql);
+ let result = Query::from(tokens);
+
+ assert!(result.is_err());
+ assert!(result.unwrap_err().to_string().contains("FROM"));
+ }
+}
diff --git a/src/query/builder/expression.rs b/src/query/builder/expression.rs
new file mode 100644
index 0000000..d4e2896
--- /dev/null
+++ b/src/query/builder/expression.rs
@@ -0,0 +1,400 @@
+use crate::errored;
+use crate::query::structs::expression::ExpressionNode::{Empty, Leaf};
+use crate::query::structs::expression::ExpressionOperator::*;
+use crate::query::structs::expression::{ExpressionNode, ExpressionOperator};
+use crate::query::structs::token::TokenKind::Keyword;
+use crate::query::structs::token::{Token, TokenKind};
+use crate::utils::errors::Errored;
+use crate::utils::errors::Errored::Syntax;
+use std::collections::VecDeque;
+
+/// Estructura para analizar y construir expresiones lógicas en una consulta.
+///
+/// `ExpressionBuilder` proporciona métodos recursivos para analizar expresiones condicionales
+/// como `AND`, `OR`, `NOT`, y comparaciones simples. Estas expresiones son comúnmente
+/// utilizadas en las cláusulas `WHERE` de las consultas SQL.
+pub struct ExpressionBuilder;
+
+impl ExpressionBuilder {
+ /// Inicia el análisis de las expresiones a partir de una lista de tokens.
+ ///
+ /// Este método comienza el proceso de parseo llamando a `parse_or` para
+ /// manejar operaciones lógicas.
+ ///
+ /// La razón por la cual este método arranca parseando las operaciones OR, es porque
+ /// es la que tiene menor precedencia, de esta manera la expresión sera parseada en el siguiente orden de
+ /// predecencia:
+ ///
+ /// - OR -> AND -> NOT -> (Parantesis)
+ ///
+ /// Teniendo prioridad los operadores que estan más la derecha.
+ ///
+ /// Podemos hacer esto gracias a la naturaleza recursiva de los métodos que siempre buscaran llegar
+ /// a una hoja desde arriba e ir evaluando en reversa hasta retornar.
+ ///
+ /// # Parámetros
+ ///
+ /// - `tokens`: Cola de tokens (`VecDeque`) que representan la consulta a analizar.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna un nodo de expresión (`ExpressionNode`) que representa la expresión completa.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error `Errored` si ocurre algún problema durante el análisis.
+ pub fn parse_expressions(tokens: &mut VecDeque) -> Result {
+ ExpressionBuilder::parse_or(tokens)
+ }
+
+ /// Analiza las expresiones con el operador `OR`.
+ ///
+ /// Este método evalúa si existen múltiples expresiones unidas por el operador `OR`
+ /// y las agrupa en un nodo de expresión.
+ ///
+ /// Primero el método busca mediante el parseo de una operacion AND el valor de la rama
+ /// izquierda de la expresión actual.
+ ///
+ /// Si el tóken actual es un operador "OR", consumimos el token y buscamos el valor
+ /// de la derecha.
+ ///
+ /// # Parámetros
+ ///
+ /// - `tokens`: Cola de tokens a analizar.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna un nodo de expresión con operadores `OR`.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error si hay un problema con los tokens.
+ fn parse_or(tokens: &mut VecDeque) -> Result {
+ let mut left = ExpressionBuilder::parse_and(tokens)?;
+ while let Some(t) = tokens.front() {
+ if t.kind != Keyword || t.value != "OR" {
+ break;
+ }
+ tokens.pop_front();
+ let right = ExpressionBuilder::parse_and(tokens)?;
+ left = ExpressionNode::Statement {
+ operator: Or,
+ left: Box::new(left),
+ right: Box::new(right),
+ };
+ }
+ Ok(left)
+ }
+
+ /// Analiza las expresiones con el operador `AND`.
+ ///
+ /// Similar a `parse_or`, pero para operaciones con `AND`.
+ /// Podemos ver como este método le delega a los operadores `NOT` la responsabilidad de evaluar.
+ ///
+ /// # Parámetros
+ ///
+ /// - `tokens`: Cola de tokens a analizar.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna un nodo de expresión con operadores `AND`.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error si hay un problema con los tokens.
+ fn parse_and(tokens: &mut VecDeque) -> Result {
+ let mut left = ExpressionBuilder::parse_not(tokens)?;
+ while let Some(t) = tokens.front() {
+ if t.kind != Keyword || t.value != "AND" {
+ break;
+ }
+ tokens.pop_front();
+ let right = ExpressionBuilder::parse_not(tokens)?;
+ left = ExpressionNode::Statement {
+ operator: And,
+ left: Box::new(left),
+ right: Box::new(right),
+ };
+ }
+ Ok(left)
+ }
+
+ /// Analiza las expresiones con el operador `NOT`.
+ ///
+ /// Maneja expresiones que comienzan con `NOT`, invirtiendo la lógica de la condición.
+ ///
+ /// Es el operador booleano con mayor precedencia, en caso de no estar dentro de una operación
+ /// NOT, este método se encarga de empezar a estudiar los nodos más simples ya que ninguno ha matcheado hasta
+ /// ahora.
+ ///
+ /// # Parámetros
+ ///
+ /// - `tokens`: Cola de tokens a analizar.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna un nodo de expresión con el operador `NOT`.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error si hay un problema con los tokens.
+ fn parse_not(tokens: &mut VecDeque) -> Result {
+ if let Some(t) = tokens.front() {
+ if t.kind == Keyword && t.value == "NOT" {
+ tokens.pop_front();
+ let node = ExpressionBuilder::parse_comparisons(tokens)?;
+ return Ok(ExpressionNode::Statement {
+ operator: Not,
+ left: Box::new(node),
+ right: Box::new(Empty),
+ });
+ }
+ }
+ ExpressionBuilder::parse_comparisons(tokens)
+ }
+
+ /// Analiza las comparaciones simples en las expresiones.
+ ///
+ /// Este método procesa las comparaciones entre dos valores usando operadores como `=`, `>`, `<`, etc.
+ ///
+ /// # Parámetros
+ ///
+ /// - `tokens`: Cola de tokens a analizar.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna un nodo de expresión que representa una comparación.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error si los tokens no forman una comparación válida.
+ fn parse_comparisons(tokens: &mut VecDeque) -> Result {
+ let left = ExpressionBuilder::parse_leaf(tokens)?;
+ let operator = ExpressionBuilder::parse_simple_operator(tokens);
+ if operator.is_err() {
+ return Ok(left);
+ }
+ let right = ExpressionBuilder::parse_leaf(tokens)?;
+ Ok(ExpressionNode::Statement {
+ operator: operator?,
+ left: Box::new(left),
+ right: Box::new(right),
+ })
+ }
+
+ /// Analiza las hojas de una expresión, como identificadores, números o cadenas.
+ ///
+ /// Este método maneja elementos básicos que no son operadores lógicos, como los valores literales.
+ ///
+ /// En caso de conseguir un parentesis, es indicador de que una nueva expresión se debe evaluar.
+ /// Es acá cuando la recursividad vuelve a empezar.
+ ///
+ /// # Parámetros
+ ///
+ /// - `tokens`: Cola de tokens a analizar.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna un nodo de expresión que representa una hoja.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error si no se encuentra una hoja válida.
+ fn parse_leaf(tokens: &mut VecDeque) -> Result {
+ let mut leaf = Empty;
+ while let Some(t) = tokens.front() {
+ match t.kind {
+ TokenKind::Identifier | TokenKind::Number | TokenKind::String => {
+ if let Some(t) = tokens.pop_front() {
+ leaf = Leaf(t);
+ break;
+ }
+ }
+ TokenKind::ParenthesisOpen => {
+ tokens.pop_front();
+ leaf = ExpressionBuilder::parse_expressions(tokens)?;
+ }
+ TokenKind::ParenthesisClose if leaf != Empty => {
+ tokens.pop_front();
+ break;
+ }
+ _ => errored!(Syntax, "unexpected token when parsing leaf: {:?}", t),
+ }
+ }
+ Ok(leaf)
+ }
+
+ /// Analiza los operadores simples en las comparaciones.
+ ///
+ /// Este método reconoce operadores como `=`, `!=`, `>`, `<`, etc.
+ ///
+ /// # Parámetros
+ ///
+ /// - `tokens`: Cola de tokens a analizar.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna el operador de la comparación (`ExpressionOperator`).
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error si no se encuentra un operador válido.
+ fn parse_simple_operator(tokens: &mut VecDeque) -> Result {
+ let t = tokens
+ .front()
+ .ok_or_else(|| Syntax("expected operator but was end of query.".to_string()))?;
+ let op = match t.value.as_str() {
+ "=" => Equals,
+ "!=" => NotEquals,
+ ">" => GreaterThan,
+ ">=" => GreaterOrEqual,
+ "<" => LessThan,
+ "<=" => LessOrEqual,
+ _ => errored!(Syntax, "invalid operator, got: {}", t.value),
+ };
+ tokens.pop_front();
+ Ok(op)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::query::structs::token::TokenKind::*;
+ use crate::query::structs::token::{Token, TokenKind};
+ use std::ops::Deref;
+
+ fn create_token(kind: TokenKind, value: &str) -> Token {
+ Token {
+ kind,
+ value: value.to_string(),
+ }
+ }
+
+ fn operator_should_be(node: &ExpressionNode, op: ExpressionOperator) {
+ match node {
+ ExpressionNode::Statement { operator, .. } => {
+ assert_eq!(*operator, op);
+ }
+ Empty => {
+ if op != None {
+ panic!("Expected a None operator but got {:?}", op)
+ }
+ }
+ _ => panic!("Expected an {:?} expression", op),
+ }
+ }
+
+ fn leaves_should_have_op(
+ node: ExpressionNode,
+ l_op: ExpressionOperator,
+ r_op: ExpressionOperator,
+ ) {
+ match node {
+ ExpressionNode::Statement { left, right, .. } => {
+ operator_should_be(left.deref(), l_op);
+ operator_should_be(right.deref(), r_op);
+ }
+ _ => panic!("Expected an OR expression"),
+ }
+ }
+
+ #[test]
+ fn test_parse_or_expression() {
+ let mut tokens = VecDeque::from(vec![
+ create_token(Identifier, "x"),
+ create_token(Keyword, "="),
+ create_token(Number, "1"),
+ create_token(Keyword, "OR"),
+ create_token(Identifier, "y"),
+ create_token(Keyword, "="),
+ create_token(Number, "2"),
+ ]);
+
+ let result = ExpressionBuilder::parse_expressions(&mut tokens).unwrap();
+ operator_should_be(&result, Or);
+ leaves_should_have_op(result, Equals, Equals);
+ }
+
+ #[test]
+ fn test_parse_and_expression() {
+ let mut tokens = VecDeque::from(vec![
+ create_token(Identifier, "x"),
+ create_token(Keyword, "="),
+ create_token(Number, "1"),
+ create_token(Keyword, "AND"),
+ create_token(Identifier, "y"),
+ create_token(Keyword, "="),
+ create_token(Number, "2"),
+ ]);
+
+ let result = ExpressionBuilder::parse_expressions(&mut tokens).unwrap();
+ operator_should_be(&result, And);
+ leaves_should_have_op(result, Equals, Equals);
+ }
+
+ #[test]
+ fn test_parse_not_expression() {
+ let mut tokens = VecDeque::from(vec![
+ create_token(Keyword, "NOT"),
+ create_token(ParenthesisOpen, "("),
+ create_token(Identifier, "x"),
+ create_token(Keyword, "="),
+ create_token(Number, "1"),
+ create_token(ParenthesisClose, ")"),
+ ]);
+
+ let result = ExpressionBuilder::parse_expressions(&mut tokens).unwrap();
+ operator_should_be(&result, Not);
+ leaves_should_have_op(result, Equals, None)
+ }
+
+ #[test]
+ fn test_parse_comparison_expression() {
+ let mut tokens = VecDeque::from(vec![
+ create_token(Identifier, "x"),
+ create_token(Keyword, ">"),
+ create_token(Number, "10"),
+ ]);
+
+ let result = ExpressionBuilder::parse_expressions(&mut tokens).unwrap();
+ operator_should_be(&result, GreaterThan)
+ }
+
+ #[test]
+ fn test_parse_complex_expression() {
+ let mut tokens = VecDeque::from(vec![
+ create_token(Identifier, "x"),
+ create_token(Keyword, "="),
+ create_token(Number, "1"),
+ create_token(Keyword, "AND"),
+ create_token(ParenthesisOpen, "("),
+ create_token(Identifier, "y"),
+ create_token(Keyword, "="),
+ create_token(Number, "2"),
+ create_token(Keyword, "OR"),
+ create_token(Keyword, "NOT"),
+ create_token(Identifier, "z"),
+ create_token(Keyword, "="),
+ create_token(Number, "3"),
+ create_token(ParenthesisClose, ")"),
+ ]);
+
+ let result = ExpressionBuilder::parse_expressions(&mut tokens).unwrap();
+ operator_should_be(&result, And);
+ leaves_should_have_op(result, Equals, Or);
+ }
+
+ #[test]
+ fn test_parse_invalid_token() {
+ let mut tokens = VecDeque::from(vec![
+ create_token(Keyword, "INVALID"),
+ create_token(Identifier, "x"),
+ create_token(Keyword, "="),
+ create_token(Number, "1"),
+ ]);
+
+ let result = ExpressionBuilder::parse_expressions(&mut tokens);
+ assert!(result.is_err());
+ }
+}
diff --git a/src/query/builder/insert.rs b/src/query/builder/insert.rs
new file mode 100644
index 0000000..63f0717
--- /dev/null
+++ b/src/query/builder/insert.rs
@@ -0,0 +1,228 @@
+use crate::errored;
+use crate::query::builder::{unexpected_token_in_stage, validate_keywords, Builder};
+use crate::query::structs::operation::Operation::Insert;
+use crate::query::structs::query::Query;
+use crate::query::structs::token::TokenKind::{Keyword, ParenthesisClose, ParenthesisOpen};
+use crate::query::structs::token::{Token, TokenKind};
+use crate::utils::errors::Errored;
+use crate::utils::errors::Errored::Syntax;
+use std::collections::VecDeque;
+
+const ALLOWED_KEYWORDS: &[&str] = &["VALUES"];
+
+/// Estructura `InsertBuilder` que permite construir una consulta de tipo INSERT.
+pub struct InsertBuilder {
+ tokens: VecDeque,
+}
+
+impl InsertBuilder {
+ /// Crea una nueva instancia de `InsertBuilder` con los tokens proporcionados.
+ ///
+ /// # Parámetros
+ /// - `tokens`: Un `VecDeque` que contiene los tokens de la consulta.
+ ///
+ /// # Retorna
+ /// - Una instancia de `InsertBuilder`.
+ pub fn new(tokens: VecDeque) -> Self {
+ Self { tokens }
+ }
+
+ /// Analiza los valores de inserción de una consulta SQL INSERT.
+ ///
+ /// Este método espera encontrar la palabra clave `VALUES` seguida de un grupo de valores
+ /// entre paréntesis. Los valores pueden ser cadenas o números.
+ ///
+ /// # Retorna
+ /// - Un `Result` que contiene un vector de vectores de `Token` representando los valores de inserción.
+ ///
+ /// # Errores
+ /// - Retorna un error si no se encuentra la palabra clave `VALUES` o si los valores no están
+ /// correctamente formateados.
+ fn parse_insert_values(&mut self) -> Result>, Errored> {
+ self.pop_expecting("VALUES", Keyword)?;
+ self.peek_expecting("(", ParenthesisOpen)?;
+ let mut inserts = vec![];
+ let mut values = vec![];
+ while let Some(t) = self.tokens.front() {
+ match t.kind {
+ TokenKind::String | TokenKind::Number => {
+ if let Some(token) = self.tokens.pop_front() {
+ values.push(token);
+ }
+ }
+ ParenthesisOpen => {
+ self.tokens.pop_front();
+ }
+ ParenthesisClose => {
+ self.tokens.pop_front();
+ inserts.push(values);
+ values = vec![];
+ }
+ _ => unexpected_token_in_stage("VALUES", t)?,
+ }
+ }
+ Ok(inserts)
+ }
+
+ /// Este método asegura que el número de valores en cada inserción coincida con el número
+ /// de columnas definidas en la consulta.
+ ///
+ /// # Parámetros
+ /// - `query`: Referencia a la consulta `Query` que contiene las columnas y los valores de inserción.
+ ///
+ /// # Retorna
+ /// - `Ok(())` si los valores son válidos.
+ ///
+ /// # Errores
+ /// - Retorna un error si el número de columnas y el número de valores en la inserción no coinciden.
+ fn validate_inserts(&self, query: &Query) -> Result<(), Errored> {
+ for insert in &query.inserts {
+ let columns = query.columns.len();
+ if insert.len() != columns {
+ let values: Vec<&String> = insert.iter().map(|t| &t.value).collect();
+ errored!(
+ Syntax,
+ "expected {} columns but insert has:\n{:?}",
+ columns,
+ values
+ )
+ }
+ }
+ Ok(())
+ }
+}
+
+impl Builder for InsertBuilder {
+ /// Construye una consulta de tipo INSERT a partir de los tokens.
+ ///
+ /// Este método analiza los tokens, identifica las columnas, los valores a insertar y valida
+ /// la estructura de la consulta.
+ ///
+ /// # Retorna
+ /// - Un `Result` que contiene la consulta `Query` si se construye exitosamente.
+ ///
+ /// # Errores
+ /// - Retorna un error si la consulta no está correctamente formada o contiene palabras clave
+ /// inválidas.
+ fn build(&mut self) -> Result {
+ let mut query = Query::default();
+ self.validate_keywords()?;
+ query.operation = Insert;
+ query.table = self.parse_table(Insert)?;
+ self.peek_expecting("(", ParenthesisOpen)?;
+ query.columns = self.parse_columns()?;
+ query.inserts = self.parse_insert_values()?;
+ self.expect_none()?;
+ self.validate_inserts(&query)?;
+ Ok(query)
+ }
+
+ /// Retorna una referencia mutable a los tokens que se están procesando.
+ ///
+ /// # Retorna
+ /// - Una referencia mutable a `VecDeque`.
+ fn tokens(&mut self) -> &mut VecDeque {
+ &mut self.tokens
+ }
+
+ /// Este método compara las palabras clave en los tokens con las permitidas para asegurarse
+ /// de que la consulta sea válida.
+ ///
+ /// # Retorna
+ /// - `Ok(())` si las palabras clave son válidas.
+ ///
+ /// # Errores
+ /// - Retorna un error si se detecta una palabra clave inválida en la consulta.
+ fn validate_keywords(&self) -> Result<(), Errored> {
+ validate_keywords(ALLOWED_KEYWORDS, &self.tokens, Insert)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::query::structs::token::TokenKind::{Identifier, Number, String};
+ use crate::query::tokenizer::Tokenizer;
+
+ fn tokenize(sql: &str) -> Vec {
+ let mut tokenizer = Tokenizer::new();
+ tokenizer.tokenize(sql).unwrap()
+ }
+
+ fn to_token(value: &str, kind: TokenKind) -> Token {
+ Token {
+ value: value.to_string(),
+ kind,
+ }
+ }
+
+ #[test]
+ fn test_insert_simple() {
+ let sql = "INSERT INTO ordenes (id, producto) VALUES (1, 'Laptop')";
+ let tokens = tokenize(sql);
+
+ let query = Query::from(tokens).unwrap();
+
+ assert_eq!(query.operation, Insert);
+ assert_eq!(query.table, "ordenes");
+ assert_eq!(
+ query.columns,
+ vec![to_token("id", Identifier), to_token("producto", Identifier)]
+ );
+ assert_eq!(
+ query.inserts,
+ vec![vec![to_token("1", Number), to_token("Laptop", String),]]
+ );
+ }
+
+ #[test]
+ fn test_insert_multiple_values() {
+ let sql = "INSERT INTO ordenes (id, producto) VALUES (1, 'Laptop'), (2, 'PS4');";
+ let tokens = tokenize(sql);
+ let query = Query::from(tokens).unwrap();
+
+ assert_eq!(query.operation, Insert);
+ assert_eq!(query.table, "ordenes");
+ assert_eq!(
+ query.columns,
+ vec![to_token("id", Identifier), to_token("producto", Identifier)]
+ );
+ assert_eq!(
+ query.inserts,
+ vec![
+ vec![to_token("1", Number), to_token("Laptop", String),],
+ vec![to_token("2", Number), to_token("PS4", String),]
+ ]
+ );
+ }
+
+ #[test]
+ fn test_insert_invalid_columns() {
+ let sql = "INSERT INTO ordenes (id, producto) VALUES (1)";
+ let tokens = tokenize(sql);
+ let result = Query::from(tokens);
+ assert!(result.is_err());
+ assert!(result
+ .unwrap_err()
+ .to_string()
+ .contains("expected 2 columns"));
+ }
+
+ #[test]
+ fn test_insert_invalid_keyword() {
+ let sql = "INSERT INTO ordenes (id, producto) VALUES (1, 'LAPTOP') WHERE 1=1";
+ let tokens = tokenize(sql);
+ let result = Query::from(tokens);
+ assert!(result.is_err());
+ assert!(result.unwrap_err().to_string().contains("WHERE"));
+ }
+
+ #[test]
+ fn test_insert_invalid_missing_parenthesis() {
+ let sql = "INSERT INTO ordenes id, producto VALUES (1, 'LAPTOP')";
+ let tokens = tokenize(sql);
+ let result = Query::from(tokens);
+ assert!(result.is_err());
+ assert!(result.unwrap_err().to_string().contains("expected"));
+ }
+}
diff --git a/src/query/builder/mod.rs b/src/query/builder/mod.rs
new file mode 100644
index 0000000..56775c2
--- /dev/null
+++ b/src/query/builder/mod.rs
@@ -0,0 +1,272 @@
+pub mod delete;
+pub mod expression;
+pub mod insert;
+pub mod select;
+pub mod update;
+
+use crate::errored;
+use crate::query::builder::expression::ExpressionBuilder;
+use crate::query::structs::expression::ExpressionNode;
+use crate::query::structs::operation::Operation;
+use crate::query::structs::operation::Operation::{Delete, Insert, Select, Unknown, Update};
+use crate::query::structs::query::Query;
+use crate::query::structs::token::TokenKind::{
+ Identifier, Keyword, Operator, ParenthesisClose, ParenthesisOpen,
+};
+use crate::query::structs::token::{Token, TokenKind};
+use crate::utils::errors::Errored;
+use crate::utils::errors::Errored::*;
+use std::collections::VecDeque;
+
+/// Interfaz para construir y validar una consulta SQL.
+///
+/// Este trait define los métodos necesarios para analizar y construir consultas SQL,
+/// incluyendo la validación de las palabras clave y la extracción de tokens relacionados
+/// con tablas, columnas y condiciones.
+pub trait Builder {
+ /// Construye y retorna una consulta SQL (`Query`) basada en los tokens disponibles.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error de tipo `Errored` si hay problemas al construir la consulta.
+ fn build(&mut self) -> Result;
+
+ /// Retorna una referencia mutable a la cola de tokens (`VecDeque`).
+ ///
+ /// # Retorno
+ ///
+ /// Una referencia mutable a los tokens procesados por el builder.
+ fn tokens(&mut self) -> &mut VecDeque;
+
+ /// Analiza el nombre de la tabla de una consulta SQL.
+ ///
+ /// Este método maneja las palabras clave `FROM` para consultas `SELECT` y `DELETE`.
+ /// En el caso de otras Operaciones SQL, no hay ningun tipo de token que se interponga entre
+ /// las palabras claves y la tabla, por esto, se saltan.
+ ///
+ /// # Parámetros
+ ///
+ /// - `operation`: Operación SQL (`Select`, `Delete`, etc.) que define cómo se analiza la tabla.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna el nombre de la tabla como `String`.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error `Errored` si no se encuentra un identificador de tabla válido.
+ fn parse_table(&mut self, operation: Operation) -> Result {
+ match operation {
+ Select | Delete => {
+ self.peek_expecting("FROM", Keyword)?;
+ self.tokens().pop_front();
+ }
+ _ => {}
+ }
+ let t = self
+ .tokens()
+ .pop_front()
+ .ok_or_else(|| Syntax("could not find table identifier.".to_string()))?;
+ if t.kind != Identifier {
+ unexpected_token_in_stage("TABLE", &t)?;
+ }
+ Ok(t.value)
+ }
+
+ /// Analiza las columnas de una consulta SQL.
+ ///
+ /// Este método procesa los tokens que representan columnas en las cláusulas `SELECT` o `INSERT`.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna un vector de tokens que representan las columnas de la consulta.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error `Errored` si se encuentra un token inesperado durante el análisis de columnas.
+ fn parse_columns(&mut self) -> Result, Errored> {
+ let mut fields: Vec = vec![];
+ while let Some(t) = self.tokens().front() {
+ match t.kind {
+ Identifier => {
+ if let Some(op) = self.tokens().pop_front() {
+ fields.push(op);
+ }
+ }
+ Keyword if t.value == "FROM" || t.value == "VALUES" => {
+ if fields.is_empty() {
+ errored!(Syntax, "read FROM without any * or fields in query.")
+ }
+ break;
+ }
+ ParenthesisClose => {
+ self.tokens().pop_front();
+ break;
+ }
+ Operator if t.value == "*" => {
+ self.tokens().pop_front();
+ break;
+ }
+ ParenthesisOpen => {
+ self.tokens().pop_front();
+ }
+ _ => unexpected_token_in_stage("COLUMN", t)?,
+ }
+ }
+ Ok(fields)
+ }
+
+ /// Analiza la cláusula `WHERE` en una consulta SQL.
+ ///
+ /// Este método busca la palabra clave `WHERE` y luego construye la expresión correspondiente.
+ ///
+ /// # Retorno
+ ///
+ /// Retorna un nodo de expresión (`ExpressionNode`) que representa la condición `WHERE`.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error `Errored` si no se encuentra o se procesa incorrectamente la cláusula `WHERE`.
+ fn parse_where(&mut self) -> Result {
+ self.pop_expecting("WHERE", Keyword)?;
+ ExpressionBuilder::parse_expressions(self.tokens())
+ }
+
+ /// Valida que no haya más tokens después de la consulta.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error `Errored` si se encuentran tokens adicionales al final de la consulta.
+ fn expect_none(&mut self) -> Result<(), Errored> {
+ if let Some(t) = self.tokens().front() {
+ errored!(Syntax, "expected end of query but got: {:?}", t);
+ }
+ Ok(())
+ }
+
+ /// Extrae el siguiente token si coincide con el valor y tipo esperados.
+ ///
+ /// # Parámetros
+ ///
+ /// - `value`: El valor esperado del token.
+ /// - `kind`: El tipo esperado del token (`TokenKind`).
+ ///
+ /// # Retorno
+ ///
+ /// Retorna el token extraído si cumple con las expectativas, o `None` si no.
+ ///
+ /// # Errores
+ ///
+ /// Retorna un error `Errored` si no se encuentra el token esperado.
+ fn pop_expecting(&mut self, value: &str, kind: TokenKind) -> Result