diff --git a/src/cli/ast/common.rs b/src/cli/ast/common.rs deleted file mode 100644 index bbc36e8..0000000 --- a/src/cli/ast/common.rs +++ /dev/null @@ -1,175 +0,0 @@ -use crate::cli::{ast::{parser::Parser, WhereClause, Operator, OrderByClause, OrderByDirection, LimitClause}, tokenizer::token::TokenTypes}; - -use crate::db::table::Value; -use hex::decode; - -// Returns an error if the current token does not match the given token type -pub fn expect_token_type(parser: &Parser, token_type: TokenTypes) -> Result<(), String> { - let token = parser.current_token()?; - if token.token_type != token_type { - return Err(parser.format_error()); - } - Ok(()) -} - -pub fn token_to_value(parser: &Parser) -> Result { - let token = parser.current_token()?; - - match token.token_type { - TokenTypes::IntLiteral => { - let num = token.value.parse::() - .map_err(|_| parser.format_error())?; - Ok(Value::Integer(num)) - }, - TokenTypes::RealLiteral => { - let num = token.value.parse::() - .map_err(|_| parser.format_error())?; - Ok(Value::Real(num)) - }, - TokenTypes::String => Ok(Value::Text(token.value.to_string())), - TokenTypes::Blob => { - let bytes = decode(token.value) - .map_err(|_| parser.format_error())?; - Ok(Value::Blob(bytes)) - }, - TokenTypes::Null => Ok(Value::Null), - _ => Err(parser.format_error()), - } -} - -// Returns a list of Strings from the tokens when they are formated as "identifier, identifier, ..." -pub fn tokens_to_identifier_list(parser: &mut Parser) -> Result, String> { - let mut identifiers: Vec = vec![]; - loop { - let token = parser.current_token()?; - expect_token_type(parser, TokenTypes::Identifier)?; - - identifiers.push(token.value.to_string()); - parser.advance()?; - let token = parser.current_token()?; - if token.token_type != TokenTypes::Comma { - break; - } - parser.advance()?; - } - return Ok(identifiers); -} - -pub fn get_table_name(parser: &mut Parser) -> Result { - parser.advance()?; - let token = parser.current_token()?; - expect_token_type(parser, TokenTypes::Identifier)?; - let result = token.value.to_string(); - Ok(result) -} - -pub fn get_where_clause(parser: &mut Parser) -> Result, String> { - if expect_token_type(parser, TokenTypes::Where).is_err() { - return Ok(None); - } - parser.advance()?; - - let token = parser.current_token()?; - expect_token_type(parser, TokenTypes::Identifier)?; - let column = token.value.to_string(); - parser.advance()?; - - let token = parser.current_token()?; - let operator = match token.token_type { - TokenTypes::Equals => Operator::Equals, - TokenTypes::NotEquals => Operator::NotEquals, - TokenTypes::LessThan => Operator::LessThan, - TokenTypes::LessEquals => Operator::LessEquals, - TokenTypes::GreaterThan => Operator::GreaterThan, - TokenTypes::GreaterEquals => Operator::GreaterEquals, - _ => return Err(parser.format_error()), - }; - parser.advance()?; - - let value = token_to_value(parser)?; - parser.advance()?; - - return Ok(Some(WhereClause { - column: column, - operator: operator, - value: value, - })); -} - - -pub fn get_order_by(parser: &mut Parser) -> Result>, String> { - if expect_token_type(parser, TokenTypes::Order).is_err() { - return Ok(None); - } - parser.advance()?; - - expect_token_type(parser, TokenTypes::By)?; - parser.advance()?; - - let mut order_by_clauses = vec![]; - loop { - let token = parser.current_token()?; - expect_token_type(parser, TokenTypes::Identifier)?; - let column = token.value.to_string(); - parser.advance()?; - - let token = parser.current_token()?; - let direction = match token.token_type { - TokenTypes::Asc => { - parser.advance()?; - OrderByDirection::Asc - }, - TokenTypes::Desc => { - parser.advance()?; - OrderByDirection::Desc - }, - _ => OrderByDirection::Asc, - }; - - order_by_clauses.push(OrderByClause { - column: column, - direction: direction, - }); - - let token = parser.current_token()?; - if token.token_type != TokenTypes::Comma { - break; - } - parser.advance()?; - } - return Ok(Some(order_by_clauses)); -} - -pub fn get_limit(parser: &mut Parser) -> Result, String> { - if expect_token_type(parser, TokenTypes::Limit).is_err() { - return Ok(None); - } - parser.advance()?; - - expect_token_type(parser, TokenTypes::IntLiteral)?; - let limit = token_to_value(parser)?; - parser.advance()?; - - let token = parser.current_token()?; - if token.token_type != TokenTypes::Offset { - return Ok(Some(LimitClause { - limit: limit, - offset: None, - })); - } - parser.advance()?; - - expect_token_type(parser, TokenTypes::IntLiteral)?; - let offset = token_to_value(parser)?; - if let Value::Integer(offset) = offset { - if offset < 0 { - return Err(parser.format_error()); - } - }; - parser.advance()?; - - return Ok(Some(LimitClause { - limit: limit, - offset: Some(offset), - })); -} \ No newline at end of file diff --git a/src/cli/ast/create_statement.rs b/src/cli/ast/create_statement.rs index 983ae16..ded96b2 100644 --- a/src/cli/ast/create_statement.rs +++ b/src/cli/ast/create_statement.rs @@ -1,4 +1,10 @@ -use crate::cli::{ast::{parser::Parser, CreateTableStatement, SqlStatement::{self, CreateTable}, common::expect_token_type}, tokenizer::token::TokenTypes}; +use crate::cli::{ + ast::{ + parser::Parser, CreateTableStatement, SqlStatement::{self, CreateTable}, + helpers::common::{expect_token_type, get_table_name} + }, + tokenizer::token::TokenTypes +}; use crate::db::table::{ColumnDefinition, DataType}; pub fn build(parser: &mut Parser) -> Result { @@ -22,11 +28,8 @@ pub fn build(parser: &mut Parser) -> Result { } fn table_statement(parser: &mut Parser) -> Result { - parser.advance()?; - - let token = parser.current_token()?; - expect_token_type(parser, TokenTypes::Identifier)?; - let table_name = token.value.to_string(); + // Get the table name + let table_name = get_table_name(parser)?; parser.advance()?; let column_definitions = column_definitions(parser)?; @@ -100,16 +103,7 @@ fn index_statement(_parser: &mut Parser) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::cli::tokenizer::scanner::Token; - - fn token(tt: TokenTypes, val: &'static str) -> Token<'static> { - Token { - token_type: tt, - value: val, - col_num: 0, - line_num: 1, - } - } + use crate::cli::ast::test_utils::token; #[test] fn create_table_generates_proper_statement(){ diff --git a/src/cli/ast/delete_statement.rs b/src/cli/ast/delete_statement.rs index e4f3d82..185fde9 100644 --- a/src/cli/ast/delete_statement.rs +++ b/src/cli/ast/delete_statement.rs @@ -1,5 +1,13 @@ -use crate::cli::ast::{parser::Parser, SqlStatement, DeleteStatement, common::{expect_token_type, get_table_name, get_where_clause, get_order_by, get_limit}}; -use crate::cli::tokenizer::token::TokenTypes; +use crate::cli::{ + ast::{ + parser::Parser, SqlStatement, DeleteStatement, + helpers::{ + common::{expect_token_type, get_table_name}, + order_by_clause::get_order_by, where_clause::get_where_clause, limit_clause::get_limit + } + }, + tokenizer::token::TokenTypes +}; pub fn build(parser: &mut Parser) -> Result { parser.advance()?; @@ -21,7 +29,7 @@ pub fn build(parser: &mut Parser) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::cli::tokenizer::scanner::Token; + use crate::cli::ast::test_utils::token; use crate::cli::ast::OrderByClause; use crate::cli::ast::OrderByDirection; use crate::cli::ast::LimitClause; @@ -29,15 +37,6 @@ mod tests { use crate::cli::ast::WhereClause; use crate::db::table::Value; - fn token(tt: TokenTypes, val: &'static str) -> Token<'static> { - Token { - token_type: tt, - value: val, - col_num: 0, - line_num: 1, - } - } - #[test] fn delete_statement_with_all_tokens_is_generated_correctly() { // DELETE FROM users; diff --git a/src/cli/ast/helpers/common.rs b/src/cli/ast/helpers/common.rs new file mode 100644 index 0000000..df28bbf --- /dev/null +++ b/src/cli/ast/helpers/common.rs @@ -0,0 +1,64 @@ +use crate::cli::{ast::{parser::Parser}, tokenizer::token::TokenTypes}; + +use crate::db::table::Value; +use hex::decode; + +// Returns an error if the current token does not match the given token type +pub fn expect_token_type(parser: &Parser, token_type: TokenTypes) -> Result<(), String> { + let token = parser.current_token()?; + if token.token_type != token_type { + return Err(parser.format_error()); + } + Ok(()) +} + +pub fn token_to_value(parser: &Parser) -> Result { + let token = parser.current_token()?; + + match token.token_type { + TokenTypes::IntLiteral => { + let num = token.value.parse::() + .map_err(|_| parser.format_error())?; + Ok(Value::Integer(num)) + }, + TokenTypes::RealLiteral => { + let num = token.value.parse::() + .map_err(|_| parser.format_error())?; + Ok(Value::Real(num)) + }, + TokenTypes::String => Ok(Value::Text(token.value.to_string())), + TokenTypes::Blob => { + let bytes = decode(token.value) + .map_err(|_| parser.format_error())?; + Ok(Value::Blob(bytes)) + }, + TokenTypes::Null => Ok(Value::Null), + _ => Err(parser.format_error()), + } +} + +// Returns a list of Strings from the tokens when they are formated as "identifier, identifier, ..." +pub fn tokens_to_identifier_list(parser: &mut Parser) -> Result, String> { + let mut identifiers: Vec = vec![]; + loop { + let token = parser.current_token()?; + expect_token_type(parser, TokenTypes::Identifier)?; + + identifiers.push(token.value.to_string()); + parser.advance()?; + let token = parser.current_token()?; + if token.token_type != TokenTypes::Comma { + break; + } + parser.advance()?; + } + return Ok(identifiers); +} + +pub fn get_table_name(parser: &mut Parser) -> Result { + parser.advance()?; + let token = parser.current_token()?; + expect_token_type(parser, TokenTypes::Identifier)?; + let result = token.value.to_string(); + Ok(result) +} \ No newline at end of file diff --git a/src/cli/ast/helpers/limit_clause.rs b/src/cli/ast/helpers/limit_clause.rs new file mode 100644 index 0000000..8788525 --- /dev/null +++ b/src/cli/ast/helpers/limit_clause.rs @@ -0,0 +1,114 @@ +use crate::cli::ast::{parser::Parser, LimitClause}; +use crate::cli::tokenizer::token::TokenTypes; +use crate::db::table::Value; +use crate::cli::ast::helpers::common::{expect_token_type, token_to_value}; + +pub fn get_limit(parser: &mut Parser) -> Result, String> { + if expect_token_type(parser, TokenTypes::Limit).is_err() { + return Ok(None); + } + parser.advance()?; + + expect_token_type(parser, TokenTypes::IntLiteral)?; + let limit = token_to_value(parser)?; + parser.advance()?; + + let token = parser.current_token()?; + if token.token_type != TokenTypes::Offset { + return Ok(Some(LimitClause { + limit: limit, + offset: None, + })); + } + parser.advance()?; + + expect_token_type(parser, TokenTypes::IntLiteral)?; + let offset = token_to_value(parser)?; + if let Value::Integer(offset) = offset { + if offset < 0 { + return Err(parser.format_error()); + } + }; + parser.advance()?; + + return Ok(Some(LimitClause { + limit: limit, + offset: Some(offset), + })); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::ast::test_utils::token; + + #[test] + fn limit_clause_is_generated_correctly() { + // LIMIT 10 OFFSET 5; + let tokens = vec![ + token(TokenTypes::Limit, "LIMIT"), + token(TokenTypes::IntLiteral, "10"), + token(TokenTypes::Offset, "OFFSET"), + token(TokenTypes::IntLiteral, "5"), + token(TokenTypes::SemiColon, ";"), + ]; + let mut parser = Parser::new(tokens); + let result = get_limit(&mut parser); + assert!(result.is_ok()); + let limit_clause = result.unwrap(); + let expected = Some(LimitClause { + limit: Value::Integer(10), + offset: Some(Value::Integer(5)), + }); + assert_eq!(expected, limit_clause); + } + + #[test] + fn limit_clause_with_no_offset_is_generated_correctly() { + // LIMIT 10; + let tokens = vec![ + token(TokenTypes::Limit, "LIMIT"), + token(TokenTypes::IntLiteral, "10"), + token(TokenTypes::SemiColon, ";"), + ]; + let mut parser = Parser::new(tokens); + let result = get_limit(&mut parser); + assert!(result.is_ok()); + let limit_clause = result.unwrap(); + let expected = Some(LimitClause { + limit: Value::Integer(10), + offset: None, + }); + assert_eq!(expected, limit_clause); + } + + #[test] + fn not_limit_clause_returns_none() { + // SELECT * ...; + let tokens = vec![ + token(TokenTypes::Select, "SELECT"), + token(TokenTypes::Asterisk, "*"), + ]; + let mut parser = Parser::new(tokens); + let result = get_limit(&mut parser); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); + assert_eq!(parser.current_token().unwrap().token_type, TokenTypes::Select); + } + + #[test] + fn limit_clause_with_negative_offset_is_generated_correctly() { + // LIMIT 10 OFFSET -5; + let tokens = vec![ + token(TokenTypes::Limit, "LIMIT"), + token(TokenTypes::IntLiteral, "10"), + token(TokenTypes::Offset, "OFFSET"), + token(TokenTypes::IntLiteral, "-5"), + token(TokenTypes::SemiColon, ";"), + ]; + let mut parser = Parser::new(tokens); + let result = get_limit(&mut parser); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), "Error at line 1, column 0: Unexpected value: -5"); + } +} \ No newline at end of file diff --git a/src/cli/ast/helpers/mod.rs b/src/cli/ast/helpers/mod.rs new file mode 100644 index 0000000..e9bfff0 --- /dev/null +++ b/src/cli/ast/helpers/mod.rs @@ -0,0 +1,4 @@ +pub mod where_clause; +pub mod order_by_clause; +pub mod limit_clause; +pub mod common; \ No newline at end of file diff --git a/src/cli/ast/helpers/order_by_clause.rs b/src/cli/ast/helpers/order_by_clause.rs new file mode 100644 index 0000000..9ccafc4 --- /dev/null +++ b/src/cli/ast/helpers/order_by_clause.rs @@ -0,0 +1,115 @@ +use crate::cli::ast::{parser::Parser, OrderByClause, OrderByDirection}; +use crate::cli::tokenizer::token::TokenTypes; + +use crate::cli::ast::helpers::common::expect_token_type; + +pub fn get_order_by(parser: &mut Parser) -> Result>, String> { + if expect_token_type(parser, TokenTypes::Order).is_err() { + return Ok(None); + } + parser.advance()?; + + expect_token_type(parser, TokenTypes::By)?; + parser.advance()?; + + let mut order_by_clauses = vec![]; + loop { + let token = parser.current_token()?; + expect_token_type(parser, TokenTypes::Identifier)?; + let column = token.value.to_string(); + parser.advance()?; + + let token = parser.current_token()?; + let direction = match token.token_type { + TokenTypes::Asc => { + parser.advance()?; + OrderByDirection::Asc + }, + TokenTypes::Desc => { + parser.advance()?; + OrderByDirection::Desc + }, + _ => OrderByDirection::Asc, + }; + + order_by_clauses.push(OrderByClause { + column: column, + direction: direction, + }); + + let token = parser.current_token()?; + if token.token_type != TokenTypes::Comma { + break; + } + parser.advance()?; + } + return Ok(Some(order_by_clauses)); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::ast::test_utils::token; + + #[test] + fn order_by_clause_is_generated_correctly() { + // ORDER BY id ASC LIMIT...; + let tokens = vec![ + token(TokenTypes::Order, "ORDER"), + token(TokenTypes::By, "BY"), + token(TokenTypes::Identifier, "id"), + token(TokenTypes::Asc, "ASC"), + token(TokenTypes::Limit, "LIMIT"), + ]; + let mut parser = Parser::new(tokens); + let result = get_order_by(&mut parser); + assert!(result.is_ok()); + let order_by_clause = result.unwrap(); + let expected = Some(vec![OrderByClause { + column: "id".to_string(), + direction: OrderByDirection::Asc, + }]); + assert_eq!(expected, order_by_clause); + } + + #[test] + fn not_order_by_clause_returns_none() { + // SELECT * ...; + let tokens = vec![ + token(TokenTypes::Select, "SELECT"), + token(TokenTypes::Asterisk, "*"), + ]; + let mut parser = Parser::new(tokens); + let result = get_order_by(&mut parser); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); + assert_eq!(parser.current_token().unwrap().token_type, TokenTypes::Select); + } + + #[test] + fn order_by_clause_with_multiple_columns_is_generated_correctly() { + // ORDER BY id ASC, name DESC; + let tokens = vec![ + token(TokenTypes::Order, "ORDER"), + token(TokenTypes::By, "BY"), + token(TokenTypes::Identifier, "id"), + token(TokenTypes::Asc, "ASC"), + token(TokenTypes::Comma, ","), + token(TokenTypes::Identifier, "name"), + token(TokenTypes::Desc, "DESC"), + token(TokenTypes::SemiColon, ";"), + ]; + let mut parser = Parser::new(tokens); + let result = get_order_by(&mut parser); + assert!(result.is_ok()); + let order_by_clause = result.unwrap(); + let expected = Some(vec![OrderByClause { + column: "id".to_string(), + direction: OrderByDirection::Asc, + }, OrderByClause { + column: "name".to_string(), + direction: OrderByDirection::Desc, + }]); + assert_eq!(expected, order_by_clause); + } +} \ No newline at end of file diff --git a/src/cli/ast/helpers/where_clause.rs b/src/cli/ast/helpers/where_clause.rs new file mode 100644 index 0000000..790e4f1 --- /dev/null +++ b/src/cli/ast/helpers/where_clause.rs @@ -0,0 +1,88 @@ +use crate::cli::ast::{parser::Parser, WhereClause, Operator, helpers::common::{expect_token_type, token_to_value}}; +use crate::cli::tokenizer::token::TokenTypes; + +pub fn get_where_clause(parser: &mut Parser) -> Result, String> { + if expect_token_type(parser, TokenTypes::Where).is_err() { + return Ok(None); + } + parser.advance()?; + + let token = parser.current_token()?; + expect_token_type(parser, TokenTypes::Identifier)?; + let column = token.value.to_string(); + parser.advance()?; + + let token = parser.current_token()?; + let operator = match token.token_type { + TokenTypes::Equals => Operator::Equals, + TokenTypes::NotEquals => Operator::NotEquals, + TokenTypes::LessThan => Operator::LessThan, + TokenTypes::LessEquals => Operator::LessEquals, + TokenTypes::GreaterThan => Operator::GreaterThan, + TokenTypes::GreaterEquals => Operator::GreaterEquals, + _ => return Err(parser.format_error()), + }; + parser.advance()?; + + let value = token_to_value(parser)?; + parser.advance()?; + + return Ok(Some(WhereClause { + column: column, + operator: operator, + value: value, + })); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::tokenizer::scanner::Token; + use crate::db::table::Value; + + fn token(tt: TokenTypes, val: &'static str) -> Token<'static> { + Token { + token_type: tt, + value: val, + col_num: 0, + line_num: 1, + } + } + + #[test] + fn where_clause_with_all_tokens_is_generated_correctly() { + // WHERE id = 1 LIMIT... + let tokens = vec![ + token(TokenTypes::Where, "WHERE"), + token(TokenTypes::Identifier, "id"), + token(TokenTypes::Equals, "="), + token(TokenTypes::IntLiteral, "1"), + token(TokenTypes::Limit, "LIMIT"), + ]; + let mut parser = Parser::new(tokens); + let result = get_where_clause(&mut parser); + assert!(result.is_ok()); + let where_clause = result.unwrap(); + let expected = Some(WhereClause { + column: "id".to_string(), + operator: Operator::Equals, + value: Value::Integer(1), + }); + assert_eq!(expected, where_clause); + assert_eq!(parser.current_token().unwrap().token_type, TokenTypes::Limit); + } + + #[test] + fn not_where_clause_returns_none() { + // SELECT * ...; + let tokens = vec![ + token(TokenTypes::Select, "SELECT"), + token(TokenTypes::Asterisk, "*"), + ]; + let mut parser = Parser::new(tokens); + let result = get_where_clause(&mut parser); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); + assert_eq!(parser.current_token().unwrap().token_type, TokenTypes::Select); + } +} diff --git a/src/cli/ast/insert_statement.rs b/src/cli/ast/insert_statement.rs index 76ebb6c..98a186e 100644 --- a/src/cli/ast/insert_statement.rs +++ b/src/cli/ast/insert_statement.rs @@ -1,4 +1,10 @@ -use crate::cli::{ast::{parser::Parser, common::token_to_value, common::expect_token_type, InsertIntoStatement, SqlStatement::{self, InsertInto}}, tokenizer::token::TokenTypes}; +use crate::cli::{ + ast::{ + helpers::common::{token_to_value, expect_token_type, get_table_name}, + parser::Parser, InsertIntoStatement, SqlStatement::{self, InsertInto} + }, + tokenizer::token::TokenTypes +}; use crate::db::table::Value; pub fn build(parser: &mut Parser) -> Result { @@ -22,11 +28,7 @@ pub fn build(parser: &mut Parser) -> Result { } fn into_statement(parser: &mut Parser) -> Result { - parser.advance()?; - - let token = parser.current_token()?; - expect_token_type(parser, TokenTypes::Identifier)?; - let table_name = token.value.to_string(); + let table_name = get_table_name(parser)?; parser.advance()?; let token = parser.current_token()?; @@ -134,16 +136,7 @@ fn or_statement(_parser: &mut Parser) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::cli::tokenizer::scanner::Token; - - fn token(tt: TokenTypes, val: &'static str) -> Token<'static> { - Token { - token_type: tt, - value: val, - col_num: 0, - line_num: 1, - } - } + use crate::cli::ast::test_utils::token; #[test] fn single_row_insert_statement_is_generated_correctly() { diff --git a/src/cli/ast/mod.rs b/src/cli/ast/mod.rs index 25708b8..8c1d5ef 100644 --- a/src/cli/ast/mod.rs +++ b/src/cli/ast/mod.rs @@ -1,13 +1,15 @@ use crate::cli::tokenizer::{scanner::Token, token::TokenTypes}; use crate::db::table::{ColumnDefinition, Value}; -mod common; mod create_statement; mod insert_statement; mod parser; mod select_statement; mod update_statement; mod delete_statement; +mod helpers; +#[cfg(test)] +mod test_utils; #[derive(Debug, PartialEq)] pub enum SqlStatement { @@ -181,16 +183,7 @@ pub fn generate(tokens: Vec) -> Vec> { #[cfg(test)] mod tests { use super::*; - use crate::cli::tokenizer::scanner::Token; - - fn token(tt: TokenTypes, val: &'static str) -> Token<'static> { - Token { - token_type: tt, - value: val, - col_num: 0, - line_num: 1, - } - } + use super::test_utils::token; #[test] fn ast_handles_invalid_statements_gracefully() { diff --git a/src/cli/ast/parser.rs b/src/cli/ast/parser.rs index f414d4b..4ebc884 100644 --- a/src/cli/ast/parser.rs +++ b/src/cli/ast/parser.rs @@ -3,8 +3,6 @@ use crate::cli::{ tokenizer::scanner::Token, tokenizer::token::TokenTypes, }; - - pub struct Parser<'a> { tokens: Vec>, current: usize, @@ -80,15 +78,7 @@ impl<'a> Parser<'a> { mod tests { use super::*; use crate::cli::ast::{CreateTableStatement, InsertIntoStatement, SelectStatement, SelectStatementColumns}; - - fn token(tt: TokenTypes, val: &'static str, col: usize, line: usize) -> Token<'static> { - Token { - token_type: tt, - value: val, - col_num: col, - line_num: line, - } - } + use crate::cli::ast::test_utils::{token_with_location, token}; #[test] fn parser_formats_error_when_at_end_of_input() { @@ -100,7 +90,7 @@ mod tests { #[test] fn parser_formats_error_when_unexpected_token_type() { - let tokens = vec![token(TokenTypes::Insert, "INSERT", 15, 3)]; + let tokens = vec![token_with_location(TokenTypes::Insert, "INSERT", 15, 3)]; let parser = Parser::new(tokens); let result = parser.format_error(); assert_eq!(result, "Error at line 3, column 15: Unexpected value: INSERT"); @@ -152,13 +142,13 @@ mod tests { #[test] fn parser_next_statement_filters_options_correctly_handles_multiple_statements() { let tokens = vec![ - token(TokenTypes::Create, "CREATE", 1, 1), - token(TokenTypes::SemiColon, ";", 1, 1), - token(TokenTypes::Insert, "INSERT", 1, 1), - token(TokenTypes::SemiColon, ";", 1, 1), - token(TokenTypes::Select, "SELECT", 1, 1), - token(TokenTypes::SemiColon, ";", 1, 1), - token(TokenTypes::EOF, "", 1, 1), + token(TokenTypes::Create, "CREATE"), + token(TokenTypes::SemiColon, ";"), + token(TokenTypes::Insert, "INSERT"), + token(TokenTypes::SemiColon, ";"), + token(TokenTypes::Select, "SELECT"), + token(TokenTypes::SemiColon, ";"), + token(TokenTypes::EOF, ""), ]; let mut parser = Parser::new(tokens); let builder : &dyn StatementBuilder = &MockStatementBuilder; @@ -199,14 +189,14 @@ mod tests { #[test] fn parser_next_statement_handles_errors_correctly() { let tokens = vec![ - token(TokenTypes::Identifier, "users", 1, 1), - token(TokenTypes::SemiColon, ";", 1, 1), - token(TokenTypes::EOF, "", 1, 1), + token(TokenTypes::Identifier, "users"), + token(TokenTypes::SemiColon, ";"), + token(TokenTypes::EOF, ""), ]; let mut parser = Parser::new(tokens); let builder : &dyn StatementBuilder = &MockStatementBuilder; let result = parser.next_statement(builder); - let expected = Some(Err("Error at line 1, column 1: Unexpected value: users".to_string())); + let expected = Some(Err("Error at line 1, column 0: Unexpected value: users".to_string())); assert_eq!(result, expected); } } diff --git a/src/cli/ast/select_statement.rs b/src/cli/ast/select_statement.rs index 7e36450..f034776 100644 --- a/src/cli/ast/select_statement.rs +++ b/src/cli/ast/select_statement.rs @@ -1,4 +1,13 @@ -use crate::{cli::{ast::{common::{expect_token_type, get_where_clause, tokens_to_identifier_list, get_order_by, get_limit, get_table_name}, parser::Parser, SelectStatement, SelectStatementColumns, SqlStatement, WhereClause}, tokenizer::token::TokenTypes}}; +use crate::{cli::{ + ast::{ + parser::Parser, SelectStatement, SelectStatementColumns, SqlStatement, WhereClause, + helpers::{ + common::{expect_token_type, tokens_to_identifier_list, get_table_name}, + order_by_clause::get_order_by, where_clause::get_where_clause, limit_clause::get_limit + } + }, + tokenizer::token::TokenTypes +}}; pub fn build(parser: &mut Parser) -> Result { parser.advance()?; @@ -36,20 +45,11 @@ fn get_columns(parser: &mut Parser) -> Result { mod tests { use super::*; use crate::cli::ast::Operator; - use crate::cli::tokenizer::scanner::Token; use crate::db::table::Value; use crate::cli::ast::OrderByClause; use crate::cli::ast::OrderByDirection; use crate::cli::ast::LimitClause; - - fn token(tt: TokenTypes, val: &'static str) -> Token<'static> { - Token { - token_type: tt, - value: val, - col_num: 0, - line_num: 1, - } - } + use crate::cli::ast::test_utils::token; #[test] fn select_statement_with_all_tokens_is_generated_correctly() { @@ -188,62 +188,4 @@ mod tests { }), })); } - - #[test] - fn select_statement_with_limit_clause_no_offset_is_generated_correctly() { - // SELECT id FROM guests WHERE id > 1 LIMIT 10; - let tokens = vec![ - token(TokenTypes::Select, "SELECT"), - token(TokenTypes::Identifier, "id"), - token(TokenTypes::From, "FROM"), - token(TokenTypes::Identifier, "guests"), - token(TokenTypes::Where, "WHERE"), - token(TokenTypes::Identifier, "id"), - token(TokenTypes::GreaterThan, ">"), - token(TokenTypes::IntLiteral, "1"), - token(TokenTypes::Limit, "LIMIT"), - token(TokenTypes::IntLiteral, "10"), - token(TokenTypes::SemiColon, ";"), - ]; - let mut parser = Parser::new(tokens); - let result = build(&mut parser); - assert!(result.is_ok()); - let statement = result.unwrap(); - assert_eq!(statement, SqlStatement::Select(SelectStatement { - table_name: "guests".to_string(), - columns: SelectStatementColumns::Specific(vec![ - "id".to_string(), - ]), - where_clause: Some(WhereClause { - column: "id".to_string(), - operator: Operator::GreaterThan, - value: Value::Integer(1), - }), - order_by_clause: None, - limit_clause: Some(LimitClause { - limit: Value::Integer(10), - offset: None, - }), - })); - } - - #[test] - fn select_statement_with_limit_clause_with_negative_offset_is_generated_correctly() { - // SELECT id FROM guests LIMIT 10 OFFSET -5; - let tokens = vec![ - token(TokenTypes::Select, "SELECT"), - token(TokenTypes::Identifier, "id"), - token(TokenTypes::From, "FROM"), - token(TokenTypes::Identifier, "guests"), - token(TokenTypes::Limit, "LIMIT"), - token(TokenTypes::IntLiteral, "10"), - token(TokenTypes::Offset, "OFFSET"), - token(TokenTypes::IntLiteral, "-5"), - token(TokenTypes::SemiColon, ";"), - ]; - let mut parser = Parser::new(tokens); - let result = build(&mut parser); - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), "Error at line 1, column 0: Unexpected value: -5"); - } } \ No newline at end of file diff --git a/src/cli/ast/test_utils.rs b/src/cli/ast/test_utils.rs new file mode 100644 index 0000000..f76b02f --- /dev/null +++ b/src/cli/ast/test_utils.rs @@ -0,0 +1,24 @@ + +#[cfg(test)] +use crate::cli::tokenizer::token::TokenTypes; +#[cfg(test)] +use crate::cli::tokenizer::scanner::Token; + +#[cfg(test)] +pub fn token(tt: TokenTypes, val: &'static str) -> Token<'static> { + Token { + token_type: tt, + value: val, + col_num: 0, + line_num: 1, + } +} + +pub fn token_with_location(tt: TokenTypes, val: &'static str, col: usize, line: usize) -> Token<'static> { + Token { + token_type: tt, + value: val, + col_num: col, + line_num: line, + } +} diff --git a/src/cli/ast/update_statement.rs b/src/cli/ast/update_statement.rs index 64ea716..7bbdc05 100644 --- a/src/cli/ast/update_statement.rs +++ b/src/cli/ast/update_statement.rs @@ -1,4 +1,8 @@ -use crate::cli::ast::{parser::Parser, SqlStatement, UpdateStatement, ColumnValue, common::{expect_token_type, get_where_clause, token_to_value, get_table_name}}; +use crate::cli::ast::{ + parser::Parser, SqlStatement, UpdateStatement, ColumnValue, + helpers::common::{expect_token_type, token_to_value, get_table_name} +}; +use crate::cli::ast::helpers::where_clause::get_where_clause; use crate::cli::tokenizer::token::TokenTypes; pub fn build(parser: &mut Parser) -> Result { @@ -54,19 +58,10 @@ fn get_update_values(parser: &mut Parser) -> Result, String> { #[cfg(test)] mod tests { use super::*; - use crate::cli::tokenizer::scanner::Token; use crate::db::table::Value; use crate::cli::ast::Operator; use crate::cli::ast::WhereClause; - - fn token(tt: TokenTypes, val: &'static str) -> Token<'static> { - Token { - token_type: tt, - value: val, - col_num: 0, - line_num: 1, - } - } + use crate::cli::ast::test_utils::token; #[test] fn update_statement_with_all_tokens_is_generated_correctly() {