Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 270 additions & 3 deletions src/cli/ast/create_statement.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,272 @@
use crate::cli::{ast::SqlStatement, ast::interpreter::Interpreter};
use crate::cli::{ast::{interpreter::Interpreter, CreateTableStatement, SqlStatement::{self, CreateTable}}, table::{ColumnDefinition, DataType}, tokenizer::token::TokenTypes};

pub fn build(interpreter: &Interpreter) -> Result<SqlStatement, String> {
todo!()
pub fn build(interpreter: &mut Interpreter) -> Result<SqlStatement, String> {
interpreter.advance();
let statement: Result<SqlStatement, String>;
match interpreter.current_token() {
Some(token) => {
match token.token_type {
TokenTypes::Table => {
statement = table_statement(interpreter);
},
TokenTypes::Index => {
statement = index_statement(interpreter);
},
_ => return Err(interpreter.format_error()),
}
},
None => return Err(interpreter.format_error()),
}
// Ensure SemiColon
interpreter.advance();
match interpreter.current_token() {
Some(token) => {
if token.token_type != TokenTypes::SemiColon {
return Err(interpreter.format_error());
}
},
None => return Err(interpreter.format_error()),
}

return statement;
}

fn table_statement(interpreter: &mut Interpreter) -> Result<SqlStatement, String> {
interpreter.advance();
let table_name = match interpreter.current_token() {
Some(token) => {
if token.token_type != TokenTypes::Identifier {
return Err(interpreter.format_error());
}
let name = token.value.to_string();
interpreter.advance();
name
},
None => return Err(interpreter.format_error()),
};
let column_definitions = column_definitions(interpreter)?;
return Ok(CreateTable(CreateTableStatement {
table_name,
columns: column_definitions,
}));
}

fn column_definitions(interpreter: &mut Interpreter) -> Result<Vec<ColumnDefinition>, String> {
let mut columns: Vec<crate::cli::table::ColumnDefinition> = vec![];
if let Some(token) = interpreter.current_token() {
if token.token_type != TokenTypes::LeftParen {
return Err(interpreter.format_error());
}
else {
interpreter.advance();
loop {
let column_name = match interpreter.current_token() {
Some(token) => {
if token.token_type != TokenTypes::Identifier {
return Err(interpreter.format_error());
}
token.value.to_string()
},
None => return Err(interpreter.format_error()),
};
interpreter.advance();

// Grab the column data type
let column_data_type = token_to_data_type(interpreter)?;
interpreter.advance();

// TODO: Modifiers and Constraints

// Ensure we have a comma or right paren
if let Some(token) = interpreter.current_token() {
match &token.token_type {
TokenTypes::Comma => {
columns.push(ColumnDefinition {
name: column_name,
data_type: column_data_type,
constraints: vec![] // TODO,
});
}
TokenTypes::RightParen => {
columns.push(ColumnDefinition {
name: column_name,
data_type: column_data_type,
constraints: vec![] // TODO,
});
break;
},
_ => return Err(interpreter.format_error()),
}
} else {
return Err(interpreter.format_error());
}
interpreter.advance();
}
return Ok(columns);
}
} else {
return Err(interpreter.format_error());
}
}

fn token_to_data_type(interpreter: &mut Interpreter) -> Result<DataType, String> {
if let Some(token) = interpreter.current_token() {
match token.token_type {
TokenTypes::Integer => {
return Ok(DataType::Integer);
},
TokenTypes::Real => {
return Ok(DataType::Real);
},
TokenTypes::Text => {
return Ok(DataType::Text);
},
TokenTypes::Blob => {
return Ok(DataType::Blob);
},
TokenTypes::Null => {
return Ok(DataType::Null);
},
_ => {
return Err(interpreter.format_error());
}
}
} else {
return Err(interpreter.format_error());
}
}

fn index_statement(_interpreter: &mut Interpreter) -> Result<SqlStatement, String> {
return Err("Index statements not yet implemented".to_string());
}


#[cfg(test)]
mod tests {
use super::*;
use crate::cli::tokenizer::scanner::Token;

fn token(tt: TokenTypes, val: &'static str, col_num: usize) -> Token<'static> {
Token {
token_type: tt,
value: val,
col_num: col_num,
line_num: 1,
}
}

#[test]
fn create_table_generates_proper_statement(){
let tokens = vec![
token(TokenTypes::Create, "CREATE", 0),
token(TokenTypes::Table, "TABLE", 7),
token(TokenTypes::Identifier, "users", 13),
token(TokenTypes::LeftParen, "(", 18),
token(TokenTypes::Identifier, "id", 19),
token(TokenTypes::Integer, "INTEGER", 22),
token(TokenTypes::Comma, ",", 29),
token(TokenTypes::Identifier, "name", 31),
token(TokenTypes::Text, "TEXT", 36),
token(TokenTypes::RightParen, ")", 40),
token(TokenTypes::SemiColon, ";", 41),
token(TokenTypes::EOF, "", 0),
];
let mut interpreter = Interpreter::new(tokens);
let result = build(&mut interpreter);
let expected = SqlStatement::CreateTable(CreateTableStatement {
table_name: "users".to_string(),
columns: vec![
ColumnDefinition {
name: "id".to_string(),
data_type: DataType::Integer,
constraints: vec![],
},
ColumnDefinition {
name: "name".to_string(),
data_type: DataType::Text,
constraints: vec![],
},
],
});
assert_eq!(result.unwrap(), expected);
}

#[test]
fn create_table_statement_missing_semicolon() {
let tokens = vec![
token(TokenTypes::Create, "CREATE", 0),
token(TokenTypes::Table, "TABLE", 7),
token(TokenTypes::Identifier, "users", 13),
token(TokenTypes::LeftParen, "(", 18),
token(TokenTypes::Identifier, "num", 19),
token(TokenTypes::Integer, "REAL", 22),
token(TokenTypes::Comma, ",", 29),
token(TokenTypes::Identifier, "my_blob", 31),
token(TokenTypes::Blob, "BLOB", 36),
token(TokenTypes::Comma, ",", 29),
token(TokenTypes::Identifier, "my_null", 31),
token(TokenTypes::Null, "Null", 36),
token(TokenTypes::RightParen, ")", 40),
// Missing SemiColon
token(TokenTypes::EOF, "", 0),
];
let mut interpreter = Interpreter::new(tokens);
let result = build(&mut interpreter);
assert!(result.is_err());
}

#[test]
fn create_table_with_bad_data_type() {
let tokens = vec![
token(TokenTypes::Create, "CREATE", 0),
token(TokenTypes::Table, "TABLE", 7),
token(TokenTypes::Identifier, "users", 13),
token(TokenTypes::LeftParen, "(", 18),
token(TokenTypes::Identifier, "id", 19),
token(TokenTypes::Asterisk, "*", 22), // Bad Data Type
token(TokenTypes::Comma, ",", 23),
token(TokenTypes::Identifier, "name", 25),
token(TokenTypes::Text, "TEXT", 30),
token(TokenTypes::RightParen, ")", 34),
token(TokenTypes::SemiColon, ";", 35),
token(TokenTypes::EOF, "", 0),
];
let mut interpreter = Interpreter::new(tokens);
let result = build(&mut interpreter);
assert!(result.is_err());
}

#[test]
fn create_table_missing_comma() {
let tokens = vec![
token(TokenTypes::Create, "CREATE", 0),
token(TokenTypes::Table, "TABLE", 7),
token(TokenTypes::Identifier, "users", 13),
token(TokenTypes::LeftParen, "(", 18),
token(TokenTypes::Identifier, "id", 19),
token(TokenTypes::Integer, "INTEGER", 22), // Missing Comma
token(TokenTypes::Identifier, "name", 31),
token(TokenTypes::Text, "TEXT", 36),
token(TokenTypes::RightParen, ")", 40),
token(TokenTypes::SemiColon, ";", 41),
token(TokenTypes::EOF, "", 0),
];
let mut interpreter = Interpreter::new(tokens);
let result = build(&mut interpreter);
assert!(result.is_err());
}

#[test]
fn index_statement_not_implemented() {
let tokens = vec![
token(TokenTypes::Create, "CREATE", 0),
token(TokenTypes::Index, "INDEX", 7),
token(TokenTypes::Identifier, "my_index", 13),
token(TokenTypes::SemiColon, ";", 22),
token(TokenTypes::EOF, "", 0),
];
let mut interpreter = Interpreter::new(tokens);
let result = build(&mut interpreter);
assert!(result.is_err());
}
}
8 changes: 4 additions & 4 deletions src/cli/ast/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl<'a> Interpreter<'a> {
self.current += 1;
}

fn format_error(&self) -> String {
pub fn format_error(&self) -> String {
if let Some(token) = self.current_token() {
return format!(
"Error at line {:?}, column {:?}: Unexpected type: {:?}",
Expand All @@ -40,9 +40,9 @@ impl<'a> Interpreter<'a> {
return Some(Err("No tokens to parse".to_string()));
}
return match self.current_token()?.token_type {
TokenTypes::Create => Some(create_statement::build(&self)),
TokenTypes::Insert => Some(insert_statement::build(&self)),
TokenTypes::Select => Some(select_statement::build(&self)),
TokenTypes::Create => Some(create_statement::build(self)),
TokenTypes::Insert => Some(insert_statement::build(self)),
TokenTypes::Select => Some(select_statement::build(self)),
_ => {
self.advance();
Some(Err(self.format_error()))
Expand Down
9 changes: 7 additions & 2 deletions src/cli/ast/mod.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
use crate::cli::{self, tokenizer::scanner::Token};
use crate::cli::{self, tokenizer::scanner::Token, table::Value};

mod create_statement;
mod insert_statement;
mod interpreter;
mod select_statement;

#[derive(Debug, PartialEq)]
pub enum SqlStatement {
CreateTable(CreateTableStatement),
Insert(InsertStatement),
Select(SelectStatement),
}

#[derive(Debug, PartialEq)]
pub struct CreateTableStatement {
pub table_name: String,
pub columns: Vec<cli::table::ColumnDefinition>,
}

#[derive(Debug, PartialEq)]
pub struct InsertStatement {
pub table_name: String,
pub columns: Vec<String>,
pub values: Vec<cli::table::Value>,
pub values: Vec<Value>,
}

#[derive(Debug, PartialEq)]
pub struct SelectStatement {
pub table_name: String,
pub columns: Vec<String>,
pub where_clause: Option<WhereClause>,
}

#[derive(Debug, PartialEq)]
pub struct WhereClause {
pub column: String,
pub value: cli::table::Value,
Expand Down
24 changes: 18 additions & 6 deletions src/cli/table.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@

#[derive(Debug, PartialEq)]
pub enum DataType {
Integer,
Float,
Real,
Text,
Boolean,
Blob,
Null,
}

pub struct Table {
Expand All @@ -12,19 +15,28 @@ pub struct Table {
length: usize,
}

#[derive(Debug, PartialEq)]
pub struct ColumnDefinition {
name: String,
data_type: DataType,
pub name: String,
pub data_type: DataType,
pub constraints: Vec<ColumnConstraint>,
}

#[derive(Debug, PartialEq)]
pub struct ColumnConstraint {
pub constraint_type: String,
}

struct Row {
primary_key: usize,
values: Vec<Value>,
}

#[derive(Debug, PartialEq)]
pub enum Value {
Integer(i64),
Float(f64),
Real(f64),
Text(String),
Bool(bool),
Blob(Vec<u8>),
Null
}
Loading