diff --git a/docs/architecture/EAQL.ebnf b/docs/architecture/EAQL.ebnf index 78e9fae..8e76abe 100644 --- a/docs/architecture/EAQL.ebnf +++ b/docs/architecture/EAQL.ebnf @@ -10,6 +10,13 @@ ::= | +## Utilities +### Conditionals + ::= { } [ ] + ::= + ::= { } + ::= { } + ::= ## Database Queries ### Interaction @@ -25,6 +32,7 @@ ## Table Queries ::= | + | ### Accessing ::= ( | ) [ ] [ ] @@ -34,15 +42,8 @@ ::= [ ] ### Table Interaction - ::= - -## Utilities -### Conditionals - ::= { } [ ] - ::= - ::= { } - ::= { } - ::= + ::=
+ ::=
## Tokens ### Literal Tokens diff --git a/src/bin/repl.rs b/src/bin/repl.rs index 2a3a4a4..5e8c6a8 100644 --- a/src/bin/repl.rs +++ b/src/bin/repl.rs @@ -12,9 +12,13 @@ fn main() { match args[args.len() - 1].as_str() { "transpile" => transpiler::repl_loop(), - "query_test" => validator::repl_loop(), + "validate" => validator::repl_loop(), _ => utils::help::display_help(Some( - format!("Invalid Testing CLI Argument -> {}, see usage!", args[2]).as_str(), + format!( + "Invalid Testing CLI Argument -> {}, see usage!", + args[args.len() - 1] + ) + .as_str(), )), } } diff --git a/src/language/parser/conditional.rs b/src/language/parser/conditional.rs index 15b91c0..fcff1c1 100644 --- a/src/language/parser/conditional.rs +++ b/src/language/parser/conditional.rs @@ -10,8 +10,8 @@ use crate::{ #[derive(Debug, PartialEq)] pub struct ConditionNode { - _condition: ConditionChild, - _literal: String, + pub condition: ConditionChild, + pub literal: String, _depth: u16, } @@ -25,26 +25,25 @@ pub enum ConditionChild { #[derive(Debug, PartialEq)] pub struct OperandNode { - _type: String, - - _ls: ConditionChild, - _rs: ConditionChild, + pub op: String, + pub ls: ConditionChild, + pub rs: ConditionChild, _depth: u16, } #[derive(Debug, PartialEq)] pub struct ExpressionNode { - _identifier: Token, - _comparison_operator: Token, - _literal: Token, + pub identifier: Token, + pub comparison_operator: Token, + pub literal: Token, _depth: u16, } #[derive(Debug, PartialEq)] pub struct BoolNode { - _value: bool, + pub value: bool, _depth: u16, } @@ -53,8 +52,8 @@ fn update_depths(node: &mut ConditionChild) -> () { match node { ConditionChild::Op(state) => { state._depth += 1; - update_depths(&mut state._ls); - update_depths(&mut state._rs); + update_depths(&mut state.ls); + update_depths(&mut state.rs); return; } ConditionChild::Bool(state) => state._depth += 1, @@ -72,9 +71,9 @@ fn handle_open_paren( closing_or: &mut bool, ) -> Result { let mut ret: ConditionChild = ConditionChild::Op(Box::new(OperandNode { - _type: "OR".to_string(), + op: "OR".to_string(), _depth: depth, - _ls: match recurse_down( + ls: match recurse_down( tokens, idx, depth + 1, @@ -87,7 +86,7 @@ fn handle_open_paren( Ok(node) => node, Err(msg) => return Err(msg), }, - _rs: match recurse_down( + rs: match recurse_down( tokens, idx, depth + 1, @@ -114,9 +113,9 @@ fn handle_open_paren( } return Ok(ConditionChild::Op(Box::new(OperandNode { - _type: "AND".to_string(), - _ls: ret, - _rs: match recurse_down( + op: "AND".to_string(), + ls: ret, + rs: match recurse_down( tokens, idx, depth + 1, @@ -144,9 +143,9 @@ fn handle_open_paren( } return Ok(ConditionChild::Op(Box::new(OperandNode { - _type: "OR".to_string(), - _ls: ret, - _rs: match recurse_down( + op: "OR".to_string(), + ls: ret, + rs: match recurse_down( tokens, idx, depth + 1, @@ -208,9 +207,9 @@ fn handle_and( closing_or: &mut bool, ) -> Result { Ok(ConditionChild::Op(Box::new(OperandNode { - _type: "AND".to_string(), + op: "AND".to_string(), _depth: depth, - _ls: match recurse_down( + ls: match recurse_down( tokens, idx, depth + 1, @@ -223,7 +222,7 @@ fn handle_and( Ok(node) => node, Err(msg) => return Err(msg), }, - _rs: match recurse_down( + rs: match recurse_down( tokens, idx, depth + 1, @@ -269,10 +268,10 @@ fn handle_literal( }; return Ok(ConditionChild::Op(Box::new(OperandNode { - _type: "AND".to_string(), + op: "AND".to_string(), _depth: depth, - _ls: ls, - _rs: rs, + ls, + rs, }))); } @@ -280,12 +279,12 @@ fn handle_close(parent_node: &String, depth: u16) -> ConditionChild { // AND default to true, OR defaults to false if *parent_node == "AND" { ConditionChild::Bool(Box::new(BoolNode { - _value: true, + value: true, _depth: depth, })) } else { ConditionChild::Bool(Box::new(BoolNode { - _value: false, + value: false, _depth: depth, })) } @@ -425,9 +424,9 @@ fn recurse_down( *closing_or = false; return Ok(ConditionChild::Op(Box::new(OperandNode { - _type: "OR".to_string(), + op: "OR".to_string(), _depth: depth, - _ls: match recurse_down( + ls: match recurse_down( tokens, idx, depth + 1, @@ -440,7 +439,7 @@ fn recurse_down( Ok(node) => node, Err(msg) => return Err(msg), }, - _rs: match recurse_down( + rs: match recurse_down( tokens, idx, depth + 1, @@ -533,9 +532,9 @@ impl ConditionNode { let start_idx: usize = *idx; let ret: ConditionChild = ConditionChild::Op(Box::new(OperandNode { - _type: "OR".to_string(), + op: "OR".to_string(), _depth: depth + 1, - _ls: match recurse_down( + ls: match recurse_down( tokens, idx, depth + 2, @@ -548,7 +547,7 @@ impl ConditionNode { Ok(node) => node, Err(msg) => return Err(msg), }, - _rs: match recurse_down( + rs: match recurse_down( tokens, idx, depth + 2, @@ -568,9 +567,9 @@ impl ConditionNode { } return Ok(ConditionNode { - _condition: ret, + condition: ret, _depth: depth, - _literal: ConditionNode::reconstruct_literal(tokens, start_idx, *idx), + literal: ConditionNode::reconstruct_literal(tokens, start_idx, *idx), }); } @@ -578,12 +577,12 @@ impl ConditionNode { /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. pub fn transpile_color(&self) -> String { - format!("{}", self._literal) + format!("{}", self.literal) } /// Outputs current AST node transpiled to raw SQL pub fn transpile_raw(&self) -> String { - format!("{}", self._literal) + format!("{}", self.literal) } } @@ -634,9 +633,9 @@ impl ExpressionNode { } return Ok(ExpressionNode { - _identifier: identifier, - _comparison_operator: comparison_operator, - _literal: literal, + identifier, + comparison_operator, + literal, _depth: depth, }); @@ -661,7 +660,7 @@ impl fmt::Display for ConditionNode { "\n{}(Condition){}{}", get_tab(self._depth), get_tab(self._depth + 1), - self._condition + self.condition ) } } @@ -672,16 +671,16 @@ impl fmt::Display for OperandNode { f, "\n{}(Operand::{}){}{}", get_tab(self._depth), - self._type, - self._ls, - self._rs, + self.op, + self.ls, + self.rs, ) } } impl fmt::Display for BoolNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "\n{}(Bool::{})", get_tab(self._depth), self._value,) + write!(f, "\n{}(Bool::{})", get_tab(self._depth), self.value,) } } @@ -695,11 +694,11 @@ impl fmt::Display for ExpressionNode { {}value: {:?}", get_tab(self._depth), get_tab(self._depth + 1), - self._identifier.lexeme, + self.identifier.lexeme, get_tab(self._depth + 1), - self._comparison_operator.token_type, + self.comparison_operator.token_type, get_tab(self._depth + 1), - self._literal.literal + self.literal.literal ) } } @@ -720,9 +719,9 @@ mod tests { let mut idx: usize = 0; let depth: u16 = 0; let expected: ExpressionNode = ExpressionNode { - _identifier: Token::new(TokenType::Identifier, &"".to_string(), &"id".to_string()), - _comparison_operator: Token::new(TokenType::Equal, &"".to_string(), &"is".to_string()), - _literal: Token::new(TokenType::NumberLiteral, &"5".to_string(), &"5".to_string()), + identifier: Token::new(TokenType::Identifier, &"".to_string(), &"id".to_string()), + comparison_operator: Token::new(TokenType::Equal, &"".to_string(), &"is".to_string()), + literal: Token::new(TokenType::NumberLiteral, &"5".to_string(), &"5".to_string()), _depth: 0, }; diff --git a/src/language/parser/database.rs b/src/language/parser/database.rs index d230c72..0b4e893 100644 --- a/src/language/parser/database.rs +++ b/src/language/parser/database.rs @@ -1,8 +1,6 @@ /* This handles our database mutators and accessors (creation and deletion of databases) - -Make the database db_name. */ use crate::{ @@ -22,10 +20,10 @@ use std::{fmt, usize}; #[derive(Debug)] pub struct DatabaseNode { - pub _create: Option, - pub _destroy: Option, - pub _use: Option, - pub _show: Option, + pub create: Option, + pub destroy: Option, + pub select: Option, + pub show: Option, _literal: String, _depth: u16, @@ -38,7 +36,7 @@ pub struct ShowNode { #[derive(Debug, PartialEq)] pub struct DestroyNode { - databases: Vec, + pub databases: Vec, _literal: String, _depth: u16, @@ -46,7 +44,7 @@ pub struct DestroyNode { #[derive(Debug, PartialEq)] pub struct CreateNode { - name: String, + pub name: String, _literal: String, _depth: u16, @@ -54,13 +52,16 @@ pub struct CreateNode { #[derive(Debug, PartialEq)] pub struct UseNode { - name: String, + pub name: String, _literal: String, _depth: u16, } impl DatabaseNode { + /// Takes current node type and given the current location in the + /// query defined by the borrowed index, makes an attempt to parse + /// this node and associated subnodes for the Abstract Syntax Tree. pub fn parse( tokens: &Vec, idx: &mut usize, @@ -70,10 +71,10 @@ impl DatabaseNode { validate_length(tokens, idx, true)?; let mut database_node: DatabaseNode = DatabaseNode { - _create: None, - _destroy: None, - _use: None, - _show: None, + create: None, + destroy: None, + select: None, + show: None, _literal: { tokens[*idx - 2..*idx] @@ -87,25 +88,25 @@ impl DatabaseNode { match action { ImpliedAction::Create => { - database_node._create = match CreateNode::parse(tokens, idx, depth + 1) { + database_node.create = match CreateNode::parse(tokens, idx, depth + 1) { Ok(state) => Some(state), Err(msg) => return Err(msg), } } ImpliedAction::Delete => { - database_node._destroy = match DestroyNode::parse(tokens, idx, depth + 1) { + database_node.destroy = match DestroyNode::parse(tokens, idx, depth + 1) { Ok(state) => Some(state), Err(msg) => return Err(msg), } } ImpliedAction::Show => { - database_node._show = match ShowNode::parse(tokens, idx, depth + 1) { + database_node.show = match ShowNode::parse(tokens, idx, depth + 1) { Ok(state) => Some(state), Err(msg) => return Err(msg), } } ImpliedAction::Use => { - database_node._use = match UseNode::parse(tokens, idx, depth + 1) { + database_node.select = match UseNode::parse(tokens, idx, depth + 1) { Ok(state) => Some(state), Err(msg) => return Err(msg), } @@ -133,7 +134,7 @@ impl DatabaseNode { /// the Transpiler REPL. pub fn transpile_color(&self) -> (String, String) { let pair: (String, (String, String)) = - match (&self._create, &self._destroy, &self._show, &self._use) { + match (&self.create, &self.destroy, &self.show, &self.select) { (Some(op), _, _, _) => ("CREATE DATABASE ".to_string(), op.transpile_color()), (_, Some(op), _, _) => ("DROP DATABASE ".to_string(), op.transpile_color()), (_, _, Some(op), _) => ("SHOW DATABASES".to_string(), op.transpile_color()), @@ -151,7 +152,7 @@ impl DatabaseNode { /// Outputs current AST node transpile to raw SQL. pub fn transpile_raw(&self) -> String { - match (&self._create, &self._destroy, &self._show, &self._use) { + match (&self.create, &self.destroy, &self.show, &self.select) { (Some(op), _, _, _) => "CREATE DATABASE ".to_string() + &op.transpile_raw(), (_, Some(op), _, _) => "DROP DATABASE ".to_string() + &op.transpile_raw(), (_, _, Some(op), _) => "SHOW DATABASES".to_string() + &op.transpile_raw(), @@ -162,6 +163,9 @@ impl DatabaseNode { } impl CreateNode { + /// Takes current node type and given the current location in the + /// query defined by the borrowed index, makes an attempt to parse + /// this node and associated subnodes for the Abstract Syntax Tree. pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { validate_length(tokens, idx, true)?; @@ -335,19 +339,19 @@ impl fmt::Display for DatabaseNode { f, "\n{}(DatabaseNode){}{}{}{}", get_tab(self._depth), - self._create + self.create .as_ref() .map(|v| v as &dyn fmt::Display) .unwrap_or(&""), - self._destroy + self.destroy .as_ref() .map(|v| v as &dyn fmt::Display) .unwrap_or(&""), - self._show + self.show .as_ref() .map(|v| v as &dyn fmt::Display) .unwrap_or(&""), - self._use + self.select .as_ref() .map(|v| v as &dyn fmt::Display) .unwrap_or(&""), diff --git a/src/language/parser/get.rs b/src/language/parser/get.rs index 3b51883..5d602dc 100644 --- a/src/language/parser/get.rs +++ b/src/language/parser/get.rs @@ -13,17 +13,17 @@ use std::{fmt, usize}; #[derive(Debug)] pub struct GetNode { - _table: TableNode, - _columns: ColumnNode, - _filter: Option, - _postprocessor: Option, + pub table: TableNode, + pub columns: ColumnNode, + pub filter: Option, + pub postprocessor: Option, _depth: u16, } #[derive(Debug, PartialEq)] pub struct TableNode { - table_name: String, + pub table_name: String, _literal: String, _depth: u16, @@ -31,8 +31,8 @@ pub struct TableNode { #[derive(Debug, PartialEq)] pub struct ColumnNode { - column_names: Vec, - is_wildcard: bool, + pub column_names: Vec, + pub is_wildcard: bool, _literal: String, _depth: u16, @@ -40,7 +40,7 @@ pub struct ColumnNode { #[derive(Debug, PartialEq)] pub struct FilterNode { - condition: ConditionNode, + pub condition: ConditionNode, _literal: String, _depth: u16, @@ -81,10 +81,10 @@ impl GetNode { } Ok(GetNode { - _table: table, - _columns: columns, - _filter: filter, - _postprocessor: postprocessor, + table, + columns, + filter, + postprocessor, _depth: depth, }) @@ -94,13 +94,13 @@ impl GetNode { /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. pub fn transpile_color(&self) -> (String, String) { - let columns: (String, String) = self._columns.transpile_color(); - let table: (String, String) = self._table.transpile_color(); - let filter: Option<(String, String)> = match &self._filter { + let columns: (String, String) = self.columns.transpile_color(); + let table: (String, String) = self.table.transpile_color(); + let filter: Option<(String, String)> = match &self.filter { Some(filter) => Some(filter.transpile_color()), None => None, }; - let postprocessor: Option<(String, String)> = match &self._postprocessor { + let postprocessor: Option<(String, String)> = match &self.postprocessor { Some(postprocessor) => Some(postprocessor.transpile_color()), None => None, }; @@ -131,13 +131,13 @@ impl GetNode { /// Outputs current AST node transpiled to raw SQL pub fn transpile_raw(&self) -> String { - let columns: String = self._columns.transpile_raw(); - let table: String = self._table.transpile_raw(); - let filter: Option = match &self._filter { + let columns: String = self.columns.transpile_raw(); + let table: String = self.table.transpile_raw(); + let filter: Option = match &self.filter { Some(filter) => Some(filter.transpile_raw()), None => None, }; - let postprocessor: Option = match &self._postprocessor { + let postprocessor: Option = match &self.postprocessor { Some(postprocessor) => Some(postprocessor.transpile_raw()), None => None, }; @@ -379,9 +379,9 @@ impl fmt::Display for GetNode { f, "\n{}(GetNode){}{}{}{}", get_tab(self._depth), - self._columns, - self._table, - self._filter + self.columns, + self.table, + self.filter .as_ref() .map(|v| v as &dyn fmt::Display) .unwrap_or(&format!( @@ -391,7 +391,7 @@ impl fmt::Display for GetNode { get_tab(self._depth + 1), get_tab(self._depth + 2) )), - self._postprocessor + self.postprocessor .as_ref() .map(|v| v as &dyn fmt::Display) .unwrap_or(&format!( diff --git a/src/language/parser/mod.rs b/src/language/parser/mod.rs index 0271cd0..b2afe5e 100644 --- a/src/language/parser/mod.rs +++ b/src/language/parser/mod.rs @@ -4,3 +4,4 @@ pub mod get; pub mod helpers; pub mod parser; pub mod postprocessor; +pub mod table; diff --git a/src/language/parser/parser.rs b/src/language/parser/parser.rs index 72a09c4..2e27b0c 100644 --- a/src/language/parser/parser.rs +++ b/src/language/parser/parser.rs @@ -1,9 +1,10 @@ use crate::{ language::{ - parser::database::DatabaseNode, parser::{ + database::DatabaseNode, get::GetNode, helpers::{get_tab, validate_length}, + table::TableNode, }, tokens::{Token, TokenType}, }, @@ -22,8 +23,10 @@ pub enum ImpliedAction { #[derive(Debug)] pub struct Query { - _get: Option, - _database: Option, + pub get: Option, + pub database: Option, + pub table: Option, + _depth: u16, } @@ -54,8 +57,9 @@ impl Query { let get_node: GetNode = GetNode::parse(&tokens, idx, depth + 1)?; return Ok(Query { - _get: Some(get_node), - _database: None, + get: Some(get_node), + database: None, + table: None, _depth: depth, }); } else if vec![ @@ -70,26 +74,47 @@ impl Query { *idx += 1; // Peek ahead to see if it's a database or table - if tokens[*idx].token_type == TokenType::Database { - *idx += 1; - - let database_node: DatabaseNode = DatabaseNode::parse( - &tokens, - idx, - depth + 1, - ImpliedAction::try_from(tokens[*idx - 2].token_type)?, - )?; - - return Ok(Query { - _get: None, - _database: Some(database_node), - _depth: depth, - }); - } else { - return Err(format!( - "Query recieved an action keyword, but received an invalid target keyword `{:?}`. Valid targets are: `Database`", - tokens[*idx].token_type - )); + match tokens[*idx].token_type { + TokenType::Database => { + *idx += 1; + + let database_node: DatabaseNode = DatabaseNode::parse( + &tokens, + idx, + depth + 1, + ImpliedAction::try_from(tokens[*idx - 2].token_type)?, + )?; + + return Ok(Query { + get: None, + database: Some(database_node), + table: None, + _depth: depth, + }); + } + TokenType::Table => { + *idx += 1; + + let table_node: TableNode = TableNode::parse( + &tokens, + idx, + depth + 1, + ImpliedAction::try_from(tokens[*idx - 2].token_type)?, + )?; + + return Ok(Query { + get: None, + database: None, + table: Some(table_node), + _depth: depth, + }); + } + _ => { + return Err(format!( + "Query recieved an action keyword, but received an invalid target keyword `{:?}`. Valid targets are: `Database`", + tokens[*idx].token_type + )); + } } } @@ -100,10 +125,12 @@ impl Query { /// and it's raw query counterpart. Output are used by /// the Transpiler REPL. pub fn transpile_color(&self) -> (String, String) { - if let Some(get) = &self._get { + if let Some(get) = &self.get { return get.transpile_color(); - } else if let Some(database) = &self._database { + } else if let Some(database) = &self.database { return database.transpile_color(); + } else if let Some(table) = &self.table { + return table.transpile_color(); } else { logger::error("A fatal error occurred while transpiling your query!"); }; @@ -111,10 +138,12 @@ impl Query { /// Ouputs current AST node tranpile to raw SQL. pub fn transpile_raw(&self) -> String { - if let Some(get) = &self._get { + if let Some(get) = &self.get { return get.transpile_raw(); - } else if let Some(database) = &self._database { + } else if let Some(database) = &self.database { return database.transpile_raw(); + } else if let Some(table) = &self.table { + return table.transpile_raw(); } else { logger::error("A fatal error occurred while transpiling your query!"); }; @@ -125,13 +154,17 @@ impl fmt::Display for Query { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "{}(Query){}{}", + "{}(Query){}{}{}", get_tab(self._depth), - self._get + self.get + .as_ref() + .map(|v| v as &dyn fmt::Display) + .unwrap_or(&""), + self.database .as_ref() .map(|v| v as &dyn fmt::Display) .unwrap_or(&""), - self._database + self.table .as_ref() .map(|v| v as &dyn fmt::Display) .unwrap_or(&"") diff --git a/src/language/parser/postprocessor.rs b/src/language/parser/postprocessor.rs index e385218..1a489e3 100644 --- a/src/language/parser/postprocessor.rs +++ b/src/language/parser/postprocessor.rs @@ -9,7 +9,7 @@ use std::{fmt, usize}; #[derive(PartialEq, Debug)] pub struct LimitNode { - limit: i32, + pub limit: i32, _literal: String, _depth: u16, diff --git a/src/language/parser/table.rs b/src/language/parser/table.rs new file mode 100644 index 0000000..ac79141 --- /dev/null +++ b/src/language/parser/table.rs @@ -0,0 +1,367 @@ +/* +This handles our table mutators and accessors +(modification, creation, and deletion of tables) +*/ + +use crate::{ + language::{ + parser::{ + conditional::ConditionNode, + helpers::{get_tab, validate_length}, + parser::ImpliedAction, + }, + tokens::{Token, TokenType}, + }, + utils::{ + colors::{AnsiColor, colorize}, + logger, + }, +}; +use std::{fmt, usize}; + +#[derive(Debug)] +pub struct TableNode { + pub show: Option, + pub destroy: Option, + + _literal: String, + _depth: u16, +} + +#[derive(Debug, PartialEq)] +pub struct ShowNode { + _depth: u16, +} + +#[derive(Debug, PartialEq)] +pub struct DestroyNode { + pub table: String, + pub condition: Option, + + _literal: String, + _depth: u16, +} + +impl TableNode { + pub fn parse( + tokens: &Vec, + idx: &mut usize, + depth: u16, + action: ImpliedAction, + ) -> Result { + validate_length(tokens, idx, true)?; + + let mut table_node: TableNode = TableNode { + destroy: None, + show: None, + + _literal: { + tokens[*idx - 2..*idx] + .iter() + .map(|v| v.lexeme.as_str()) + .collect::>() + .join(" ") + }, + _depth: depth, + }; + + match action { + ImpliedAction::Delete => { + table_node.destroy = match DestroyNode::parse(tokens, idx, depth + 1) { + Ok(state) => Some(state), + Err(msg) => return Err(msg), + } + } + ImpliedAction::Show => { + table_node.show = match ShowNode::parse(tokens, idx, depth + 1) { + Ok(state) => Some(state), + Err(msg) => return Err(msg), + } + } + _ => { + return Err(format!( + "Got unexpected action requested for table -> '{:?}'", + action + )); + } + }; + + if tokens[*idx].token_type != TokenType::EoqToken { + return Err(format!( + "Unexpected token '{}', expected end-of-query token by this point.", + tokens[*idx].lexeme + )); + } + + return Ok(table_node); + } + + /// Outputs current AST node transpiled with color + /// and it's raw query counterpart. Output are used by + /// the Transpiler REPL. + pub fn transpile_color(&self) -> (String, String) { + let pair: (String, (String, String)) = match (&self.destroy, &self.show) { + (Some(op), _) => ( + if op.condition.is_some() { + "DELETE FROM TABLE ".to_string() + } else { + "DROP TABLE ".to_string() + }, + op.transpile_color(), + ), + (_, Some(op)) => ("SHOW TABLES".to_string(), op.transpile_color()), + _ => logger::error("No table operation provided"), + }; + + ( + colorize(&self._literal, AnsiColor::Yellow) + + if *&pair.1.0.len() != 0 { &" " } else { "" } + + &pair.1.0, + format!("{}{}", colorize(&pair.0, AnsiColor::Yellow), &pair.1.1), + ) + } + + /// Outputs current AST node transpile to raw SQL. + pub fn transpile_raw(&self) -> String { + match (&self.destroy, &self.show) { + (Some(op), _) => { + (if op.condition.is_some() { + "DELETE FROM TABLE ".to_string() + } else { + "DROP TABLE ".to_string() + } + &op.transpile_raw()) + } + (_, Some(op)) => "SHOW TABLES".to_string() + &op.transpile_raw(), + _ => logger::error("No table operation provided"), + } + } +} + +impl DestroyNode { + /// Takes current node type and given the current location in the + /// query defined by the borrowed index, makes an attempt to parse + /// this node and associated subnodes for the Abstract Syntax Tree. + pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { + validate_length(tokens, idx, true)?; + + let start_idx: usize = *idx; + + if tokens[*idx].token_type != TokenType::Identifier { + return Err(format!( + "Table destruction expected an identifier for discerning the target table, received `{:?}` instead", + tokens[*idx].token_type + )); + } + + let table: String = tokens[*idx].literal.clone(); + let mut condition: Option = None; + *idx += 1; + + // We atleast should have an EOQ token + validate_length(tokens, idx, true)?; + + if tokens[*idx].token_type == TokenType::FilterKeyword { + *idx += 1; + condition = match ConditionNode::parse(tokens, idx, depth + 1) { + Ok(state) => Some(state), + Err(msg) => return Err(msg), + }; + } + + return Ok(DestroyNode { + table: table, + condition: condition, + + _literal: { + tokens[start_idx..*idx] + .iter() + .map(|v| v.lexeme.as_str()) + .collect::>() + .join(" ") + }, + _depth: depth, + }); + } + + /// Outputs current AST node transpiled with color + /// and it's raw query counterpart. Output are used by + /// the Transpiler REPL. + pub fn transpile_color(&self) -> (String, String) { + let table_name: String = colorize(&self.table, AnsiColor::Blue); + let cond_transpile_raw: String; + let cond_transpile_sql: String; + + if let Some(condition_node) = &self.condition { + cond_transpile_raw = condition_node.transpile_raw(); + cond_transpile_sql = condition_node.transpile_color(); + } else { + cond_transpile_raw = "".to_string(); + cond_transpile_sql = "".to_string(); + } + + let where_clause_sql: String = if cond_transpile_sql.is_empty() { + String::new() + } else { + format!(" WHERE {}", cond_transpile_sql) + }; + + let where_clause_raw: String = if cond_transpile_raw.is_empty() { + String::new() + } else { + format!(" WHERE {}", cond_transpile_raw) + }; + + ( + table_name.clone() + &colorize(&format!("{}", &where_clause_raw), AnsiColor::Cyan), + table_name.clone() + &colorize(&format!("{}", &where_clause_sql), AnsiColor::Cyan), + ) + } + + /// Outputs current AST node transpile to raw SQL. + pub fn transpile_raw(&self) -> String { + let cond_transpile_sql: String = if let Some(condition_node) = &self.condition { + condition_node.transpile_raw() + } else { + "".to_string() + }; + + let where_clause_sql: String = if cond_transpile_sql.is_empty() { + String::new() + } else { + format!(" WHERE {}", cond_transpile_sql) + }; + + self.table.clone() + &format!("{}", &where_clause_sql) + } +} + +impl ShowNode { + pub fn parse(tokens: &Vec, idx: &mut usize, depth: u16) -> Result { + validate_length(tokens, idx, true)?; + + return Ok(ShowNode { _depth: depth }); + } + + /// Outputs current AST node transpiled with color + /// and it's raw query counterpart. Output are used by + /// the Transpiler REPL. + pub fn transpile_color(&self) -> (String, String) { + ("".to_string(), "".to_string()) + } + + /// Outputs current AST node transpiled to raw SQL. + pub fn transpile_raw(&self) -> String { + "".to_string() + } +} + +// Display Functions +impl fmt::Display for TableNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "\n{}(TableNode){}{}", + get_tab(self._depth), + self.destroy + .as_ref() + .map(|v| v as &dyn fmt::Display) + .unwrap_or(&""), + self.show + .as_ref() + .map(|v| v as &dyn fmt::Display) + .unwrap_or(&""), + ) + } +} + +impl fmt::Display for DestroyNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "\n{}(DestroyNode) +{}table: {:?}{}", + get_tab(self._depth), + get_tab(self._depth + 1), + self.table, + self.condition + .as_ref() + .map(|v| v as &dyn fmt::Display) + .unwrap_or(&""), + ) + } +} + +impl fmt::Display for ShowNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\n{}(ShowNode)", get_tab(self._depth),) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn unit_test_show_normal() { + let input: Vec = vec![Token::new( + TokenType::EoqToken, + &"".to_string(), + &"!".to_string(), + )]; + + let mut idx: usize = 0; + let depth: u16 = 0; + let expected: ShowNode = ShowNode { _depth: depth }; + + match ShowNode::parse(&input, &mut idx, depth) { + Ok(val) => assert_eq!(val, expected), + Err(err) => assert!(false, "Output errored out -> {}", err), + } + } + + #[test] + fn unit_test_destroy_normal() { + let input: Vec = vec![ + Token::new( + TokenType::Identifier, + &"test_table".to_string(), + &"test_table".to_string(), + ), + Token::new(TokenType::EoqToken, &"".to_string(), &"!".to_string()), + ]; + + let mut idx: usize = 0; + let depth: u16 = 0; + let expected: DestroyNode = DestroyNode { + table: "test_table".to_string(), + condition: None, + _literal: "test_table".to_string(), + _depth: depth, + }; + + match DestroyNode::parse(&input, &mut idx, depth) { + Ok(val) => assert_eq!(val, expected), + Err(err) => assert!(false, "Output errored out -> {}", err), + } + } + + #[test] + fn unit_test_destroy_error() { + let input: Vec = vec![ + Token::new(TokenType::Get, &"get".to_string(), &"get".to_string()), + Token::new(TokenType::EoqToken, &"".to_string(), &"!".to_string()), + ]; + + let mut idx: usize = 0; + let depth: u16 = 0; + + match DestroyNode::parse(&input, &mut idx, depth) { + Ok(val) => assert!( + false, + "Output was expected to error but returned -> {}", + val + ), + Err(_) => assert!(true, "Output expected to error and did."), + } + } +} diff --git a/src/language/tokens.rs b/src/language/tokens.rs index 0c03647..28d9d3d 100644 --- a/src/language/tokens.rs +++ b/src/language/tokens.rs @@ -30,6 +30,7 @@ pub enum TokenType { FilterKeyword, PostProcessorEntrance, Database, + Table, Get, From, And, @@ -129,6 +130,9 @@ lazy_static! { ("database", TokenType::Database), ("databases", TokenType::Database), + ("table", TokenType::Table), + ("tables", TokenType::Table), + ("find", TokenType::Get), ("retrieve", TokenType::Get), ("get", TokenType::Get), diff --git a/src/utils/logger.rs b/src/utils/logger.rs index 9e7868e..617fbd9 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -4,7 +4,7 @@ use chrono::Local; // TODO: Move these to config? pub const TEST_MODE: bool = true; -pub const LOG_LEVEL: i32 = 3; +pub const LOG_LEVEL: i32 = 0; pub const DEBUG: (i32, &str, AnsiColor) = (1, "Debug", AnsiColor::BrightBlack); pub const ERROR: (i32, &str, AnsiColor) = (4, "Error", AnsiColor::BrightRed); diff --git a/tests/transpiler_tests.rs b/tests/transpiler_tests.rs index 6c693c7..3815b6f 100644 --- a/tests/transpiler_tests.rs +++ b/tests/transpiler_tests.rs @@ -1,6 +1,6 @@ use eaql::transpiler::engine; -// Database Query Tests (Validator) +// Database Query Tests (transpile) // Normal #[test] fn transpile_integration_test_db_create_normal() { @@ -95,7 +95,7 @@ fn transpile_integration_test_db_destroy_error() { assert!(engine("Destroy the databases db1, \"db2\" and db3.").is_err()); } -// Table Accessor Query Tests (Validator) +// Table Accessor Query Tests (transpile) // Normal #[test] fn transpile_integration_test_table_accessor_normal_get() { @@ -259,3 +259,50 @@ fn transpile_integration_test_table_accessor_error_postprocessor_limit() { assert!(engine("get all from test_table then limit = 5;").is_err()); assert!(engine("get all from test_table then limit id;").is_err()); } + +// Table Query Tests (transpile) +// Normal +#[test] +fn transpile_integration_test_table_show_normal() { + // Show keyword tests + assert_eq!(engine("show table;"), Ok("SHOW TABLES;".to_string())); + assert_eq!(engine("list tables."), Ok("SHOW TABLES;".to_string())); +} + +#[test] +fn transpile_integration_test_table_destroy_normal() { + // Destroy + assert_eq!(engine("remove table t1!"), Ok("DROP TABLE t1;".to_string())); + assert_eq!( + engine("destroy table t1!"), + Ok("DROP TABLE t1;".to_string()) + ); + assert_eq!(engine("delete table t1!"), Ok("DROP TABLE t1;".to_string())); + + // Tables with conditions tests + assert_eq!( + engine("delete in the table t1 where value = 3;"), + Ok("DELETE FROM TABLE t1 WHERE value = 3;".to_string()) + ); + assert_eq!( + engine("Destroy in the table t1 where the value is 5 and (price < 3 or time >= 7)."), + Ok("DELETE FROM TABLE t1 WHERE value = 5 and (price < 3 or time >= 7);".to_string()) + ); +} + +// Error +#[test] +fn transpile_integration_test_table_show_error() { + // Generic Error Test + assert!(engine("show table").is_err()); + assert!(engine("show!").is_err()); + assert!(engine("List the \"tables\".").is_err()); +} + +#[test] +fn transpile_integration_test_table_destroy_error() { + // Generic Error Test + assert!(engine("delete table t1").is_err()); + assert!(engine("Delete t1!").is_err()); + assert!(engine("Destroy the tables t1, \"t2\" and t3.").is_err()); +} diff --git a/tests/validator_tests.rs b/tests/validator_tests.rs index f2b2f78..08d72d4 100644 --- a/tests/validator_tests.rs +++ b/tests/validator_tests.rs @@ -60,12 +60,45 @@ fn validator_integration_test_db_show_error() { assert_eq!(engine("List the \"databases\"."), false); } +// Table Query Tests (Validator) +// Normal +#[test] +fn validator_integration_test_table_show_normal() { + // Show keyword tests + assert_eq!(engine("show table;"), true); + assert_eq!(engine("list tables."), true); +} + +#[test] +fn validator_integration_test_table_destroy_normal() { + // Destroy + assert_eq!(engine("remove table t1!"), true); + assert_eq!(engine("destroy table t1!"), true); + assert_eq!(engine("delete table t1!"), true); + + // Tables with conditions tests + assert_eq!(engine("delete table t1 where value = 3;"), true); + assert_eq!( + engine("Destroy the table t1 where the value is 5 and (price < 3 or time >= 7)."), + true + ); +} + +// Error +#[test] +fn validator_integration_test_table_show_error() { + // Generic Error Test + assert_eq!(engine("show table"), false); + assert_eq!(engine("show!"), false); + assert_eq!(engine("List the \"tables\"."), false); +} + #[test] -fn validator_integration_test_db_destroy_error() { +fn validator_integration_test_table_destroy_error() { // Generic Error Test - assert_eq!(engine("delete databases db1, db2, db3"), false); - assert_eq!(engine("Delete db1!"), false); - assert_eq!(engine("Destroy the databases db1, \"db2\" and db3."), false); + assert_eq!(engine("delete table t1"), false); + assert_eq!(engine("Delete t1!"), false); + assert_eq!(engine("Destroy the tables t1, \"t2\" and t3."), false); } // Table Accessor Query Tests (Validator)