From b3ec18c9129a8b1bad7bb4d23014c23c938129da Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Sun, 31 Aug 2025 18:36:19 -0400 Subject: [PATCH 1/3] Start of the offset clause --- src/db/table/select/limit_clause.rs | 29 +++++++++++++++++++++++++++++ src/db/table/select/mod.rs | 16 ++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/db/table/select/limit_clause.rs diff --git a/src/db/table/select/limit_clause.rs b/src/db/table/select/limit_clause.rs new file mode 100644 index 0000000..b07c0bc --- /dev/null +++ b/src/db/table/select/limit_clause.rs @@ -0,0 +1,29 @@ +use crate::cli::ast::LimitClause; +use crate::db::table::Value; + + +pub fn get_limited_rows(rows: Vec>, limit_clause: &LimitClause) -> Result>, String> { + let mut index = 0; + if let Some(offset) = &limit_clause.offset && let Value::Integer(offset) = offset { + index = *offset as usize; + } + if index > rows.len() { + return Ok(rows); + } + + let limit = match limit_clause.limit { + Value::Integer(limit) => { + if limit < 0 { + rows.len() + } else { + limit as usize + } + }, + _ => { + return Err("Limit must be an integer".to_string()); + } + }; + + // let limited_rows: Vec> = rows.into_iter().skip(index as usize).take(limit as usize).collect(); + return Ok(limited_rows); +} diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index 7a88682..e8355bf 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -1,4 +1,5 @@ pub mod where_clause; +pub mod limit_clause; use crate::db::table::{Table, Value}; use crate::cli::ast::SelectStatement; use crate::cli::ast::SelectStatementColumns; @@ -6,8 +7,18 @@ 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(limit_clause) = &statement.limit_clause { + rows = limit_clause::get_limited_rows(rows, limit_clause)?; + } + + return Ok(rows); +} + +pub fn get_initial_rows(table: &Table, statement: &SelectStatement) -> Result>, String> { let mut rows: Vec> = vec![]; - if let Some(where_clause) = statement.where_clause { + if let Some(where_clause) = &statement.where_clause { for row in table.rows.iter() { if where_clause::matches_where_clause(table, &row, &where_clause) { rows.push(get_columns_from_row(table, &row, &statement.columns)?); @@ -18,9 +29,10 @@ pub fn select(table: &Table, statement: SelectStatement) -> Result, selected_columns: &SelectStatementColumns) -> Result, String> { let mut row_values: Vec = vec![]; if *selected_columns == SelectStatementColumns::All { From 0742d50ae2c5780d31255c44f96ecb767f52163a Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Sun, 31 Aug 2025 19:15:55 -0400 Subject: [PATCH 2/3] Add feature for limits and offsets with testing covering cases --- src/db/table/mod.rs | 13 +--- src/db/table/select/limit_clause.rs | 110 ++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 19 deletions(-) diff --git a/src/db/table/mod.rs b/src/db/table/mod.rs index 8f372d8..653d9e4 100644 --- a/src/db/table/mod.rs +++ b/src/db/table/mod.rs @@ -23,7 +23,7 @@ pub struct ColumnConstraint { pub constraint_type: String, } -#[derive(Debug, PartialEq, PartialOrd)] +#[derive(Debug, PartialEq, PartialOrd, Clone)] pub enum Value { Integer(i64), Real(f64), @@ -32,6 +32,7 @@ pub enum Value { Null } + impl Value { pub fn get_type(&self) -> DataType { match self { @@ -42,16 +43,6 @@ impl Value { Value::Null => DataType::Null, } } - - pub fn clone(&self) -> Value { - match self { - Value::Integer(value) => Value::Integer(*value), - Value::Real(value) => Value::Real(*value), - Value::Text(value) => Value::Text(value.clone()), - Value::Blob(value) => Value::Blob(value.clone()), - Value::Null => Value::Null, - } - } } #[derive(Debug)] diff --git a/src/db/table/select/limit_clause.rs b/src/db/table/select/limit_clause.rs index b07c0bc..f318ec4 100644 --- a/src/db/table/select/limit_clause.rs +++ b/src/db/table/select/limit_clause.rs @@ -1,14 +1,16 @@ +use std::cmp::min; + use crate::cli::ast::LimitClause; use crate::db::table::Value; -pub fn get_limited_rows(rows: Vec>, limit_clause: &LimitClause) -> Result>, String> { +pub fn get_limited_rows<'a>(rows: Vec>, limit_clause: &LimitClause) -> Result>, String> { let mut index = 0; if let Some(offset) = &limit_clause.offset && let Value::Integer(offset) = offset { index = *offset as usize; } - if index > rows.len() { - return Ok(rows); + if index >= rows.len() { + return Ok(vec![]); } let limit = match limit_clause.limit { @@ -16,14 +18,106 @@ pub fn get_limited_rows(rows: Vec>, limit_clause: &LimitClause) -> Re if limit < 0 { rows.len() } else { - limit as usize + min((limit as usize)+index, rows.len()) } }, - _ => { - return Err("Limit must be an integer".to_string()); - } + _ => return Err("Limit must be an integer".to_string()), // The parser should have already validated this }; - // let limited_rows: Vec> = rows.into_iter().skip(index as usize).take(limit as usize).collect(); + let mut limited_rows: Vec> = vec![]; + for i in index..limit { + limited_rows.push(rows[i].clone()); + } return Ok(limited_rows); } + + +#[cfg(test)] +mod tests { + use super::*; + + fn default_rows() -> Vec> { + vec![ + vec![Value::Integer(1)], + vec![Value::Integer(2)], + vec![Value::Integer(3)], + vec![Value::Integer(4)], + vec![Value::Integer(5)], + vec![Value::Integer(6)], + vec![Value::Integer(7)], + vec![Value::Integer(8)], + vec![Value::Integer(9)], + vec![Value::Integer(10)], + ] + } + + fn generate_limit_clause(limit: i64, offset: Option) -> LimitClause { + LimitClause { + limit: Value::Integer(limit as i64), + offset: offset.map(|offset| Value::Integer(offset as i64)), + } + } + + #[test] + fn no_offset_and_limit_is_equal_to_rows_length() { + let limit_clause = generate_limit_clause(10, None); + let result = get_limited_rows(default_rows(), &limit_clause); + assert!(result.is_ok()); + assert_eq!(default_rows(), result.unwrap()); + } + + #[test] + fn no_offset_and_limit_is_greater_than_rows_length() { + let limit_clause = generate_limit_clause(15, None); + let result = get_limited_rows(default_rows(), &limit_clause); + assert!(result.is_ok()); + assert_eq!(default_rows(), result.unwrap()); + } + + #[test] + fn no_offset_and_limit_is_less_than_rows_length() { + let limit_clause = generate_limit_clause(5, None); + let result = get_limited_rows(default_rows(), &limit_clause); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(1)], + vec![Value::Integer(2)], + vec![Value::Integer(3)], + vec![Value::Integer(4)], + vec![Value::Integer(5)], + ]; + assert_eq!(expected, result.unwrap()); + } + + #[test] + fn no_offset_and_negative_limit_returns_all_rows() { + let limit_clause = generate_limit_clause(-1, None); + let result = get_limited_rows(default_rows(), &limit_clause); + assert!(result.is_ok()); + assert_eq!(default_rows(), result.unwrap()); + } + + #[test] + fn offset_and_limit_is_generated_correctly() { + let limit_clause = generate_limit_clause(5, Some(1)); + let result = get_limited_rows(default_rows(), &limit_clause); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(2)], + vec![Value::Integer(3)], + vec![Value::Integer(4)], + vec![Value::Integer(5)], + vec![Value::Integer(6)], + ]; + assert_eq!(expected, result.unwrap()); + } + + #[test] + fn offset_is_greater_than_rows_length_returns_empty_rows() { + let limit_clause = generate_limit_clause(5, Some(10)); + let result = get_limited_rows(default_rows(), &limit_clause); + assert!(result.is_ok()); + let expected: Vec> = vec![]; + assert_eq!(expected, result.unwrap()); + } +} \ No newline at end of file From 5e5461abb33bc51b18710b8392165f9c76632574 Mon Sep 17 00:00:00 2001 From: Fletcher555 Date: Sun, 31 Aug 2025 19:17:13 -0400 Subject: [PATCH 3/3] Add integration-ish test with the rest of the select tests --- src/db/table/select/mod.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/db/table/select/mod.rs b/src/db/table/select/mod.rs index e8355bf..a4d74aa 100644 --- a/src/db/table/select/mod.rs +++ b/src/db/table/select/mod.rs @@ -55,6 +55,7 @@ mod tests { use crate::cli::ast::SelectStatementColumns; use crate::cli::ast::Operator; use crate::cli::ast::WhereClause; + use crate::cli::ast::LimitClause; fn default_table() -> Table { Table { @@ -159,4 +160,25 @@ mod tests { ]; assert_eq!(expected, result.unwrap()); } + + #[test] + fn select_with_limit_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: None, + limit_clause: Some(LimitClause { + limit: Value::Integer(1), + offset: Some(Value::Integer(1)), + }), + }; + let result = select(&table, statement); + assert!(result.is_ok()); + let expected = vec![ + vec![Value::Integer(2), Value::Text("Jane".to_string()), Value::Integer(30), Value::Real(2000.0)], + ]; + assert_eq!(expected, result.unwrap()); + } } \ No newline at end of file