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
40 changes: 7 additions & 33 deletions src/db/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::db::table::select;
use crate::db::table::insert;
use crate::db::table::delete;
use crate::db::table::update;
use crate::db::table::create_table;
use std::collections::HashMap;

pub struct Database {
Expand Down Expand Up @@ -43,38 +44,29 @@ impl Database {
}

fn create_table(&mut self, statement: CreateTableStatement) -> Result<(), String> {
if self.has_table(&statement.table_name) {
return Err(format!("Table {} already exists", statement.table_name));
}
let table = Table::new(statement.table_name, statement.columns) ;
self.tables.insert(table.name.clone(), table);
Ok(())
create_table::create_table(self, statement)
}

fn insert_into_table(&mut self, statement: InsertIntoStatement) -> Result<(), String> {
let table = self.get_table_mut(&statement.table_name)?;
insert::insert(table, statement)?;
Ok(())
insert::insert(table, statement)
}

fn select_statement_stack(&mut self, statement: SelectStatementStack) -> Result<Vec<Vec<Value>>, String> {
let rows = select::select_statement_stack(self, statement)?;
Ok(rows)
select::select_statement_stack(self, statement)
}

fn delete_from_table(&mut self, statement: DeleteStatement) -> Result<(), String> {
let table = self.get_table_mut(&statement.table_name)?;
delete::delete(table, statement)?;
Ok(())
delete::delete(table, statement)
}

fn update_table(&mut self, statement: UpdateStatement) -> Result<(), String> {
let table = self.get_table_mut(&statement.table_name)?;
update::update(table, statement)?;
Ok(())
update::update(table, statement)
}

fn has_table(&self, table_name: &str) -> bool {
pub fn has_table(&self, table_name: &str) -> bool {
self.tables.contains_key(table_name)
}

Expand All @@ -96,7 +88,6 @@ impl Database {
#[cfg(test)]
mod tests {
use super::*;
use crate::interpreter::ast::CreateTableStatement;
use crate::db::table::{ColumnDefinition, DataType};


Expand All @@ -119,23 +110,6 @@ mod tests {
}
}

#[test]
fn create_table_generates_proper_table() {
let statement = CreateTableStatement {
table_name: "users".to_string(),
columns: vec![
ColumnDefinition {
name: "id".to_string(),
data_type: DataType::Integer,
constraints: vec![]
},
],
};
let mut database = Database::new();
assert!(database.create_table(statement).is_ok());
assert!(database.has_table("users"));
}

#[test]
fn has_table_returns_proper_response() {
let database = default_database();
Expand Down
71 changes: 71 additions & 0 deletions src/db/table/create_table/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use crate::db::database::Database;
use crate::interpreter::ast::{CreateTableStatement, ExistenceCheck};
use crate::db::table::Table;


pub fn create_table(database: &mut Database, statement: CreateTableStatement) -> Result<(), String> {
if database.has_table(&statement.table_name) {
match statement.existence_check {
Some(ExistenceCheck::IfExists) => {
return Ok(());
}
_ => {
return Err(format!("Table {} already exists", statement.table_name));
}
}
}
let table = Table::new(statement.table_name, statement.columns) ;
database.tables.insert(table.name.clone(), table);
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use crate::interpreter::ast::CreateTableStatement;
use crate::db::table::{ColumnDefinition, DataType};
use crate::db::table::test_utils::default_database;

#[test]
fn create_table_generates_proper_table() {
let statement = CreateTableStatement {
table_name: "users".to_string(),
existence_check: None,
columns: vec![
ColumnDefinition {
name: "id".to_string(),
data_type: DataType::Integer,
constraints: vec![]
},
],
};
let mut database = Database::new();
assert!(create_table(&mut database, statement).is_ok());
assert!(database.has_table("users"));
}

#[test]
fn create_table_errors_when_table_already_exists() {
let statement = CreateTableStatement {
table_name: "users".to_string(),
existence_check: None,
columns: vec![ColumnDefinition { name: "id".to_string(), data_type: DataType::Integer, constraints: vec![] }],
};
let mut database = default_database();
let result = create_table(&mut database, statement);
assert!(result.is_err());
assert_eq!("Table users already exists", result.err().unwrap());
}

#[test]
fn create_table_with_if_exists_clause_does_not_error_when_table_already_exists() {
let statement = CreateTableStatement {
table_name: "users".to_string(),
existence_check: Some(ExistenceCheck::IfExists),
columns: vec![ColumnDefinition { name: "id".to_string(), data_type: DataType::Integer, constraints: vec![] }],
};
let mut database = default_database();
let result = create_table(&mut database, statement);
assert!(result.is_ok());
}
}
1 change: 1 addition & 0 deletions src/db/table/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod select;
pub mod insert;
pub mod delete;
pub mod update;
pub mod create_table;
pub mod helpers;
#[cfg(test)]
pub mod test_utils;
Expand Down
50 changes: 35 additions & 15 deletions src/interpreter/ast/create_statement.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::interpreter::{
ast::{
parser::Parser, CreateTableStatement, SqlStatement::{self, CreateTable},
parser::Parser, CreateTableStatement, SqlStatement::{self, CreateTable}, ExistenceCheck,
helpers::common::{expect_token_type, get_table_name}
},
tokenizer::token::TokenTypes
Expand All @@ -16,10 +16,9 @@ pub fn build(parser: &mut Parser) -> Result<SqlStatement, String> {
TokenTypes::Table => {
statement = table_statement(parser);
},
TokenTypes::Index => {
statement = index_statement(parser);
_ => {
return Err(parser.format_error())
},
_ => return Err(parser.format_error()),
}

// Ensure SemiColon
Expand All @@ -28,13 +27,25 @@ pub fn build(parser: &mut Parser) -> Result<SqlStatement, String> {
}

fn table_statement(parser: &mut Parser) -> Result<SqlStatement, String> {
// Get the table name
let table_name = get_table_name(parser)?;
parser.advance()?;
let existence_check = match parser.current_token()?.token_type {
TokenTypes::If => {
parser.advance()?;
expect_token_type(parser, TokenTypes::Not)?;
parser.advance()?;
expect_token_type(parser, TokenTypes::Exists)?;
parser.advance()?;
Some(ExistenceCheck::IfExists)
},
_ => None,
};

let table_name = get_table_name(parser)?;

let column_definitions = column_definitions(parser)?;
return Ok(CreateTable(CreateTableStatement {
table_name,
existence_check,
columns: column_definitions,
}));
}
Expand Down Expand Up @@ -95,10 +106,6 @@ fn token_to_data_type(parser: &mut Parser) -> Result<DataType, String> {
};
}

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


#[cfg(test)]
mod tests {
Expand Down Expand Up @@ -126,6 +133,7 @@ mod tests {
let result = build(&mut parser);
let expected = SqlStatement::CreateTable(CreateTableStatement {
table_name: "users".to_string(),
existence_check: None,
columns: vec![
ColumnDefinition {
name: "id".to_string(),
Expand Down Expand Up @@ -211,17 +219,29 @@ mod tests {
}

#[test]
fn index_statement_not_implemented() {
// CREATE INDEX my_index;
fn create_table_with_if_exists_clause() {
// CREATE TABLE IF NOT EXISTS users (id INTEGER);
let tokens = vec![
token(TokenTypes::Create, "CREATE"),
token(TokenTypes::Index, "INDEX"),
token(TokenTypes::Identifier, "my_index"),
token(TokenTypes::Table, "TABLE"),
token(TokenTypes::If, "IF"),
token(TokenTypes::Not, "NOT"),
token(TokenTypes::Exists, "EXISTS"),
token(TokenTypes::Identifier, "users"),
token(TokenTypes::LeftParen, "("),
token(TokenTypes::Identifier, "id"),
token(TokenTypes::Integer, "INTEGER"),
token(TokenTypes::RightParen, ")"),
token(TokenTypes::SemiColon, ";"),
token(TokenTypes::EOF, ""),
];
let mut parser = Parser::new(tokens);
let result = build(&mut parser);
assert!(result.is_err());
let expected = SqlStatement::CreateTable(CreateTableStatement {
table_name: "users".to_string(),
existence_check: Some(ExistenceCheck::IfExists),
columns: vec![ColumnDefinition { name: "id".to_string(), data_type: DataType::Integer, constraints: vec![] }],
});
assert_eq!(result.unwrap(), expected);
}
}
2 changes: 1 addition & 1 deletion src/interpreter/ast/delete_statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use crate::interpreter::{
pub fn build(parser: &mut Parser) -> Result<SqlStatement, String> {
parser.advance()?;
expect_token_type(parser, TokenTypes::From)?;
let table_name = get_table_name(parser)?;
parser.advance()?;
let table_name = get_table_name(parser)?;
let where_clause = get_where_clause(parser)?;
let order_by_clause = get_order_by(parser)?;
let limit_clause = get_limit(parser)?;
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/ast/helpers/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ pub fn tokens_to_identifier_list(parser: &mut Parser) -> Result<Vec<String>, Str
}

pub fn get_table_name(parser: &mut Parser) -> Result<String, String> {
parser.advance()?;
let token = parser.current_token()?;
expect_token_type(parser, TokenTypes::Identifier)?;
let result = token.value.to_string();
parser.advance()?;
Ok(result)
}

Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/ast/helpers/select_statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ pub fn get_statement(parser: &mut Parser) -> Result<SelectStatement, String> {
parser.advance()?;
let columns = get_columns(parser)?;
expect_token_type(parser, TokenTypes::From)?;
let table_name = get_table_name(parser)?;
parser.advance()?;
let table_name = get_table_name(parser)?;
let where_clause: Option<Vec<WhereStackElement>> = get_where_clause(parser)?;
let order_by_clause = get_order_by(parser)?;
let limit_clause = get_limit(parser)?;
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/ast/insert_statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ pub fn build(parser: &mut Parser) -> Result<SqlStatement, String> {
}

fn into_statement(parser: &mut Parser) -> Result<SqlStatement, String> {
let table_name = get_table_name(parser)?;
parser.advance()?;
let table_name = get_table_name(parser)?;

let token = parser.current_token()?;
let columns = match token.token_type {
Expand Down
7 changes: 7 additions & 0 deletions src/interpreter/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,16 @@ pub enum SqlStatement {
#[derive(Debug, PartialEq)]
pub struct CreateTableStatement {
pub table_name: String,
pub existence_check: Option<ExistenceCheck>,
pub columns: Vec<ColumnDefinition>,
}

#[derive(Debug, PartialEq)]
pub enum ExistenceCheck { // Eventually expand to temp tables
IfNotExists,
IfExists,
}

#[derive(Debug, PartialEq)]
pub struct InsertIntoStatement {
pub table_name: String,
Expand Down
2 changes: 2 additions & 0 deletions src/interpreter/ast/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ mod tests {
parser.advance_past_semicolon()?;
return Ok(SqlStatement::CreateTable(CreateTableStatement {
table_name: "users".to_string(),
existence_check: None,
columns: vec![],
}));
}
Expand Down Expand Up @@ -187,6 +188,7 @@ mod tests {
let result = parser.next_statement(builder);
let expected = Some(Ok(SqlStatement::CreateTable(CreateTableStatement {
table_name: "users".to_string(),
existence_check: None,
columns: vec![],
})));
assert_eq!(result, expected);
Expand Down
3 changes: 1 addition & 2 deletions src/interpreter/ast/update_statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ use crate::interpreter::ast::helpers::where_stack::get_where_clause;
use crate::interpreter::tokenizer::token::TokenTypes;

pub fn build(parser: &mut Parser) -> Result<SqlStatement, String> {

parser.advance()?;
let table_name = get_table_name(parser)?;
// Ensure Set
parser.advance()?;
expect_token_type(parser, TokenTypes::Set)?;
let update_values = get_update_values(parser)?;
let where_clause = get_where_clause(parser)?;
Expand Down
3 changes: 2 additions & 1 deletion src/interpreter/tokenizer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ mod tests {
ORDER BY GROUP HAVING DISTINCT ALL AS ASC DESC
INNER LEFT RIGHT FULL OUTER JOIN ON UNION
LIMIT OFFSET
AND OR IN EXISTS
AND OR IN EXISTS IF
CASE WHEN THEN ELSE END
= != < <= > >=
COUNT SUM AVG MIN MAX
Expand Down Expand Up @@ -141,6 +141,7 @@ mod tests {
token(TokenTypes::Or, "OR", 12, 9),
token(TokenTypes::In, "IN", 15, 9),
token(TokenTypes::Exists, "EXISTS", 18, 9),
token(TokenTypes::If, "IF", 25, 9),
token(TokenTypes::Case, "CASE", 8, 10),
token(TokenTypes::When, "WHEN", 13, 10),
token(TokenTypes::Then, "THEN", 18, 10),
Expand Down
1 change: 1 addition & 0 deletions src/interpreter/tokenizer/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ impl<'a> Scanner<'a> {
slice if slice.eq_ignore_ascii_case("OR") => TokenTypes::Or,
slice if slice.eq_ignore_ascii_case("IN") => TokenTypes::In,
slice if slice.eq_ignore_ascii_case("EXISTS") => TokenTypes::Exists,
slice if slice.eq_ignore_ascii_case("IF") => TokenTypes::If,
slice if slice.eq_ignore_ascii_case("CASE") => TokenTypes::Case,
slice if slice.eq_ignore_ascii_case("WHEN") => TokenTypes::When,
slice if slice.eq_ignore_ascii_case("THEN") => TokenTypes::Then,
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/tokenizer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub enum TokenTypes {
Inner, Left, Right, Full, Outer, Join, On,
Limit, Offset, Union, Intersect, Except,
// Logical Operators
And, Or, In, Exists,
And, Or, In, Exists, If,
Case, When, Then, Else, End, Is,
Equals, NotEquals, LessThan, LessEquals, GreaterThan, GreaterEquals,
// Aggregate Functions
Expand Down
Loading