Implement basic ORDER BY

- Only SELECT * is supported
- Only ASC is supported
This commit is contained in:
Bennett Clement
2024-07-18 22:29:06 +08:00
parent 865b3a04e9
commit 2e0d4c6fdb
10 changed files with 236 additions and 72 deletions

View File

@@ -152,12 +152,12 @@ impl Cursor for BTreeCursor {
Ok(())
}
fn rowid(&self) -> Result<Ref<Option<u64>>> {
Ok(self.rowid.borrow())
fn rowid(&self) -> Result<Option<u64>> {
Ok(*self.rowid.borrow())
}
fn record(&self) -> Result<Ref<Option<OwnedRecord>>> {
Ok(self.record.borrow())
fn record(&self) -> Result<Option<OwnedRecord>> {
Ok(self.record.borrow().to_owned())
}
fn insert(&mut self, _record: &OwnedRecord) -> Result<()> {

View File

@@ -4,6 +4,7 @@ mod expr;
mod function;
mod io;
mod pager;
mod pseudo;
mod schema;
mod select;
mod sorter;

64
core/pseudo.rs Normal file
View File

@@ -0,0 +1,64 @@
use anyhow::Result;
use std::cell::{Ref, RefCell};
use crate::types::{Cursor, CursorResult, OwnedRecord, OwnedValue};
pub struct PseudoCursor {
current: RefCell<Option<OwnedRecord>>,
}
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<CursorResult<()>> {
*self.current.borrow_mut() = None;
Ok(CursorResult::Ok(()))
}
fn next(&mut self) -> Result<CursorResult<()>> {
*self.current.borrow_mut() = None;
Ok(CursorResult::Ok(()))
}
fn wait_for_completion(&mut self) -> Result<()> {
Ok(())
}
fn rowid(&self) -> Result<Option<u64>> {
let x = self.current.borrow().as_ref().map(|record| {
let rowid = match record.values[0] {
OwnedValue::Integer(rowid) => rowid as u64,
_ => panic!("Expected integer value"),
};
rowid
});
Ok(x)
}
fn record(&self) -> Result<Option<OwnedRecord>> {
Ok(self.current.borrow().to_owned())
}
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
}
}

View File

@@ -155,11 +155,15 @@ impl BTreeTable {
#[derive(Debug)]
pub struct PseudoTable {
pub columns: Vec<Column>,
pub row: Option<OwnedRecord>,
}
impl PseudoTable {
pub fn new() -> Self {
Self { columns: vec![] }
Self {
columns: vec![],
row: None,
}
}
pub fn add_column(&mut self, name: &str, ty: Type, primary_key: bool) {

View File

@@ -1,24 +1,32 @@
use crate::types::{Cursor, CursorResult, OwnedRecord, OwnedValue};
use anyhow::Result;
use log::trace;
use ordered_multimap::ListOrderedMultimap;
use std::cell::{Ref, RefCell};
use std::{
cell::RefCell,
collections::{BTreeMap, VecDeque},
};
pub struct Sorter {
records: ListOrderedMultimap<String, OwnedRecord>,
current: RefCell<Option<OwnedRecord>>,
records: BTreeMap<OwnedRecord, VecDeque<OwnedRecord>>,
current: RefCell<Option<VecDeque<OwnedRecord>>>,
order: Vec<bool>,
}
impl Sorter {
pub fn new() -> Self {
pub fn new(order: Vec<bool>) -> 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 +36,26 @@ impl Cursor for Sorter {
}
fn rewind(&mut self) -> Result<CursorResult<()>> {
let current = self.records.pop_front();
match current {
Some((_, record)) => {
*self.current.borrow_mut() = Some(record);
}
None => {
*self.current.borrow_mut() = None;
}
let empty = {
let current = self.current.borrow();
current.as_ref().map(|r| r.is_empty()).unwrap_or(true)
};
if empty {
let mut c = self.current.borrow_mut();
*c = self.records.pop_first().map(|(_, record)| record);
}
Ok(CursorResult::Ok(()))
}
fn next(&mut self) -> Result<CursorResult<()>> {
let current = self.records.pop_front();
match current {
Some((_, record)) => {
*self.current.borrow_mut() = Some(record);
}
None => {
*self.current.borrow_mut() = None;
}
let empty = {
let current = self.current.borrow();
current.as_ref().map(|r| r.is_empty()).unwrap_or(true)
};
if empty {
let mut c = self.current.borrow_mut();
*c = self.records.pop_first().map(|(_, record)| record);
}
Ok(CursorResult::Ok(()))
}
@@ -57,22 +63,20 @@ impl Cursor for Sorter {
Ok(())
}
fn rowid(&self) -> Result<Ref<Option<u64>>> {
fn rowid(&self) -> Result<Option<u64>> {
todo!();
}
fn record(&self) -> Result<Ref<Option<OwnedRecord>>> {
Ok(self.current.borrow())
fn record(&self) -> Result<Option<OwnedRecord>> {
let mut current = self.current.borrow_mut();
Ok(current.as_mut().map(|r| r.pop_front().unwrap()))
}
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());
trace!("Inserting record with key: {:?}", key);
self.insert(key, OwnedRecord::new(record.values[key_fields..].to_vec()));
Ok(())
}

View File

@@ -7,6 +7,7 @@ use crate::pager::Pager;
use crate::schema::{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::vdbe::{BranchOffset, Insn, Program, ProgramBuilder};
use crate::where_clause::{
evaluate_conditions, translate_conditions, translate_where, Inner, Left, QueryConstraint,
@@ -56,10 +57,20 @@ fn translate_select(mut select: Select) -> Result<Program> {
);
let start_offset = program.offset();
let mut sort_info = if let Some(_) = select.order_by {
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,
@@ -174,7 +185,7 @@ fn translate_select(mut select: Select) -> Result<Program> {
// now do the sort for ORDER BY
if select.order_by.is_some() {
let _ = translate_sorter(&select, &mut program, &sort_info.unwrap());
let _ = translate_sorter(&select, &mut program, &sort_info.unwrap(), &limit_info);
}
program.emit_insn(Insn::Halt);
@@ -214,6 +225,7 @@ fn translate_sorter(
select: &Select,
program: &mut ProgramBuilder,
sort_info: &SortInfo,
limit_info: &Option<LimitInfo>,
) -> Result<()> {
assert!(sort_info.count > 0);
@@ -236,12 +248,14 @@ fn translate_sorter(
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,

View File

@@ -93,6 +93,14 @@ impl std::cmp::PartialOrd<OwnedValue> 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<OwnedValue> 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<OwnedValue>,
}
@@ -288,8 +296,8 @@ pub trait Cursor {
fn rewind(&mut self) -> Result<CursorResult<()>>;
fn next(&mut self) -> Result<CursorResult<()>>;
fn wait_for_completion(&mut self) -> Result<()>;
fn rowid(&self) -> Result<Ref<Option<u64>>>;
fn record(&self) -> Result<Ref<Option<OwnedRecord>>>;
fn rowid(&self) -> Result<Option<u64>>;
fn record(&self) -> Result<Option<OwnedRecord>>;
fn insert(&mut self, record: &OwnedRecord) -> Result<()>;
fn set_null_flag(&mut self, flag: bool);
fn get_null_flag(&self) -> bool;

View File

@@ -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.
@@ -233,8 +236,9 @@ pub enum Insn {
// 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.
@@ -871,13 +875,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();
@@ -908,7 +911,7 @@ impl Program {
dest,
} => {
let cursor = cursors.get_mut(cursor_id).unwrap();
if let Some(ref record) = *cursor.record()? {
if let Some(ref record) = cursor.record()? {
let null_flag = cursor.get_null_flag();
state.registers[*dest] = if null_flag {
OwnedValue::Null
@@ -989,7 +992,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!();
@@ -1221,21 +1224,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 {
@@ -1809,23 +1829,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<String> = 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),

View File

@@ -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

26
testing/orderby.test Normal file
View File

@@ -0,0 +1,26 @@
#!/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}