impl order by desc

This commit is contained in:
limeng.1 2024-11-10 23:33:00 +08:00
parent b86501f12e
commit 8cca659052
13 changed files with 452 additions and 25 deletions

View file

@ -91,4 +91,12 @@ impl Cursor for PseudoCursor {
fn btree_create(&mut self, _flags: usize) -> u32 {
unreachable!("Please don't.")
}
fn last(&mut self) -> Result<CursorResult<()>> {
todo!()
}
fn prev(&mut self) -> Result<CursorResult<()>> {
todo!()
}
}

View file

@ -117,6 +117,10 @@ impl BTreeCursor {
}
}
fn cell_count(&self) -> Result<CursorResult<usize>> {
self.load_page_with(|page| Ok(CursorResult::Ok(page.cell_count())))
}
fn is_empty_table(&mut self) -> Result<CursorResult<bool>> {
let page = self.pager.read_page(self.root_page)?;
let page = RefCell::borrow(&page);
@ -128,6 +132,106 @@ impl BTreeCursor {
Ok(CursorResult::Ok(cell_count == 0))
}
fn get_cell_with<T>(
&self,
handle: impl Fn(BTreeCell) -> Result<CursorResult<T>>,
) -> Result<CursorResult<T>> {
let mem_page_rc = self.stack.top();
let cell_idx = self.stack.current_index();
debug!("current id={} cell={}", mem_page_rc.borrow().id, cell_idx);
if mem_page_rc.borrow().is_locked() {
return Ok(CursorResult::IO);
}
if !mem_page_rc.borrow().is_loaded() {
self.pager.load_page(mem_page_rc.clone())?;
return Ok(CursorResult::IO);
}
let mem_page = mem_page_rc.borrow();
let contents = mem_page.contents.as_ref().unwrap();
let cell = contents.cell_get(
cell_idx,
self.pager.clone(),
self.max_local(contents.page_type()),
self.min_local(contents.page_type()),
self.usable_space(),
)?;
handle(cell)
}
fn move_to_child_rightmost(&mut self) -> Result<CursorResult<()>> {
// let mem_page = self.get_mem_page();
// let handle_cell = |cell: BTreeCell| match cell {
// BTreeCell::TableInteriorCell(TableInteriorCell {
// _left_child_page,
// _rowid,
// }) => {
// let cell_count = self.cell_count(_left_child_page as usize)?;
// match cell_count {
// CursorResult::Ok(cell_count) => {
// self.page.replace(Some(Rc::new(MemPage::new(
// Some(mem_page.clone()),
// _left_child_page as usize,
// cell_count - 1,
// ))));
// Ok(CursorResult::Ok(()))
// }
// CursorResult::IO => Ok(CursorResult::IO),
// }
// }
// _ => todo!(),
// };
// self.get_cell_with(handle_cell)
Ok(CursorResult::Ok(()))
}
fn btree_prev(&mut self) -> Result<CursorResult<()>> {
// let mut mem_page = self.get_mem_page();
// loop {
// if mem_page.cell_idx() > 0 {
// mem_page.retreat();
// let page_idx = mem_page.page_idx;
// self.page.replace(Some(mem_page));
// match self.load_page_with(page_idx, |page| Ok(CursorResult::Ok(page.is_leaf())))? {
// CursorResult::Ok(true) => return Ok(CursorResult::Ok(())),
// CursorResult::Ok(false) => return self.move_to_child_rightmost(),
// CursorResult::IO => return Ok(CursorResult::IO),
// };
// }
// match &mem_page.parent {
// Some(parent) => mem_page = parent.clone(),
// None => {
// // moved to start of btree
// self.page.replace(None);
// return Ok(CursorResult::Ok(()));
// }
// }
// }
Ok(CursorResult::Ok(()))
}
fn get_prev_record(&mut self) -> Result<CursorResult<(Option<u64>, Option<OwnedRecord>)>> {
let handle_cell = |cell: BTreeCell| match cell {
BTreeCell::TableLeafCell(TableLeafCell {
_rowid, _payload, ..
}) => {
let record: OwnedRecord = crate::storage::sqlite3_ondisk::read_record(&_payload)?;
Ok(CursorResult::Ok((Some(_rowid), Some(record))))
}
BTreeCell::IndexLeafCell(_) => todo!(),
_ => unreachable!(),
};
let record = self.get_cell_with(handle_cell)?;
self.btree_prev()?;
Ok(record)
}
fn get_next_record(
&mut self,
predicate: Option<(SeekKey<'_>, SeekOp)>,
@ -727,6 +831,39 @@ impl BTreeCursor {
page.write_u16(BTREE_HEADER_OFFSET_CELL_COUNT, page.cell_count() as u16 - 1);
}
fn load_page_with<T>(
&self,
handle: impl Fn(&PageContent) -> Result<CursorResult<T>>,
) -> Result<CursorResult<T>> {
let mem_page_rc = self.stack.top();
let cell_idx = self.stack.current_index();
debug!("current id={} cell={}", mem_page_rc.borrow().id, cell_idx);
if mem_page_rc.borrow().is_locked() {
return Ok(CursorResult::IO);
}
if !mem_page_rc.borrow().is_loaded() {
self.pager.load_page(mem_page_rc.clone())?;
return Ok(CursorResult::IO);
}
let mem_page = mem_page_rc.borrow();
let contents = mem_page.contents.as_ref().unwrap();
handle(contents)
}
// fn get_page(&mut self) -> crate::Result<Rc<RefCell<Page>>> {
// let mem_page = {
// let mem_page = self.page.borrow();
// let mem_page = mem_page.as_ref().unwrap();
// mem_page.clone()
// };
// let page_idx = mem_page.page_idx;
// let page_ref = self.pager.read_page(page_idx)?;
// Ok(page_ref)
// }
/// This is a naive algorithm that doesn't try to distribute cells evenly by content.
/// It will try to split the page in half by keys not by content.
/// Sqlite tries to have a page at least 40% full.
@ -1570,6 +1707,13 @@ impl Cursor for BTreeCursor {
}
}
fn last(&mut self) -> Result<CursorResult<()>> {
match self.move_to_rightmost()? {
CursorResult::Ok(_) => self.prev(),
CursorResult::IO => Ok(CursorResult::IO),
}
}
fn next(&mut self) -> Result<CursorResult<()>> {
match self.get_next_record(None)? {
CursorResult::Ok((rowid, next)) => {
@ -1581,6 +1725,17 @@ impl Cursor for BTreeCursor {
}
}
fn prev(&mut self) -> Result<CursorResult<()>> {
match self.get_prev_record()? {
CursorResult::Ok((rowid, record)) => {
self.rowid.replace(rowid);
self.record.replace(record);
Ok(CursorResult::Ok(()))
}
CursorResult::IO => Ok(CursorResult::IO),
}
}
fn wait_for_completion(&mut self) -> Result<()> {
// TODO: Wait for pager I/O to complete
Ok(())

View file

@ -173,12 +173,13 @@ impl Emitter for Operator {
id,
step,
predicates,
..
reverse,
} => {
*step += 1;
const SCAN_OPEN_READ: usize = 1;
const SCAN_BODY: usize = 2;
const SCAN_NEXT: usize = 3;
let reverse = reverse.is_some_and(|r| r);
match *step {
SCAN_OPEN_READ => {
let cursor_id = program.alloc_cursor_id(
@ -199,13 +200,24 @@ impl Emitter for Operator {
SCAN_BODY => {
let cursor_id =
program.resolve_cursor_id(&table_reference.table_identifier, None);
program.emit_insn(Insn::RewindAsync { cursor_id });
if reverse {
program.emit_insn(Insn::LastAsync { cursor_id });
} else {
program.emit_insn(Insn::RewindAsync { cursor_id });
}
let scan_loop_body_label = program.allocate_label();
let halt_label = m.termination_label_stack.last().unwrap();
program.emit_insn_with_label_dependency(
Insn::RewindAwait {
cursor_id,
pc_if_empty: *halt_label,
if reverse {
Insn::LastAwait {
cursor_id,
pc_if_empty: *halt_label,
}
} else {
Insn::RewindAwait {
cursor_id,
pc_if_empty: *halt_label,
}
},
*halt_label,
);
@ -242,15 +254,30 @@ impl Emitter for Operator {
program.resolve_cursor_id(&table_reference.table_identifier, None);
program
.resolve_label(*m.next_row_labels.get(id).unwrap(), program.offset());
program.emit_insn(Insn::NextAsync { cursor_id });
if reverse {
program.emit_insn(Insn::PrevAsync { cursor_id });
} else {
program.emit_insn(Insn::NextAsync { cursor_id });
}
let jump_label = m.scan_loop_body_labels.pop().unwrap();
program.emit_insn_with_label_dependency(
Insn::NextAwait {
cursor_id,
pc_if_next: jump_label,
},
jump_label,
);
if reverse {
program.emit_insn_with_label_dependency(
Insn::PrevAwait {
cursor_id,
pc_if_next: jump_label,
},
jump_label,
);
} else {
program.emit_insn_with_label_dependency(
Insn::NextAwait {
cursor_id,
pc_if_next: jump_label,
},
jump_label,
);
}
Ok(OpStepResult::Done)
}
_ => Ok(OpStepResult::Done),

View file

@ -88,16 +88,18 @@ fn eliminate_unnecessary_orderby(
) -> Result<()> {
match operator {
Operator::Order { source, key, .. } => {
if key.len() != 1 || key.first().unwrap().1 != Direction::Ascending {
if key.len() != 1 {
// TODO: handle multiple order by keys and descending order
return Ok(());
}
let already_ordered = _operator_is_already_ordered_by(
source,
&mut key.first_mut().unwrap().0,
available_indexes,
)?;
let (key, direction) = key.first_mut().unwrap();
let already_ordered = _operator_is_already_ordered_by(source, key, available_indexes)?;
if already_ordered {
push_scan_direction(source, direction);
*operator = source.take_ownership();
}
Ok(())
@ -572,6 +574,21 @@ fn push_predicate(
}
}
fn push_scan_direction(operator: &mut Operator, direction: &Direction) {
match operator {
Operator::Projection { source, .. } => push_scan_direction(source, direction),
Operator::Scan { reverse, .. } => {
if reverse.is_none() {
match direction {
Direction::Ascending => *reverse = Some(false),
Direction::Descending => *reverse = Some(true),
}
}
}
_ => todo!(),
}
}
#[derive(Debug)]
pub struct ExpressionResultCache {
resultmap: HashMap<usize, CachedResult>,

View file

@ -114,6 +114,7 @@ pub enum Operator {
table_reference: BTreeTableReference,
predicates: Option<Vec<ast::Expr>>,
step: usize,
reverse: Option<bool>,
},
// Search operator
// This operator is used to search for a row in a table using an index

View file

@ -319,6 +319,7 @@ fn parse_from(
predicates: None,
id: operator_id_counter.get_next_id(),
step: 0,
reverse: None,
};
let mut tables = vec![first_table];
@ -398,6 +399,7 @@ fn parse_join(
predicates: None,
id: operator_id_counter.get_next_id(),
step: 0,
reverse: None,
},
outer,
predicates,

View file

@ -426,7 +426,9 @@ pub enum SeekKey<'a> {
pub trait Cursor {
fn is_empty(&self) -> bool;
fn rewind(&mut self) -> Result<CursorResult<()>>;
fn last(&mut self) -> Result<CursorResult<()>>;
fn next(&mut self) -> Result<CursorResult<()>>;
fn prev(&mut self) -> Result<CursorResult<()>>;
fn wait_for_completion(&mut self) -> Result<()>;
fn rowid(&self) -> Result<Option<u64>>;
fn seek(&mut self, key: SeekKey, op: SeekOp) -> Result<CursorResult<bool>>;

View file

@ -232,6 +232,13 @@ impl ProgramBuilder {
assert!(*pc_if_empty < 0);
*pc_if_empty = to_offset;
}
Insn::LastAwait {
cursor_id: _cursor_id,
pc_if_empty,
} => {
assert!(*pc_if_empty < 0);
*pc_if_empty = to_offset;
}
Insn::Goto { target_pc } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
@ -269,6 +276,10 @@ impl ProgramBuilder {
assert!(*pc_if_next < 0);
*pc_if_next = to_offset;
}
Insn::PrevAwait { pc_if_next, .. } => {
assert!(*pc_if_next < 0);
*pc_if_next = to_offset;
}
Insn::InitCoroutine {
yield_reg: _,
jump_on_definition,

View file

@ -893,6 +893,15 @@ pub fn insn_to_str(
0,
"".to_string(),
),
Insn::LastAsync { .. } => (
"LastAsync",
0,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::IsNull { src, target_pc } => (
"IsNull",
*src as i32,
@ -911,6 +920,33 @@ pub fn insn_to_str(
0,
where_clause.clone(),
),
Insn::LastAwait { .. } => (
"LastAwait",
0,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::PrevAsync { .. } => (
"PrevAsync",
0,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::PrevAwait { .. } => (
"PrevAwait",
0,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
};
format!(
"{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}",

View file

@ -213,6 +213,15 @@ pub enum Insn {
pc_if_empty: BranchOffset,
},
LastAsync {
cursor_id: CursorID,
},
LastAwait {
cursor_id: CursorID,
pc_if_empty: BranchOffset,
},
// Read a column from the current row of the cursor.
Column {
cursor_id: CursorID,
@ -244,6 +253,15 @@ pub enum Insn {
pc_if_next: BranchOffset,
},
PrevAsync {
cursor_id: CursorID,
},
PrevAwait {
cursor_id: CursorID,
pc_if_next: BranchOffset,
},
// Halt the program.
Halt {
err_code: usize,
@ -1093,6 +1111,29 @@ impl Program {
}
state.pc += 1;
}
Insn::LastAsync { cursor_id } => {
let cursor = cursors.get_mut(cursor_id).unwrap();
match cursor.last()? {
CursorResult::Ok(()) => {}
CursorResult::IO => {
// If there is I/O, the instruction is restarted.
return Ok(StepResult::IO);
}
}
state.pc += 1;
}
Insn::LastAwait {
cursor_id,
pc_if_empty,
} => {
let cursor = cursors.get_mut(cursor_id).unwrap();
cursor.wait_for_completion()?;
if cursor.is_empty() {
state.pc = *pc_if_empty;
} else {
state.pc += 1;
}
}
Insn::RewindAwait {
cursor_id,
pc_if_empty,
@ -1162,6 +1203,31 @@ impl Program {
}
state.pc += 1;
}
Insn::PrevAsync { cursor_id } => {
let cursor = cursors.get_mut(cursor_id).unwrap();
cursor.set_null_flag(false);
match cursor.prev()? {
CursorResult::Ok(_) => {}
CursorResult::IO => {
// If there is I/O, the instruction is restarted.
return Ok(StepResult::IO);
}
}
state.pc += 1;
}
Insn::PrevAwait {
cursor_id,
pc_if_next,
} => {
assert!(*pc_if_next >= 0);
let cursor = cursors.get_mut(cursor_id).unwrap();
cursor.wait_for_completion()?;
if !cursor.is_empty() {
state.pc = *pc_if_next;
} else {
state.pc += 1;
}
}
Insn::NextAwait {
cursor_id,
pc_if_next,
@ -2312,6 +2378,7 @@ fn get_indent_count(indent_count: usize, curr_insn: &Insn, prev_insn: Option<&In
let indent_count = if let Some(insn) = prev_insn {
match insn {
Insn::RewindAwait { .. }
| Insn::LastAwait { .. }
| Insn::SorterSort { .. }
| Insn::SeekGE { .. }
| Insn::SeekGT { .. } => indent_count + 1,
@ -2322,7 +2389,9 @@ fn get_indent_count(indent_count: usize, curr_insn: &Insn, prev_insn: Option<&In
};
match curr_insn {
Insn::NextAsync { .. } | Insn::SorterNext { .. } => indent_count - 1,
Insn::NextAsync { .. } | Insn::SorterNext { .. } | Insn::PrevAsync { .. } => {
indent_count - 1
}
_ => indent_count,
}
}
@ -3088,6 +3157,14 @@ mod tests {
fn btree_create(&mut self, _flags: usize) -> u32 {
unimplemented!()
}
fn last(&mut self) -> Result<CursorResult<()>> {
todo!()
}
fn prev(&mut self) -> Result<CursorResult<()>> {
todo!()
}
}
#[test]

View file

@ -3,6 +3,7 @@ use crate::{
Result,
};
use std::cell::{Ref, RefCell};
use std::cmp::Ordering;
pub struct Sorter {
records: Vec<OwnedRecord>,
@ -27,11 +28,27 @@ impl Cursor for Sorter {
// We do the sorting here since this is what is called by the SorterSort instruction
fn rewind(&mut self) -> Result<CursorResult<()>> {
let key_fields = self.order.len();
self.records
.sort_by_cached_key(|record| OwnedRecord::new(record.values[0..key_fields].to_vec()));
self.records.reverse();
self.records.sort_by(|a, b| {
let cmp_by_idx = |idx: usize, ascending: bool| {
let a = &a.values[idx];
let b = &b.values[idx];
if ascending {
a.cmp(b)
} else {
b.cmp(a)
}
};
let mut cmp_ret = Ordering::Equal;
for (idx, &is_asc) in self.order.iter().enumerate() {
cmp_ret = cmp_by_idx(idx, is_asc);
if cmp_ret != Ordering::Equal {
break;
}
}
cmp_ret
});
self.records.reverse();
self.next()
}
@ -91,4 +108,12 @@ impl Cursor for Sorter {
fn btree_create(&mut self, _flags: usize) -> u32 {
unreachable!("Why did you try to build a new tree with a sorter??? Stand up, open the door and take a walk for 30 min to come back with a better plan.");
}
fn last(&mut self) -> Result<CursorResult<()>> {
todo!()
}
fn prev(&mut self) -> Result<CursorResult<()>> {
todo!()
}
}

View file

@ -885,7 +885,7 @@ fn sqlite3_errstr_impl(rc: i32) -> *const std::ffi::c_char {
"datatype mismatch", // SQLITE_MISMATCH
"bad parameter or other API misuse", // SQLITE_MISUSE
#[cfg(feature = "lfs")]
"", // SQLITE_NOLFS
"", // SQLITE_NOLFS
#[cfg(not(feature = "lfs"))]
"large file support is disabled", // SQLITE_NOLFS
"authorization denied", // SQLITE_AUTH

View file

@ -17,6 +17,20 @@ do_execsql_test basic-order-by {
2|cap|82.0
8|sneakers|82.0}
do_execsql_test basic-order-by {
select * from products order by price desc;
} {2|cap|82.0
8|sneakers|82.0
11|accessories|81.0
1|hat|79.0
7|jeans|78.0
5|sweatshirt|74.0
6|shorts|70.0
10|coat|33.0
4|sweater|25.0
3|shirt|18.0
9|boots|1.0}
do_execsql_test basic-order-by-and-limit {
select * from products order by name limit 5;
} {11|accessories|81.0
@ -25,6 +39,14 @@ do_execsql_test basic-order-by-and-limit {
10|coat|33.0
1|hat|79.0}
do_execsql_test basic-order-by-and-limit {
select * from products order by name desc limit 5;
} {5|sweatshirt|74.0
4|sweater|25.0
8|sneakers|82.0
6|shorts|70.0
3|shirt|18.0}
do_execsql_test basic-order-by-and-limit-2 {
select id, name from products order by name limit 5;
} {11|accessories
@ -33,6 +55,14 @@ do_execsql_test basic-order-by-and-limit-2 {
10|coat
1|hat}
do_execsql_test basic-order-by-and-limit-2 {
select id, name from products order by name desc limit 5;
} {5|sweatshirt
4|sweater
8|sneakers
6|shorts
3|shirt}
do_execsql_test basic-order-by-and-limit-3 {
select price, name from products where price > 70 order by name;
} {81.0|accessories
@ -42,11 +72,47 @@ do_execsql_test basic-order-by-and-limit-3 {
82.0|sneakers
74.0|sweatshirt}
do_execsql_test basic-order-by-and-limit-3 {
select price, name from products where price > 70 order by name desc;
} {74.0|sweatshirt
82.0|sneakers
78.0|jeans
79.0|hat
82.0|cap
81.0|accessories}
do_execsql_test order-by-qualified {
select u.first_name from users u order by u.first_name limit 1;
} {Aaron}
do_execsql_test order-by-qualified {
select u.first_name from users u order by u.first_name desc limit 1;
} {Zoe}
do_execsql_test order-by-column-number {
select first_name, last_name, age from users order by 3,2 limit 2;
} {Teresa|Allen|1
David|Baker|1}
do_execsql_test order-by-column-number {
select first_name, last_name, age from users order by 3 desc, 2 asc limit 2;
} {Connor|Adkins|100
John|Bell|100}
do_execsql_test order-by-column-number {
select first_name, last_name, age from users order by 3 asc, 2 desc limit 2;
} {Kyle|Wolf|1
Jason|Williams|1}
do_execsql_test order-by-column-number {
select first_name, last_name, age from users order by 3 asc, 2 desc limit 10;
} {Kyle|Wolf|1
Jason|Williams|1
Tracey|Williams|1
Jessica|Werner|1
Jasmine|Warren|1
Dennis|Ward|1
Whitney|Walker|1
Robert|Villanueva|1
Cynthia|Thomas|1
Brandon|Tate|1}