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
41 changes: 41 additions & 0 deletions src/db/table/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use std::cmp::Ordering;

use crate::cli::ast::OrderByDirection;

pub mod select;
pub mod insert;
pub mod common;
Expand Down Expand Up @@ -43,6 +47,34 @@ impl Value {
Value::Null => DataType::Null,
}
}

pub fn compare(&self, other: &Value, direction: &OrderByDirection) -> Ordering {
let result = match (self, other) {
(Value::Null, Value::Null) => Ordering::Equal,
(Value::Null, _) => Ordering::Less,
(_, Value::Null) => Ordering::Greater,
(Value::Integer(a), Value::Integer(b)) => a.cmp(b),
(Value::Real(a), Value::Real(b)) => {
if a > b {
Ordering::Greater
} else if a < b {
Ordering::Less
} else {
Ordering::Equal
}

},
(Value::Text(a), Value::Text(b)) => a.cmp(b),
(Value::Blob(a), Value::Blob(b)) => a.cmp(b),
_ => return Ordering::Equal, // Bad - returns equal if data types are different
};

if direction == &OrderByDirection::Asc {
return result;
} else {
return result.reverse();
}
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -77,4 +109,13 @@ impl Table {
fn width(&self) -> usize {
self.columns.len()
}

pub fn get_index_of_column(&self, column: &String) -> Result<usize, String> {
for (i, c) in self.columns.iter().enumerate() {
if c.name == *column {
return Ok(i);
}
}
return Err(format!("Column {} does not exist in table {}", column, self.name));
}
}
33 changes: 28 additions & 5 deletions src/db/table/select/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod where_clause;
pub mod limit_clause;
pub mod order_by_clause;
use crate::db::table::{Table, Value};
use crate::cli::ast::SelectStatement;
use crate::cli::ast::SelectStatementColumns;
Expand All @@ -8,7 +9,11 @@ use crate::db::table::common::validate_and_clone_row;

pub fn select(table: &Table, statement: SelectStatement) -> Result<Vec<Vec<Value>>, String> {
let mut rows = get_initial_rows(table, &statement)?;
// Implement order by

if let Some(order_by_clause) = statement.order_by_clause {
rows = order_by_clause::get_ordered_rows(table, rows, &order_by_clause)?;
}

if let Some(limit_clause) = &statement.limit_clause {
rows = limit_clause::get_limited_rows(rows, limit_clause)?;
}
Expand Down Expand Up @@ -56,10 +61,7 @@ pub fn get_columns_from_row(table: &Table, row: &Vec<Value>, selected_columns: &
mod tests {
use super::*;
use crate::db::table::{Table, Value, DataType, ColumnDefinition};
use crate::cli::ast::SelectStatementColumns;
use crate::cli::ast::Operator;
use crate::cli::ast::WhereClause;
use crate::cli::ast::LimitClause;
use crate::cli::ast::{SelectStatementColumns, WhereClause, LimitClause, OrderByClause, OrderByDirection, Operator};

fn default_table() -> Table {
Table {
Expand Down Expand Up @@ -204,4 +206,25 @@ mod tests {
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Column column_not_included does not exist in table users");
}

#[test]
fn select_with_order_by_clause_is_generated_correctly() {
let table = default_table();
let statement = SelectStatement {
table_name: "users".to_string(),
columns: SelectStatementColumns::All,
where_clause: None,
order_by_clause: Some(vec![OrderByClause {column: "money".to_string(), direction: OrderByDirection::Desc}]),
limit_clause: None,
};
let result = select(&table, statement);
assert!(result.is_ok());
let expected = vec![
vec![Value::Integer(4), Value::Null, Value::Integer(40), Value::Real(4000.0)],
vec![Value::Integer(3), Value::Text("Jim".to_string()), Value::Integer(35), Value::Real(3000.0)],
vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)],
vec![Value::Integer(1), Value::Text("John".to_string()), Value::Integer(25), Value::Real(1000.0)],
];
assert_eq!(expected, result.unwrap());
}
}
141 changes: 141 additions & 0 deletions src/db/table/select/order_by_clause.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use std::cmp::Ordering;

use crate::cli::ast::OrderByClause;
use crate::db::table::Table;
use crate::db::table::Value;



// This sorting algorithm will always return a stable sort, this is given by all of the order columns
// then the input order of the rows is maintained with any required tie breaking.
pub fn get_ordered_rows(table: &Table, mut rows: Vec<Vec<Value>>, order_by_clauses: &Vec<OrderByClause>) -> Result<Vec<Vec<Value>>, String> {
rows.sort_by(|a, b| {
perform_comparions(table, a, b, order_by_clauses)
});
return Ok(rows);
}

fn perform_comparions(table: &Table, row1: &Vec<Value>, row2: &Vec<Value>, order_by_clauses: &Vec<OrderByClause>) -> Ordering {
let mut result = Ordering::Equal;
for comparison in order_by_clauses {
let index = table.get_index_of_column(&comparison.column);
let index = match index {
Ok(index) => index,
Err(_) => return Ordering::Equal, // Bad but should never happen because we've validated the columns in the parser
};
let ordering = row1[index].compare(&row2[index], &comparison.direction);
if ordering != Ordering::Equal {
result = ordering;
break;
}
}
return result;
}

#[cfg(test)]
mod tests {
use super::*;
use crate::db::table::{Table, Value, DataType, ColumnDefinition};
use crate::cli::ast::OrderByDirection;

fn default_table() -> Table {
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![]},
ColumnDefinition {name: "money".to_string(), data_type: DataType::Real, constraints: vec![]},
ColumnDefinition {name: "some_data".to_string(), data_type: DataType::Blob, constraints: vec![]},
],
rows: vec![],
}
}

fn default_rows() -> Vec<Vec<Value>> {
vec![
vec![Value::Integer(3), Value::Text("c_Jim".to_string()), Value::Real(3000.0), Value::Blob(b"0022".to_vec())],
vec![Value::Integer(1), Value::Text("a_John".to_string()), Value::Real(1000.0), Value::Blob(b"0000".to_vec())],
vec![Value::Null, Value::Null, Value::Null, Value::Null],
vec![Value::Integer(2), Value::Text("b_Jane".to_string()), Value::Real(2000.0), Value::Blob(b"0201".to_vec())],
vec![Value::Integer(3), Value::Text("b_Jim".to_string()), Value::Real(1500.0), Value::Blob(b"0102".to_vec())],
vec![Value::Integer(4), Value::Text("a_Jim".to_string()), Value::Real(500.0), Value::Blob(b"0101".to_vec())],
vec![Value::Integer(1), Value::Text("a_Jim".to_string()), Value::Real(5000.0), Value::Blob(b"0401".to_vec())],
]
}

#[test]
fn get_ordered_rows_returns_rows_with_id_column_returns_rows_in_correct_order() {
let table = default_table();
let rows = default_rows();
let order_by_clauses = vec![OrderByClause {column: "id".to_string(), direction: OrderByDirection::Asc}];
let result = get_ordered_rows(&table, rows, &order_by_clauses);
assert!(result.is_ok());
let expected = vec![
vec![Value::Null, Value::Null, Value::Null, Value::Null],
vec![Value::Integer(1), Value::Text("a_John".to_string()), Value::Real(1000.0), Value::Blob(b"0000".to_vec())],
vec![Value::Integer(1), Value::Text("a_Jim".to_string()), Value::Real(5000.0), Value::Blob(b"0401".to_vec())],
vec![Value::Integer(2), Value::Text("b_Jane".to_string()), Value::Real(2000.0), Value::Blob(b"0201".to_vec())],
vec![Value::Integer(3), Value::Text("c_Jim".to_string()), Value::Real(3000.0), Value::Blob(b"0022".to_vec())],
vec![Value::Integer(3), Value::Text("b_Jim".to_string()), Value::Real(1500.0), Value::Blob(b"0102".to_vec())],
vec![Value::Integer(4), Value::Text("a_Jim".to_string()), Value::Real(500.0), Value::Blob(b"0101".to_vec())],
];
assert_eq!(expected, result.unwrap());
}

#[test]
fn get_ordered_rows_returns_rows_with_name_column_returns_rows_in_correct_order() {
let table = default_table();
let rows = default_rows();
let order_by_clauses = vec![OrderByClause {column: "name".to_string(), direction: OrderByDirection::Asc}];
let result = get_ordered_rows(&table, rows, &order_by_clauses);
assert!(result.is_ok());
let expected = vec![
vec![Value::Null, Value::Null, Value::Null, Value::Null],
vec![Value::Integer(4), Value::Text("a_Jim".to_string()), Value::Real(500.0), Value::Blob(b"0101".to_vec())],
vec![Value::Integer(1), Value::Text("a_Jim".to_string()), Value::Real(5000.0), Value::Blob(b"0401".to_vec())],
vec![Value::Integer(1), Value::Text("a_John".to_string()), Value::Real(1000.0), Value::Blob(b"0000".to_vec())],
vec![Value::Integer(2), Value::Text("b_Jane".to_string()), Value::Real(2000.0), Value::Blob(b"0201".to_vec())],
vec![Value::Integer(3), Value::Text("b_Jim".to_string()), Value::Real(1500.0), Value::Blob(b"0102".to_vec())],
vec![Value::Integer(3), Value::Text("c_Jim".to_string()), Value::Real(3000.0), Value::Blob(b"0022".to_vec())],
];
assert_eq!(expected, result.unwrap());
}

#[test]
fn get_ordered_rows_ordered_descending_returns_rows_in_correct_order() {
let table = default_table();
let rows = default_rows();
let order_by_clauses = vec![OrderByClause {column: "money".to_string(), direction: OrderByDirection::Desc}];
let result = get_ordered_rows(&table, rows, &order_by_clauses);
assert!(result.is_ok());
let expected = vec![
vec![Value::Integer(1), Value::Text("a_Jim".to_string()), Value::Real(5000.0), Value::Blob(b"0401".to_vec())],
vec![Value::Integer(3), Value::Text("c_Jim".to_string()), Value::Real(3000.0), Value::Blob(b"0022".to_vec())],
vec![Value::Integer(2), Value::Text("b_Jane".to_string()), Value::Real(2000.0), Value::Blob(b"0201".to_vec())],
vec![Value::Integer(3), Value::Text("b_Jim".to_string()), Value::Real(1500.0), Value::Blob(b"0102".to_vec())],
vec![Value::Integer(1), Value::Text("a_John".to_string()), Value::Real(1000.0), Value::Blob(b"0000".to_vec())],
vec![Value::Integer(4), Value::Text("a_Jim".to_string()), Value::Real(500.0), Value::Blob(b"0101".to_vec())],
vec![Value::Null, Value::Null, Value::Null, Value::Null],
];
assert_eq!(expected, result.unwrap());
}

#[test]
fn get_ordered_rows_multiple_sort_orders_returns_rows_in_correct_order() {
let table = default_table();
let rows = default_rows();
let order_by_clauses = vec![OrderByClause {column: "name".to_string(), direction: OrderByDirection::Desc}, OrderByClause {column: "some_data".to_string(), direction: OrderByDirection::Asc}];
let result = get_ordered_rows(&table, rows, &order_by_clauses);
assert!(result.is_ok());
let expected = vec![
vec![Value::Integer(3), Value::Text("c_Jim".to_string()), Value::Real(3000.0), Value::Blob(b"0022".to_vec())],
vec![Value::Integer(3), Value::Text("b_Jim".to_string()), Value::Real(1500.0), Value::Blob(b"0102".to_vec())],
vec![Value::Integer(2), Value::Text("b_Jane".to_string()), Value::Real(2000.0), Value::Blob(b"0201".to_vec())],
vec![Value::Integer(1), Value::Text("a_John".to_string()), Value::Real(1000.0), Value::Blob(b"0000".to_vec())],
vec![Value::Integer(4), Value::Text("a_Jim".to_string()), Value::Real(500.0), Value::Blob(b"0101".to_vec())],
vec![Value::Integer(1), Value::Text("a_Jim".to_string()), Value::Real(5000.0), Value::Blob(b"0401".to_vec())],
vec![Value::Null, Value::Null, Value::Null, Value::Null],
];
assert_eq!(expected, result.unwrap());
}
}
Loading