diff --git a/COMPAT.md b/COMPAT.md index 12c34115c..1cde4971d 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -44,7 +44,7 @@ This document describes the SQLite compatibility status of Limbo: | SELECT ... WHERE | Partial | | | SELECT ... WHERE ... LIKE | Yes | | | SELECT ... LIMIT | Yes | | -| SELECT ... ORDER BY | No | | +| SELECT ... ORDER BY | Partial | | | SELECT ... GROUP BY | No | | | SELECT ... JOIN | Partial | | | SELECT ... CROSS JOIN | Partial | | diff --git a/Cargo.lock b/Cargo.lock index 9be5c8555..f3737cf57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,26 +357,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom", - "once_cell", - "tiny-keccak", -] - [[package]] name = "cpp_demangle" version = "0.4.3" @@ -505,15 +485,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "dlv-list" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" -dependencies = [ - "const-random", -] - [[package]] name = "either" version = "1.13.0" @@ -1005,7 +976,6 @@ dependencies = [ "log", "mimalloc", "nix 0.29.0", - "ordered-multimap", "polling", "pprof", "regex", @@ -1171,16 +1141,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "ordered-multimap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" -dependencies = [ - "dlv-list", - "hashbrown 0.14.5", -] - [[package]] name = "os_str_bytes" version = "6.6.1" @@ -1809,15 +1769,6 @@ dependencies = [ "syn 2.0.69", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinytemplate" version = "1.2.1" diff --git a/core/Cargo.toml b/core/Cargo.toml index 4a4dbe5b5..b94931e30 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -34,7 +34,6 @@ fallible-iterator = "0.3.0" libc = "0.2.155" log = "0.4.20" nix = { version = "0.29.0", features = ["fs"] } -ordered-multimap = "0.7.1" sieve-cache = "0.1.4" sqlite3-parser = "0.11.0" thiserror = "1.0.61" diff --git a/core/btree.rs b/core/btree.rs index 37e9a8c21..0ac1e5331 100644 --- a/core/btree.rs +++ b/core/btree.rs @@ -152,8 +152,8 @@ impl Cursor for BTreeCursor { Ok(()) } - fn rowid(&self) -> Result>> { - Ok(self.rowid.borrow()) + fn rowid(&self) -> Result> { + Ok(*self.rowid.borrow()) } fn record(&self) -> Result>> { diff --git a/core/expr.rs b/core/expr.rs index fbdfff86d..0b37aa1de 100644 --- a/core/expr.rs +++ b/core/expr.rs @@ -3,7 +3,7 @@ use sqlite3_parser::ast::{self, Expr, UnaryOperator}; use crate::{ function::{Func, SingleRowFunc}, - schema::{Column, Schema, Table}, + schema::{Schema, Table, Type}, select::{ColumnInfo, Select, SrcTable}, util::normalize_ident, vdbe::{BranchOffset, Insn, ProgramBuilder}, @@ -78,6 +78,7 @@ pub fn build_select<'a>(schema: &Schema, select: &'a ast::Select) -> Result(schema: &Schema, select: &'a ast::Select) -> Result, ) -> Result { match expr { ast::Expr::Between { .. } => todo!(), ast::Expr::Binary(e1, op, e2) => { let e1_reg = program.alloc_register(); let e2_reg = program.alloc_register(); - let _ = translate_expr(program, select, e1, e1_reg)?; - let _ = translate_expr(program, select, e2, e2_reg)?; + let _ = translate_expr(program, select, e1, e1_reg, cursor_hint)?; + let _ = translate_expr(program, select, e2, e2_reg, cursor_hint)?; match op { ast::Operator::NotEquals => { @@ -250,7 +253,13 @@ pub fn translate_expr( // whenever a not null check succeeds, we jump to the end of the series let label_coalesce_end = program.allocate_label(); for (index, arg) in args.iter().enumerate() { - let reg = translate_expr(program, select, arg, target_register)?; + let reg = translate_expr( + program, + select, + arg, + target_register, + cursor_hint, + )?; if index < args.len() - 1 { program.emit_insn_with_label_dependency( Insn::NotNull { @@ -282,7 +291,7 @@ pub fn translate_expr( }; for arg in args { let reg = program.alloc_register(); - let _ = translate_expr(program, select, &arg, reg)?; + let _ = translate_expr(program, select, &arg, reg, cursor_hint)?; match arg { ast::Expr::Literal(_) => program.mark_last_insn_constant(), _ => {} @@ -315,7 +324,7 @@ pub fn translate_expr( }; let regs = program.alloc_register(); - translate_expr(program, select, &args[0], regs)?; + translate_expr(program, select, &args[0], regs, cursor_hint)?; program.emit_insn(Insn::Function { start_reg: regs, dest: target_register, @@ -356,7 +365,7 @@ pub fn translate_expr( for arg in args.iter() { let reg = program.alloc_register(); - translate_expr(program, select, arg, reg)?; + translate_expr(program, select, arg, reg, cursor_hint)?; if let ast::Expr::Literal(_) = arg { program.mark_last_insn_constant(); } @@ -378,8 +387,9 @@ pub fn translate_expr( ast::Expr::FunctionCallStar { .. } => todo!(), ast::Expr::Id(ident) => { // let (idx, col) = table.unwrap().get_column(&ident.0).unwrap(); - let (idx, col, cursor_id) = resolve_ident_table(program, &ident.0, select)?; - if col.primary_key { + let (idx, col_type, cursor_id, is_primary_key) = + resolve_ident_table(program, &ident.0, select, cursor_hint)?; + if is_primary_key { program.emit_insn(Insn::RowId { cursor_id, dest: target_register, @@ -391,7 +401,7 @@ pub fn translate_expr( cursor_id, }); } - maybe_apply_affinity(col, target_register, program); + maybe_apply_affinity(col_type, target_register, program); Ok(target_register) } ast::Expr::InList { .. } => todo!(), @@ -439,8 +449,9 @@ pub fn translate_expr( ast::Expr::NotNull(_) => todo!(), ast::Expr::Parenthesized(_) => todo!(), ast::Expr::Qualified(tbl, ident) => { - let (idx, col, cursor_id) = resolve_ident_qualified(program, &tbl.0, &ident.0, select)?; - if col.primary_key { + let (idx, col_type, cursor_id, is_primary_key) = + resolve_ident_qualified(program, &tbl.0, &ident.0, select, cursor_hint)?; + if is_primary_key { program.emit_insn(Insn::RowId { cursor_id, dest: target_register, @@ -452,7 +463,7 @@ pub fn translate_expr( cursor_id, }); } - maybe_apply_affinity(col, target_register, program); + maybe_apply_affinity(col_type, target_register, program); Ok(target_register) } ast::Expr::Raise(_, _) => todo!(), @@ -563,7 +574,8 @@ pub fn resolve_ident_qualified<'a>( table_name: &String, ident: &String, select: &'a Select, -) -> Result<(usize, &'a Column, usize)> { + cursor_hint: Option, +) -> Result<(usize, Type, usize, bool)> { for join in &select.src_tables { match join.table { Table::BTree(ref table) => { @@ -579,8 +591,8 @@ pub fn resolve_ident_qualified<'a>( .find(|(_, col)| col.name == *ident); if res.is_some() { let (idx, col) = res.unwrap(); - let cursor_id = program.resolve_cursor_id(&table_identifier); - return Ok((idx, col, cursor_id)); + let cursor_id = program.resolve_cursor_id(&table_identifier, cursor_hint); + return Ok((idx, col.ty, cursor_id, col.primary_key)); } } } @@ -598,7 +610,8 @@ pub fn resolve_ident_table<'a>( program: &ProgramBuilder, ident: &String, select: &'a Select, -) -> Result<(usize, &'a Column, usize)> { + cursor_hint: Option, +) -> Result<(usize, Type, usize, bool)> { let mut found = Vec::new(); for join in &select.src_tables { match join.table { @@ -611,11 +624,29 @@ pub fn resolve_ident_table<'a>( .columns .iter() .enumerate() - .find(|(_, col)| col.name == *ident); + .find(|(_, col)| col.name == *ident) + .map(|(idx, col)| (idx, col.ty, col.primary_key)); + let mut idx; + let mut col_type; + let mut is_primary_key; if res.is_some() { - let (idx, col) = res.unwrap(); - let cursor_id = program.resolve_cursor_id(&table_identifier); - found.push((idx, col, cursor_id)); + (idx, col_type, is_primary_key) = res.unwrap(); + // overwrite if cursor hint is provided + if let Some(cursor_hint) = cursor_hint { + let cols = &program.cursor_ref[cursor_hint].1; + if let Some(res) = cols.as_ref().and_then(|res| { + res.columns() + .iter() + .enumerate() + .find(|x| x.1.name == *ident) + }) { + idx = res.0; + col_type = res.1.ty; + is_primary_key = res.1.primary_key; + } + } + let cursor_id = program.resolve_cursor_id(&table_identifier, cursor_hint); + found.push((idx, col_type, cursor_id, is_primary_key)); } } Table::Pseudo(_) => todo!(), @@ -631,8 +662,8 @@ pub fn resolve_ident_table<'a>( anyhow::bail!("Parse error: ambiguous column name {}", ident.as_str()); } -pub fn maybe_apply_affinity(col: &Column, target_register: usize, program: &mut ProgramBuilder) { - if col.ty == crate::schema::Type::Real { +pub fn maybe_apply_affinity(col_type: Type, target_register: usize, program: &mut ProgramBuilder) { + if col_type == crate::schema::Type::Real { program.emit_insn(Insn::RealAffinity { register: target_register, }) diff --git a/core/function.rs b/core/function.rs index 224e96500..0ec15ce28 100644 --- a/core/function.rs +++ b/core/function.rs @@ -56,6 +56,7 @@ impl ToString for SingleRowFunc { } } +#[derive(Debug)] pub enum Func { Agg(AggFunc), SingleRow(SingleRowFunc), diff --git a/core/lib.rs b/core/lib.rs index 4049ab8f0..ff4caac92 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -4,6 +4,7 @@ mod expr; mod function; mod io; mod pager; +mod pseudo; mod schema; mod select; mod sorter; diff --git a/core/pseudo.rs b/core/pseudo.rs new file mode 100644 index 000000000..dd8668d2e --- /dev/null +++ b/core/pseudo.rs @@ -0,0 +1,65 @@ +use anyhow::Result; +use std::cell::{Ref, RefCell}; + +use crate::types::{Cursor, CursorResult, OwnedRecord, OwnedValue}; + +pub struct PseudoCursor { + current: RefCell>, +} + +impl PseudoCursor { + pub fn new() -> Self { + Self { + current: RefCell::new(None), + } + } +} + +impl Cursor for PseudoCursor { + fn is_empty(&self) -> bool { + self.current.borrow().is_none() + } + + fn rewind(&mut self) -> Result> { + *self.current.borrow_mut() = None; + Ok(CursorResult::Ok(())) + } + + fn next(&mut self) -> Result> { + *self.current.borrow_mut() = None; + Ok(CursorResult::Ok(())) + } + + fn wait_for_completion(&mut self) -> Result<()> { + Ok(()) + } + + fn rowid(&self) -> Result> { + let x = self + .current + .borrow() + .as_ref() + .map(|record| match record.values[0] { + OwnedValue::Integer(rowid) => rowid as u64, + _ => panic!("Expected integer value"), + }); + Ok(x) + } + + fn record(&self) -> Result>> { + Ok(self.current.borrow()) + } + + fn insert(&mut self, record: &OwnedRecord) -> Result<()> { + *self.current.borrow_mut() = Some(record.clone()); + Ok(()) + } + + fn get_null_flag(&self) -> bool { + false + } + + fn set_null_flag(&mut self, _null_flag: bool) { + // Do nothing + } +} diff --git a/core/schema.rs b/core/schema.rs index 7f41dffc0..3c932a454 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -54,7 +54,7 @@ impl Table { pub fn get_name(&self) -> &str { match self { Table::BTree(table) => &table.name, - Table::Pseudo(table) => &table.columns[0].name, + Table::Pseudo(_) => "", } } @@ -297,7 +297,7 @@ pub fn _build_pseudo_table(columns: &[ResultColumn]) -> PseudoTable { table } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Column { pub name: String, pub ty: Type, diff --git a/core/select.rs b/core/select.rs index ba23b1b4a..f54e581f8 100644 --- a/core/select.rs +++ b/core/select.rs @@ -8,6 +8,7 @@ pub struct SrcTable<'a> { pub join_info: Option<&'a ast::JoinedSelectTable>, // FIXME: preferably this should be a reference with lifetime == Select ast expr } +#[derive(Debug)] pub struct ColumnInfo<'a> { pub func: Option, pub args: &'a Option>, @@ -39,6 +40,7 @@ pub struct Select<'a> { pub column_info: Vec>, pub src_tables: Vec>, // Tables we use to get data from. This includes "from" and "joins" pub limit: &'a Option, + pub order_by: &'a Option>, pub exist_aggregation: bool, pub where_clause: &'a Option, /// Ordered list of opened read table loops diff --git a/core/sorter.rs b/core/sorter.rs index 3372734ef..2e0bf74d1 100644 --- a/core/sorter.rs +++ b/core/sorter.rs @@ -1,24 +1,31 @@ -use crate::types::{Cursor, CursorResult, OwnedRecord, OwnedValue}; +use crate::types::{Cursor, CursorResult, OwnedRecord}; use anyhow::Result; -use log::trace; -use ordered_multimap::ListOrderedMultimap; -use std::cell::{Ref, RefCell}; +use std::{ + cell::{Ref, RefCell}, + collections::{BTreeMap, VecDeque}, +}; pub struct Sorter { - records: ListOrderedMultimap, + records: BTreeMap>, current: RefCell>, + order: Vec, } impl Sorter { - pub fn new() -> Self { + pub fn new(order: Vec) -> Self { Self { - records: ListOrderedMultimap::new(), + records: BTreeMap::new(), current: RefCell::new(None), + order, } } - pub fn insert(&mut self, key: String, record: OwnedRecord) { - self.records.insert(key, record); + pub fn insert(&mut self, key: OwnedRecord, record: OwnedRecord) { + if let Some(vec) = self.records.get_mut(&key) { + vec.push_back(record); + } else { + self.records.insert(key, VecDeque::from(vec![record])); + } } } @@ -28,28 +35,33 @@ impl Cursor for Sorter { } fn rewind(&mut self) -> Result> { - let current = self.records.pop_front(); - match current { - Some((_, record)) => { - *self.current.borrow_mut() = Some(record); + let mut c = self.current.borrow_mut(); + for (_, record) in self.records.iter_mut() { + let record = record.pop_front(); + if record.is_some() { + *c = record; + break; } - None => { - *self.current.borrow_mut() = None; - } - }; + } + Ok(CursorResult::Ok(())) } fn next(&mut self) -> Result> { - let current = self.records.pop_front(); - match current { - Some((_, record)) => { - *self.current.borrow_mut() = Some(record); + let mut c = self.current.borrow_mut(); + let mut matched = false; + for (_, record) in self.records.iter_mut() { + let record = record.pop_front(); + if record.is_some() { + *c = record; + matched = true; + break; } - None => { - *self.current.borrow_mut() = None; - } - }; + } + self.records.retain(|_, v| !v.is_empty()); + if !matched { + *c = None; + } Ok(CursorResult::Ok(())) } @@ -57,7 +69,7 @@ impl Cursor for Sorter { Ok(()) } - fn rowid(&self) -> Result>> { + fn rowid(&self) -> Result> { todo!(); } @@ -66,13 +78,9 @@ impl Cursor for Sorter { } fn insert(&mut self, record: &OwnedRecord) -> Result<()> { - let key = match record.values[0] { - OwnedValue::Integer(i) => i.to_string(), - OwnedValue::Text(ref s) => s.to_string(), - _ => todo!(), - }; - trace!("Inserting record with key: {}", key); - self.insert(key, record.clone()); + let key_fields = self.order.len(); + let key = OwnedRecord::new(record.values[0..key_fields].to_vec()); + self.insert(key, OwnedRecord::new(record.values[key_fields..].to_vec())); Ok(()) } diff --git a/core/translate.rs b/core/translate.rs index 14f1ff207..0b85e6c13 100644 --- a/core/translate.rs +++ b/core/translate.rs @@ -4,9 +4,11 @@ use std::rc::Rc; use crate::expr::{build_select, maybe_apply_affinity, translate_expr}; use crate::function::{AggFunc, Func}; use crate::pager::Pager; -use crate::schema::{Schema, Table}; +use crate::schema::{Column, PseudoTable, Schema, Table}; use crate::select::{ColumnInfo, LoopInfo, Select, SrcTable}; use crate::sqlite3_ondisk::{DatabaseHeader, MIN_PAGE_CACHE_SIZE}; +use crate::types::{OwnedRecord, OwnedValue}; +use crate::util::normalize_ident; use crate::vdbe::{BranchOffset, Insn, Program, ProgramBuilder}; use crate::where_clause::{ evaluate_conditions, translate_conditions, translate_where, Inner, Left, QueryConstraint, @@ -20,6 +22,13 @@ struct LimitInfo { goto_label: BranchOffset, } +#[derive(Debug)] +struct SortInfo { + sorter_cursor: usize, + sorter_reg: usize, + count: usize, +} + /// Translate SQL statement into bytecode program. pub fn translate( schema: &Schema, @@ -49,10 +58,34 @@ fn translate_select(mut select: Select) -> Result { ); let start_offset = program.offset(); + let mut sort_info = if let Some(order_by) = select.order_by { + let sorter_cursor = program.alloc_cursor_id(None, None); + let mut order = Vec::new(); + for col in order_by { + order.push(OwnedValue::Integer(if let Some(ord) = col.order { + ord as i64 + } else { + 0 + })); + } + program.emit_insn(Insn::SorterOpen { + cursor_id: sorter_cursor, + order: OwnedRecord::new(order), + columns: select.column_info.len() + 1, // +1 for the key + }); + Some(SortInfo { + sorter_cursor, + sorter_reg: 0, // will be overwritten later + count: 0, // will be overwritten later + }) + } else { + None + }; + let limit_info = if let Some(limit) = &select.limit { assert!(limit.offset.is_none()); let target_register = program.alloc_register(); - let limit_reg = translate_expr(&mut program, &select, &limit.expr, target_register)?; + let limit_reg = translate_expr(&mut program, &select, &limit.expr, target_register, None)?; let num = if let ast::Expr::Literal(ast::Literal::Numeric(num)) = &limit.expr { num.parse::()? } else { @@ -79,14 +112,41 @@ fn translate_select(mut select: Select) -> Result { if !select.src_tables.is_empty() { let constraint = translate_tables_begin(&mut program, &mut select)?; - let (register_start, register_end) = translate_columns(&mut program, &select)?; + let (register_start, column_count) = if let Some(sort_columns) = select.order_by { + let start = program.next_free_register(); + for col in sort_columns.iter() { + let target = program.alloc_register(); + translate_expr(&mut program, &select, &col.expr, target, None)?; + } + let (_, result_cols_count) = translate_columns(&mut program, &select, None)?; + sort_info + .as_mut() + .map(|inner| inner.count = result_cols_count + sort_columns.len() + 1); // +1 for the key + (start, result_cols_count + sort_columns.len()) + } else { + translate_columns(&mut program, &select, None)? + }; if !select.exist_aggregation { - program.emit_insn(Insn::ResultRow { - start_reg: register_start, - count: register_end - register_start, - }); - emit_limit_insn(&limit_info, &mut program); + if let Some(ref mut sort_info) = sort_info { + let dest = program.alloc_register(); + program.emit_insn(Insn::MakeRecord { + start_reg: register_start, + count: column_count, + dest_reg: dest, + }); + program.emit_insn(Insn::SorterInsert { + cursor_id: sort_info.sorter_cursor, + record_reg: dest, + }); + sort_info.sorter_reg = register_start; + } else { + program.emit_insn(Insn::ResultRow { + start_reg: register_start, + count: column_count, + }); + emit_limit_insn(&limit_info, &mut program); + } } translate_tables_end(&mut program, &select, constraint); @@ -105,23 +165,30 @@ fn translate_select(mut select: Select) -> Result { // only one result row program.emit_insn(Insn::ResultRow { start_reg: register_start, - count: register_end - register_start, + count: column_count, }); emit_limit_insn(&limit_info, &mut program); } } else { assert!(!select.exist_aggregation); + assert!(sort_info.is_none()); let where_maybe = translate_where(&select, &mut program)?; - let (register_start, register_end) = translate_columns(&mut program, &select)?; + let (register_start, count) = translate_columns(&mut program, &select, None)?; if let Some(where_clause_label) = where_maybe { program.resolve_label(where_clause_label, program.offset() + 1); } program.emit_insn(Insn::ResultRow { start_reg: register_start, - count: register_end - register_start, + count, }); emit_limit_insn(&limit_info, &mut program); }; + + // now do the sort for ORDER BY + if select.order_by.is_some() { + let _ = translate_sorter(&select, &mut program, &sort_info.unwrap(), &limit_info); + } + program.emit_insn(Insn::Halt); let halt_offset = program.offset() - 1; if let Some(limit_info) = limit_info { @@ -155,6 +222,72 @@ fn emit_limit_insn(limit_info: &Option, program: &mut ProgramBuilder) } } +fn translate_sorter( + select: &Select, + program: &mut ProgramBuilder, + sort_info: &SortInfo, + limit_info: &Option, +) -> Result<()> { + assert!(sort_info.count > 0); + let mut pseudo_columns = Vec::new(); + for col in select.columns.iter() { + match col { + ast::ResultColumn::Expr(expr, _) => match expr { + ast::Expr::Id(ident) => { + pseudo_columns.push(Column { + name: normalize_ident(&ident.0), + primary_key: false, + ty: crate::schema::Type::Null, + }); + } + _ => { + todo!(); + } + }, + ast::ResultColumn::Star => {} + ast::ResultColumn::TableStar(_) => {} + } + } + let pseudo_cursor = program.alloc_cursor_id( + None, + Some(Table::Pseudo(Rc::new(PseudoTable { + columns: pseudo_columns, + }))), + ); + let pseudo_content_reg = program.alloc_register(); + program.emit_insn(Insn::OpenPseudo { + cursor_id: pseudo_cursor, + content_reg: pseudo_content_reg, + num_fields: sort_info.count, + }); + let label = program.allocate_label(); + program.emit_insn_with_label_dependency( + Insn::SorterSort { + cursor_id: sort_info.sorter_cursor, + pc_if_empty: label, + }, + label, + ); + let sorter_data_offset = program.offset(); + program.emit_insn(Insn::SorterData { + cursor_id: sort_info.sorter_cursor, + dest_reg: pseudo_content_reg, + pseudo_cursor, + }); + let (register_start, count) = translate_columns(program, select, Some(pseudo_cursor))?; + program.emit_insn(Insn::ResultRow { + start_reg: register_start, + count, + }); + emit_limit_insn(&limit_info, program); + program.emit_insn(Insn::SorterNext { + cursor_id: sort_info.sorter_cursor, + pc_if_next: sorter_data_offset, + }); + program.resolve_label(label, program.offset()); + Ok(()) +} + fn translate_tables_begin( program: &mut ProgramBuilder, select: &mut Select, @@ -164,7 +297,7 @@ fn translate_tables_begin( select.loops.push(loop_info); } - let conditions = evaluate_conditions(program, select)?; + let conditions = evaluate_conditions(program, select, None)?; for loop_info in &mut select.loops { let mut left_join_match_flag_maybe = None; @@ -181,7 +314,7 @@ fn translate_tables_begin( translate_table_open_loop(program, loop_info, left_join_match_flag_maybe); } - translate_conditions(program, select, conditions) + translate_conditions(program, select, conditions, None) } fn handle_skip_row( @@ -293,7 +426,7 @@ fn translate_table_open_cursor(program: &mut ProgramBuilder, table: &SrcTable) - Some(alias) => alias.clone(), None => table.table.get_name().to_string(), }; - let cursor_id = program.alloc_cursor_id(table_identifier, table.table.clone()); + let cursor_id = program.alloc_cursor_id(Some(table_identifier), Some(table.table.clone())); let root_page = match &table.table { Table::BTree(btree) => btree.root_page, Table::Pseudo(_) => todo!(), @@ -337,7 +470,11 @@ fn translate_table_open_loop( loop_info.rewind_offset = program.offset() - 1; } -fn translate_columns(program: &mut ProgramBuilder, select: &Select) -> Result<(usize, usize)> { +fn translate_columns( + program: &mut ProgramBuilder, + select: &Select, + cursor_hint: Option, +) -> Result<(usize, usize)> { let register_start = program.next_free_register(); // allocate one register as output for each col @@ -347,14 +484,14 @@ fn translate_columns(program: &mut ProgramBuilder, select: &Select) -> Result<(u .map(|col| col.columns_to_allocate) .sum(); program.alloc_registers(registers); - let register_end = program.next_free_register(); + let count = program.next_free_register() - register_start; let mut target = register_start; for (col, info) in select.columns.iter().zip(select.column_info.iter()) { - translate_column(program, select, col, info, target)?; + translate_column(program, select, col, info, target, cursor_hint)?; target += info.columns_to_allocate; } - Ok((register_start, register_end)) + Ok((register_start, count)) } fn translate_column( @@ -363,19 +500,27 @@ fn translate_column( col: &ast::ResultColumn, info: &ColumnInfo, target_register: usize, // where to store the result, in case of star it will be the start of registers added + cursor_hint: Option, ) -> Result<()> { match col { ast::ResultColumn::Expr(expr, _) => { if info.is_aggregation_function() { - let _ = translate_aggregation(program, select, expr, info, target_register)?; + let _ = translate_aggregation( + program, + select, + expr, + info, + target_register, + cursor_hint, + )?; } else { - let _ = translate_expr(program, select, expr, target_register)?; + let _ = translate_expr(program, select, expr, target_register, cursor_hint)?; } } ast::ResultColumn::Star => { let mut target_register = target_register; for join in &select.src_tables { - translate_table_star(join, program, target_register); + translate_table_star(join, program, target_register, cursor_hint); target_register += &join.table.columns().len(); } } @@ -384,12 +529,17 @@ fn translate_column( Ok(()) } -fn translate_table_star(table: &SrcTable, program: &mut ProgramBuilder, target_register: usize) { +fn translate_table_star( + table: &SrcTable, + program: &mut ProgramBuilder, + target_register: usize, + cursor_hint: Option, +) { let table_identifier = match table.alias { Some(alias) => alias.clone(), None => table.table.get_name().to_string(), }; - let table_cursor = program.resolve_cursor_id(&table_identifier); + let table_cursor = program.resolve_cursor_id(&table_identifier, cursor_hint); let table = &table.table; for (i, col) in table.columns().iter().enumerate() { let col_target_register = target_register + i; @@ -404,7 +554,7 @@ fn translate_table_star(table: &SrcTable, program: &mut ProgramBuilder, target_r dest: col_target_register, cursor_id: table_cursor, }); - maybe_apply_affinity(col, col_target_register, program); + maybe_apply_affinity(col.ty, col_target_register, program); } } } @@ -415,6 +565,7 @@ fn translate_aggregation( expr: &ast::Expr, info: &ColumnInfo, target_register: usize, + cursor_hint: Option, ) -> Result { let _ = expr; assert!(info.func.is_some()); @@ -430,7 +581,7 @@ fn translate_aggregation( } let expr = &args[0]; let expr_reg = program.alloc_register(); - let _ = translate_expr(program, select, expr, expr_reg)?; + let _ = translate_expr(program, select, expr, expr_reg, cursor_hint)?; program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, @@ -445,7 +596,7 @@ fn translate_aggregation( } else { let expr = &args[0]; let expr_reg = program.alloc_register(); - let _ = translate_expr(program, select, expr, expr_reg); + let _ = translate_expr(program, select, expr, expr_reg, cursor_hint); expr_reg }; program.emit_insn(Insn::AggStep { @@ -486,10 +637,11 @@ fn translate_aggregation( delimiter_expr = ast::Expr::Literal(Literal::String(String::from("\",\""))); } - if let Err(error) = translate_expr(program, select, expr, expr_reg) { + if let Err(error) = translate_expr(program, select, expr, expr_reg, cursor_hint) { anyhow::bail!(error); } - if let Err(error) = translate_expr(program, select, &delimiter_expr, delimiter_reg) + if let Err(error) = + translate_expr(program, select, &delimiter_expr, delimiter_reg, cursor_hint) { anyhow::bail!(error); } @@ -509,7 +661,7 @@ fn translate_aggregation( } let expr = &args[0]; let expr_reg = program.alloc_register(); - let _ = translate_expr(program, select, expr, expr_reg); + let _ = translate_expr(program, select, expr, expr_reg, cursor_hint); program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, @@ -524,7 +676,7 @@ fn translate_aggregation( } let expr = &args[0]; let expr_reg = program.alloc_register(); - let _ = translate_expr(program, select, expr, expr_reg); + let _ = translate_expr(program, select, expr, expr_reg, cursor_hint); program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, @@ -558,10 +710,11 @@ fn translate_aggregation( _ => anyhow::bail!("Incorrect delimiter parameter"), }; - if let Err(error) = translate_expr(program, select, expr, expr_reg) { + if let Err(error) = translate_expr(program, select, expr, expr_reg, cursor_hint) { anyhow::bail!(error); } - if let Err(error) = translate_expr(program, select, &delimiter_expr, delimiter_reg) + if let Err(error) = + translate_expr(program, select, &delimiter_expr, delimiter_reg, cursor_hint) { anyhow::bail!(error); } @@ -581,7 +734,7 @@ fn translate_aggregation( } let expr = &args[0]; let expr_reg = program.alloc_register(); - let _ = translate_expr(program, select, expr, expr_reg)?; + let _ = translate_expr(program, select, expr, expr_reg, cursor_hint)?; program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, @@ -596,7 +749,7 @@ fn translate_aggregation( } let expr = &args[0]; let expr_reg = program.alloc_register(); - let _ = translate_expr(program, select, expr, expr_reg)?; + let _ = translate_expr(program, select, expr, expr_reg, cursor_hint)?; program.emit_insn(Insn::AggStep { acc_reg: target_register, col: expr_reg, diff --git a/core/types.rs b/core/types.rs index 3f62d9f03..137f8e8fd 100644 --- a/core/types.rs +++ b/core/types.rs @@ -93,6 +93,14 @@ impl std::cmp::PartialOrd for OwnedValue { } } +impl std::cmp::Eq for OwnedValue {} + +impl std::cmp::Ord for OwnedValue { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap() + } +} + impl std::ops::Add for OwnedValue { type Output = OwnedValue; @@ -267,7 +275,7 @@ impl<'a> Record<'a> { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct OwnedRecord { pub values: Vec, } @@ -288,7 +296,7 @@ pub trait Cursor { fn rewind(&mut self) -> Result>; fn next(&mut self) -> Result>; fn wait_for_completion(&mut self) -> Result<()>; - fn rowid(&self) -> Result>>; + fn rowid(&self) -> Result>; fn record(&self) -> Result>>; fn insert(&mut self, record: &OwnedRecord) -> Result<()>; fn set_null_flag(&mut self, flag: bool); diff --git a/core/vdbe.rs b/core/vdbe.rs index 40d56aea5..adbe7dc72 100644 --- a/core/vdbe.rs +++ b/core/vdbe.rs @@ -1,6 +1,7 @@ use crate::btree::BTreeCursor; use crate::function::{AggFunc, SingleRowFunc}; use crate::pager::Pager; +use crate::pseudo::PseudoCursor; use crate::schema::Table; use crate::types::{AggContext, Cursor, CursorResult, OwnedRecord, OwnedValue, Record}; @@ -216,7 +217,9 @@ pub enum Insn { // Open a sorter. SorterOpen { - cursor_id: CursorID, + cursor_id: CursorID, // P1 + columns: usize, // P2 + order: OwnedRecord, // P4. 0 if ASC and 1 if DESC }, // Insert a row into the sorter. @@ -228,12 +231,14 @@ pub enum Insn { // Sort the rows in the sorter. SorterSort { cursor_id: CursorID, + pc_if_empty: BranchOffset, }, // Retrieve the next row from the sorter. SorterData { - cursor_id: CursorID, // P1 - dest_reg: usize, // P2 + cursor_id: CursorID, // P1 + dest_reg: usize, // P2 + pseudo_cursor: usize, // P3 }, // Advance to the next row in the sorter. @@ -266,7 +271,7 @@ pub struct ProgramBuilder { unresolved_labels: Vec>, next_insn_label: Option, // Cursors that are referenced by the program. Indexed by CursorID. - cursor_ref: Vec<(String, Table)>, + pub cursor_ref: Vec<(Option, Option)>, // List of deferred label resolutions. Each entry is a pair of (label, insn_reference). deferred_label_resolutions: Vec<(BranchOffset, InsnReference)>, } @@ -302,7 +307,11 @@ impl ProgramBuilder { self.next_free_register } - pub fn alloc_cursor_id(&mut self, table_identifier: String, table: Table) -> usize { + pub fn alloc_cursor_id( + &mut self, + table_identifier: Option, + table: Option
, + ) -> usize { let cursor = self.next_free_cursor_id; self.next_free_cursor_id += 1; self.cursor_ref.push((table_identifier, table)); @@ -477,6 +486,10 @@ impl ProgramBuilder { assert!(*pc_if_next < 0); *pc_if_next = to_offset; } + Insn::SorterSort { pc_if_empty, .. } => { + assert!(*pc_if_empty < 0); + *pc_if_empty = to_offset; + } Insn::NotNull { reg: _reg, target_pc, @@ -497,10 +510,21 @@ impl ProgramBuilder { } // translate table to cursor id - pub fn resolve_cursor_id(&self, table_identifier: &str) -> CursorID { + pub fn resolve_cursor_id( + &self, + table_identifier: &str, + cursor_hint: Option, + ) -> CursorID { + if let Some(cursor_hint) = cursor_hint { + return cursor_hint; + } self.cursor_ref .iter() - .position(|(t_ident, _)| *t_ident == table_identifier) + .position(|(t_ident, _)| { + t_ident + .as_ref() + .is_some_and(|ident| ident == table_identifier) + }) .unwrap() } @@ -566,7 +590,7 @@ impl ProgramState { pub struct Program { pub max_registers: usize, pub insns: Vec, - pub cursor_ref: Vec<(String, Table)>, + pub cursor_ref: Vec<(Option, Option
)>, } impl Program { @@ -832,13 +856,12 @@ impl Program { } Insn::OpenPseudo { cursor_id, - content_reg, - num_fields, + content_reg: _, + num_fields: _, } => { - let _ = cursor_id; - let _ = content_reg; - let _ = num_fields; - todo!(); + let cursor = Box::new(PseudoCursor::new()); + cursors.insert(*cursor_id, cursor); + state.pc += 1; } Insn::RewindAsync { cursor_id } => { let cursor = cursors.get_mut(cursor_id).unwrap(); @@ -950,7 +973,7 @@ impl Program { } Insn::RowId { cursor_id, dest } => { let cursor = cursors.get_mut(cursor_id).unwrap(); - if let Some(ref rowid) = *cursor.rowid()? { + if let Some(ref rowid) = cursor.rowid()? { state.registers[*dest] = OwnedValue::Integer(*rowid as i64); } else { todo!(); @@ -1182,21 +1205,38 @@ impl Program { }; state.pc += 1; } - Insn::SorterOpen { cursor_id } => { - let cursor = Box::new(crate::sorter::Sorter::new()); + Insn::SorterOpen { + cursor_id, + columns, + order, + } => { + let order = order + .values + .iter() + .map(|v| match v { + OwnedValue::Integer(i) => *i == 0, + _ => unreachable!(), + }) + .collect(); + let cursor = Box::new(crate::sorter::Sorter::new(order)); cursors.insert(*cursor_id, cursor); state.pc += 1; } Insn::SorterData { cursor_id, dest_reg, + pseudo_cursor: sorter_cursor, } => { let cursor = cursors.get_mut(cursor_id).unwrap(); - if let Some(ref record) = *cursor.record()? { - state.registers[*dest_reg] = OwnedValue::Record(record.clone()); - } else { - todo!(); - } + let record = match *cursor.record()? { + Some(ref record) => record.clone(), + None => { + todo!(); + } + }; + state.registers[*dest_reg] = OwnedValue::Record(record.clone()); + let sorter_cursor = cursors.get_mut(sorter_cursor).unwrap(); + sorter_cursor.insert(&record)?; state.pc += 1; } Insn::SorterInsert { @@ -1211,10 +1251,16 @@ impl Program { cursor.insert(record)?; state.pc += 1; } - Insn::SorterSort { cursor_id } => { - let cursor = cursors.get_mut(cursor_id).unwrap(); - cursor.rewind()?; - state.pc += 1; + Insn::SorterSort { + cursor_id, + pc_if_empty, + } => { + if let Some(cursor) = cursors.get_mut(cursor_id) { + cursor.rewind()?; + state.pc += 1; + } else { + state.pc = *pc_if_empty; + } } Insn::SorterNext { cursor_id, @@ -1589,8 +1635,14 @@ fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent: Stri format!( "r[{}]={}.{}", dest, - table.get_name(), - table.column_index_to_name(*column).unwrap() + table + .as_ref() + .map(|x| x.get_name()) + .unwrap_or(format!("cursor {}", cursor_id).as_str()), + table + .as_ref() + .and_then(|x| x.column_index_to_name(*column)) + .unwrap_or(format!("column {}", *column).as_str()) ), ) } @@ -1605,7 +1657,12 @@ fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent: Stri *dest_reg as i32, OwnedValue::Text(Rc::new("".to_string())), 0, - format!("r[{}..{}] -> r[{}]", start_reg, start_reg + count, dest_reg), + format!( + "r[{}]=mkrec(r[{}..{}])", + dest_reg, + start_reg, + start_reg + count - 1, + ), ), Insn::ResultRow { start_reg, count } => ( "ResultRow", @@ -1714,7 +1771,11 @@ fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent: Stri format!( "r[{}]={}.rowid", dest, - &program.cursor_ref[*cursor_id].1.get_name() + &program.cursor_ref[*cursor_id] + .1 + .as_ref() + .map(|x| x.get_name()) + .unwrap_or(format!("cursor {}", cursor_id).as_str()) ), ), Insn::DecrJumpZero { reg, target_pc } => ( @@ -1749,23 +1810,45 @@ fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent: Stri 0, format!("accum=r[{}]", *register), ), - Insn::SorterOpen { cursor_id } => ( - "SorterOpen", - *cursor_id as i32, - 0, - 0, - OwnedValue::Text(Rc::new("".to_string())), - 0, - format!("cursor={}", cursor_id), - ), + Insn::SorterOpen { + cursor_id, + columns, + order, + } => { + let p4 = String::new(); + let to_print: Vec = order + .values + .iter() + .map(|v| match v { + OwnedValue::Integer(i) => { + if *i == 0 { + "B".to_string() + } else { + "-B".to_string() + } + } + _ => unreachable!(), + }) + .collect(); + ( + "SorterOpen", + *cursor_id as i32, + *columns as i32, + 0, + OwnedValue::Text(Rc::new(format!("k({},{})", columns, to_print.join(",")))), + 0, + format!("cursor={}", cursor_id), + ) + } Insn::SorterData { cursor_id, dest_reg, + pseudo_cursor, } => ( "SorterData", *cursor_id as i32, *dest_reg as i32, - 0, + *pseudo_cursor as i32, OwnedValue::Text(Rc::new("".to_string())), 0, format!("r[{}]=data", dest_reg), @@ -1778,14 +1861,17 @@ fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent: Stri *cursor_id as i32, *record_reg as i32, 0, - OwnedValue::Text(Rc::new("".to_string())), + OwnedValue::Integer(0), 0, format!("key=r[{}]", record_reg), ), - Insn::SorterSort { cursor_id } => ( + Insn::SorterSort { + cursor_id, + pc_if_empty, + } => ( "SorterSort", *cursor_id as i32, - 0, + *pc_if_empty as i32, 0, OwnedValue::Text(Rc::new("".to_string())), 0, @@ -1833,11 +1919,7 @@ fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent: Stri fn get_indent_count(indent_count: usize, curr_insn: &Insn, prev_insn: Option<&Insn>) -> usize { let indent_count = if let Some(insn) = prev_insn { match insn { - Insn::RewindAwait { - cursor_id: _, - pc_if_empty: _, - } => indent_count + 1, - Insn::SorterSort { cursor_id: _ } => indent_count + 1, + Insn::RewindAwait { .. } | Insn::SorterSort { .. } => indent_count + 1, _ => indent_count, } } else { @@ -1845,11 +1927,7 @@ fn get_indent_count(indent_count: usize, curr_insn: &Insn, prev_insn: Option<&In }; match curr_insn { - Insn::NextAsync { cursor_id: _ } => indent_count - 1, - Insn::SorterNext { - cursor_id: _, - pc_if_next: _, - } => indent_count - 1, + Insn::NextAsync { .. } | Insn::SorterNext { .. } => indent_count - 1, _ => indent_count, } } diff --git a/core/where_clause.rs b/core/where_clause.rs index 225d83246..18e6114c5 100644 --- a/core/where_clause.rs +++ b/core/where_clause.rs @@ -53,7 +53,7 @@ pub fn translate_where( ) -> Result> { if let Some(w) = &select.where_clause { let label = program.allocate_label(); - translate_condition_expr(program, select, w, label, false)?; + translate_condition_expr(program, select, w, label, false, None)?; Ok(Some(label)) } else { Ok(None) @@ -63,6 +63,7 @@ pub fn translate_where( pub fn evaluate_conditions( program: &mut ProgramBuilder, select: &Select, + cursor_hint: Option, ) -> Result> { let join_constraints = select .src_tables @@ -80,7 +81,12 @@ pub fn evaluate_conditions( let parsed_where_maybe = select.where_clause.as_ref().map(|where_clause| Where { constraint_expr: where_clause.clone(), no_match_jump_label: program.allocate_label(), - no_match_target_cursor: get_no_match_target_cursor(program, select, where_clause), + no_match_target_cursor: get_no_match_target_cursor( + program, + select, + where_clause, + cursor_hint, + ), }); let parsed_join_maybe = join_maybe.and_then(|(constraint, _)| { @@ -88,7 +94,12 @@ pub fn evaluate_conditions( Some(Join { constraint_expr: expr.clone(), no_match_jump_label: program.allocate_label(), - no_match_target_cursor: get_no_match_target_cursor(program, select, expr), + no_match_target_cursor: get_no_match_target_cursor( + program, + select, + expr, + cursor_hint, + ), }) } else { None @@ -155,6 +166,7 @@ pub fn translate_conditions( program: &mut ProgramBuilder, select: &Select, conditions: Option, + cursor_hint: Option, ) -> Result> { match conditions.as_ref() { Some(QueryConstraint::Left(Left { @@ -171,6 +183,7 @@ pub fn translate_conditions( &where_clause.constraint_expr, where_clause.no_match_jump_label, false, + cursor_hint, )?; } if let Some(join_clause) = join_clause { @@ -180,6 +193,7 @@ pub fn translate_conditions( &join_clause.constraint_expr, join_clause.no_match_jump_label, false, + cursor_hint, )?; } // Set match flag to 1 if we hit the marker (i.e. jump didn't happen to no_match_label as a result of the condition) @@ -197,6 +211,7 @@ pub fn translate_conditions( &where_clause.constraint_expr, where_clause.no_match_jump_label, false, + cursor_hint, )?; } if let Some(join_clause) = &inner_join.join_clause { @@ -206,6 +221,7 @@ pub fn translate_conditions( &join_clause.constraint_expr, join_clause.no_match_jump_label, false, + cursor_hint, )?; } } @@ -221,40 +237,47 @@ fn translate_condition_expr( expr: &ast::Expr, target_jump: BranchOffset, jump_if_true: bool, // if true jump to target on op == true, if false invert op + cursor_hint: Option, ) -> Result<()> { match expr { ast::Expr::Between { .. } => todo!(), ast::Expr::Binary(lhs, ast::Operator::And, rhs) => { if jump_if_true { let label = program.allocate_label(); - let _ = translate_condition_expr(program, select, lhs, label, false); - let _ = translate_condition_expr(program, select, rhs, target_jump, true); + let _ = translate_condition_expr(program, select, lhs, label, false, cursor_hint); + let _ = + translate_condition_expr(program, select, rhs, target_jump, true, cursor_hint); program.resolve_label(label, program.offset()); } else { - let _ = translate_condition_expr(program, select, lhs, target_jump, false); - let _ = translate_condition_expr(program, select, rhs, target_jump, false); + let _ = + translate_condition_expr(program, select, lhs, target_jump, false, cursor_hint); + let _ = + translate_condition_expr(program, select, rhs, target_jump, false, cursor_hint); } } ast::Expr::Binary(lhs, ast::Operator::Or, rhs) => { if jump_if_true { - let _ = translate_condition_expr(program, select, lhs, target_jump, true); - let _ = translate_condition_expr(program, select, rhs, target_jump, true); + let _ = + translate_condition_expr(program, select, lhs, target_jump, true, cursor_hint); + let _ = + translate_condition_expr(program, select, rhs, target_jump, true, cursor_hint); } else { let label = program.allocate_label(); - let _ = translate_condition_expr(program, select, lhs, label, true); - let _ = translate_condition_expr(program, select, rhs, target_jump, false); + let _ = translate_condition_expr(program, select, lhs, label, true, cursor_hint); + let _ = + translate_condition_expr(program, select, rhs, target_jump, false, cursor_hint); program.resolve_label(label, program.offset()); } } ast::Expr::Binary(lhs, op, rhs) => { let lhs_reg = program.alloc_register(); let rhs_reg = program.alloc_register(); - let _ = translate_expr(program, select, lhs, lhs_reg); + let _ = translate_expr(program, select, lhs, lhs_reg, cursor_hint); match lhs.as_ref() { ast::Expr::Literal(_) => program.mark_last_insn_constant(), _ => {} } - let _ = translate_expr(program, select, rhs, rhs_reg); + let _ = translate_expr(program, select, rhs, rhs_reg, cursor_hint); match rhs.as_ref() { ast::Expr::Literal(_) => program.mark_last_insn_constant(), _ => {} @@ -434,9 +457,9 @@ fn translate_condition_expr( let pattern_reg = program.alloc_register(); let column_reg = program.alloc_register(); // LIKE(pattern, column). We should translate the pattern first before the column - let _ = translate_expr(program, select, rhs, pattern_reg)?; + let _ = translate_expr(program, select, rhs, pattern_reg, cursor_hint)?; program.mark_last_insn_constant(); - let _ = translate_expr(program, select, lhs, column_reg)?; + let _ = translate_expr(program, select, lhs, column_reg, cursor_hint)?; program.emit_insn(Insn::Function { func: SingleRowFunc::Like, start_reg: pattern_reg, @@ -476,19 +499,31 @@ fn introspect_expression_for_cursors( program: &ProgramBuilder, select: &Select, where_expr: &ast::Expr, + cursor_hint: Option, ) -> Result> { let mut cursors = vec![]; match where_expr { ast::Expr::Binary(e1, _, e2) => { - cursors.extend(introspect_expression_for_cursors(program, select, e1)?); - cursors.extend(introspect_expression_for_cursors(program, select, e2)?); + cursors.extend(introspect_expression_for_cursors( + program, + select, + e1, + cursor_hint, + )?); + cursors.extend(introspect_expression_for_cursors( + program, + select, + e2, + cursor_hint, + )?); } ast::Expr::Id(ident) => { - let (_, _, cursor_id) = resolve_ident_table(program, &ident.0, select)?; + let (_, _, cursor_id, _) = resolve_ident_table(program, &ident.0, select, cursor_hint)?; cursors.push(cursor_id); } ast::Expr::Qualified(tbl, ident) => { - let (_, _, cursor_id) = resolve_ident_qualified(program, &tbl.0, &ident.0, select)?; + let (_, _, cursor_id, _) = + resolve_ident_qualified(program, &tbl.0, &ident.0, select, cursor_hint)?; cursors.push(cursor_id); } ast::Expr::Literal(_) => {} @@ -499,8 +534,18 @@ fn introspect_expression_for_cursors( rhs, escape, } => { - cursors.extend(introspect_expression_for_cursors(program, select, lhs)?); - cursors.extend(introspect_expression_for_cursors(program, select, rhs)?); + cursors.extend(introspect_expression_for_cursors( + program, + select, + lhs, + cursor_hint, + )?); + cursors.extend(introspect_expression_for_cursors( + program, + select, + rhs, + cursor_hint, + )?); } other => { anyhow::bail!("Parse error: unsupported expression: {:?}", other); @@ -514,12 +559,14 @@ fn get_no_match_target_cursor( program: &ProgramBuilder, select: &Select, expr: &ast::Expr, + cursor_hint: Option, ) -> usize { // This is the hackiest part of the code. We are finding the cursor that should be advanced to the next row // when the condition is not met. This is done by introspecting the expression and finding the innermost cursor that is // used in the expression. This is a very naive approach and will not work in all cases. // Thankfully though it might be possible to just refine the logic contained here to make it work in all cases. Maybe. - let cursors = introspect_expression_for_cursors(program, select, expr).unwrap_or_default(); + let cursors = + introspect_expression_for_cursors(program, select, expr, cursor_hint).unwrap_or_default(); if cursors.is_empty() { HARDCODED_CURSOR_LEFT_TABLE } else { diff --git a/testing/all.test b/testing/all.test index 4cb3d9460..7b5edff5c 100755 --- a/testing/all.test +++ b/testing/all.test @@ -10,3 +10,4 @@ source $testdir/select.test source $testdir/where.test source $testdir/like.test source $testdir/scalar-functions.test +source $testdir/orderby.test diff --git a/testing/orderby.test b/testing/orderby.test new file mode 100644 index 000000000..d9dcc6874 --- /dev/null +++ b/testing/orderby.test @@ -0,0 +1,43 @@ +#!/usr/bin/env tclsh + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_execsql_test basic-order-by { + select * from products order by price; +} {9|boots|1.0 +3|shirt|18.0 +4|sweater|25.0 +10|coat|33.0 +6|shorts|70.0 +5|sweatshirt|74.0 +7|jeans|78.0 +1|hat|79.0 +11|accessories|81.0 +2|cap|82.0 +8|sneakers|82.0} + +do_execsql_test basic-order-by-and-limit { + select * from products order by name limit 5; +} {11|accessories|81.0 +9|boots|1.0 +2|cap|82.0 +10|coat|33.0 +1|hat|79.0} + +do_execsql_test basic-order-by-and-limit-2 { + select id, name from products order by name limit 5; +} {11|accessories +9|boots +2|cap +10|coat +1|hat} + +do_execsql_test basic-order-by-and-limit-3 { + select price, name from products where price > 70 order by name; +} {81.0|accessories +82.0|cap +79.0|hat +78.0|jeans +82.0|sneakers +74.0|sweatshirt}