diff --git a/src/db/table/mod.rs b/src/db/table/mod.rs index 14758df..173bdb4 100644 --- a/src/db/table/mod.rs +++ b/src/db/table/mod.rs @@ -1,3 +1,7 @@ +use std::cmp::Ordering; + +use crate::cli::ast::OrderByDirection; + pub mod select; pub mod insert; pub mod common; @@ -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)] @@ -77,4 +109,13 @@ impl Table { fn width(&self) -> usize { self.columns.len() } + + pub fn get_index_of_column(&self, column: &String) -> Result { + 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)); + } } \ No newline at end of file diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index 75f824e..e95ab92 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -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; @@ -8,7 +9,11 @@ use crate::db::table::common::validate_and_clone_row; pub fn select(table: &Table, statement: SelectStatement) -> Result>, 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)?; } @@ -56,10 +61,7 @@ pub fn get_columns_from_row(table: &Table, row: &Vec, 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 { @@ -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()); + } } \ No newline at end of file diff --git a/src/db/table/select/order_by_clause.rs b/src/db/table/select/order_by_clause.rs new file mode 100644 index 0000000..cf1572e --- /dev/null +++ b/src/db/table/select/order_by_clause.rs @@ -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>, order_by_clauses: &Vec) -> Result>, String> { + rows.sort_by(|a, b| { + perform_comparions(table, a, b, order_by_clauses) + }); + return Ok(rows); +} + +fn perform_comparions(table: &Table, row1: &Vec, row2: &Vec, order_by_clauses: &Vec) -> 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![ + 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()); + } +} \ No newline at end of file