mirror of
https://github.com/tursodatabase/limbo.git
synced 2025-08-04 18:18:03 +00:00
impl order by desc
This commit is contained in:
parent
b86501f12e
commit
8cca659052
13 changed files with 452 additions and 25 deletions
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>>;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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} {}",
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
Loading…
Add table
Add a link
Reference in a new issue