mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-04 17:04:18 +01:00
@@ -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 | |
|
||||
|
||||
49
Cargo.lock
generated
49
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -152,8 +152,8 @@ 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>>> {
|
||||
|
||||
77
core/expr.rs
77
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<Sele
|
||||
column_info,
|
||||
src_tables: joins,
|
||||
limit: &select.limit,
|
||||
order_by: &select.order_by,
|
||||
exist_aggregation,
|
||||
where_clause,
|
||||
loops: Vec::new(),
|
||||
@@ -98,6 +99,7 @@ pub fn build_select<'a>(schema: &Schema, select: &'a ast::Select) -> Result<Sele
|
||||
column_info,
|
||||
src_tables: Vec::new(),
|
||||
limit: &select.limit,
|
||||
order_by: &select.order_by,
|
||||
where_clause,
|
||||
exist_aggregation,
|
||||
loops: Vec::new(),
|
||||
@@ -112,14 +114,15 @@ pub fn translate_expr(
|
||||
select: &Select,
|
||||
expr: &ast::Expr,
|
||||
target_register: usize,
|
||||
cursor_hint: Option<usize>,
|
||||
) -> Result<usize> {
|
||||
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<usize>,
|
||||
) -> 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<usize>,
|
||||
) -> 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,
|
||||
})
|
||||
|
||||
@@ -56,6 +56,7 @@ impl ToString for SingleRowFunc {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Func {
|
||||
Agg(AggFunc),
|
||||
SingleRow(SingleRowFunc),
|
||||
|
||||
@@ -4,6 +4,7 @@ mod expr;
|
||||
mod function;
|
||||
mod io;
|
||||
mod pager;
|
||||
mod pseudo;
|
||||
mod schema;
|
||||
mod select;
|
||||
mod sorter;
|
||||
|
||||
65
core/pseudo.rs
Normal file
65
core/pseudo.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
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| match record.values[0] {
|
||||
OwnedValue::Integer(rowid) => rowid as u64,
|
||||
_ => panic!("Expected integer value"),
|
||||
});
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
fn record(&self) -> Result<Ref<Option<OwnedRecord>>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Func>,
|
||||
pub args: &'a Option<Vec<ast::Expr>>,
|
||||
@@ -39,6 +40,7 @@ pub struct Select<'a> {
|
||||
pub column_info: Vec<ColumnInfo<'a>>,
|
||||
pub src_tables: Vec<SrcTable<'a>>, // Tables we use to get data from. This includes "from" and "joins"
|
||||
pub limit: &'a Option<ast::Limit>,
|
||||
pub order_by: &'a Option<Vec<ast::SortedColumn>>,
|
||||
pub exist_aggregation: bool,
|
||||
pub where_clause: &'a Option<ast::Expr>,
|
||||
/// Ordered list of opened read table loops
|
||||
|
||||
@@ -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<String, OwnedRecord>,
|
||||
records: BTreeMap<OwnedRecord, VecDeque<OwnedRecord>>,
|
||||
current: RefCell<Option<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 +35,33 @@ 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);
|
||||
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<CursorResult<()>> {
|
||||
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<Ref<Option<u64>>> {
|
||||
fn rowid(&self) -> Result<Option<u64>> {
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Program> {
|
||||
);
|
||||
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::<i64>()?
|
||||
} else {
|
||||
@@ -79,14 +112,41 @@ fn translate_select(mut select: Select) -> Result<Program> {
|
||||
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<Program> {
|
||||
// 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<LimitInfo>, program: &mut ProgramBuilder)
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_sorter(
|
||||
select: &Select,
|
||||
program: &mut ProgramBuilder,
|
||||
sort_info: &SortInfo,
|
||||
limit_info: &Option<LimitInfo>,
|
||||
) -> 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<usize>,
|
||||
) -> 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<usize>,
|
||||
) -> 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<usize>,
|
||||
) {
|
||||
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<usize>,
|
||||
) -> Result<usize> {
|
||||
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,
|
||||
|
||||
@@ -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,7 +296,7 @@ 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 rowid(&self) -> Result<Option<u64>>;
|
||||
fn record(&self) -> Result<Ref<Option<OwnedRecord>>>;
|
||||
fn insert(&mut self, record: &OwnedRecord) -> Result<()>;
|
||||
fn set_null_flag(&mut self, flag: bool);
|
||||
|
||||
184
core/vdbe.rs
184
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<Vec<InsnReference>>,
|
||||
next_insn_label: Option<BranchOffset>,
|
||||
// Cursors that are referenced by the program. Indexed by CursorID.
|
||||
cursor_ref: Vec<(String, Table)>,
|
||||
pub cursor_ref: Vec<(Option<String>, Option<Table>)>,
|
||||
// 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<String>,
|
||||
table: Option<Table>,
|
||||
) -> 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>,
|
||||
) -> 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<Insn>,
|
||||
pub cursor_ref: Vec<(String, Table)>,
|
||||
pub cursor_ref: Vec<(Option<String>, Option<Table>)>,
|
||||
}
|
||||
|
||||
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<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),
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ pub fn translate_where(
|
||||
) -> Result<Option<BranchOffset>> {
|
||||
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<usize>,
|
||||
) -> Result<Option<QueryConstraint>> {
|
||||
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<QueryConstraint>,
|
||||
cursor_hint: Option<usize>,
|
||||
) -> Result<Option<QueryConstraint>> {
|
||||
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<usize>,
|
||||
) -> 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<usize>,
|
||||
) -> Result<Vec<usize>> {
|
||||
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>,
|
||||
) -> 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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
43
testing/orderby.test
Normal file
43
testing/orderby.test
Normal file
@@ -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}
|
||||
Reference in New Issue
Block a user