mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-08 10:44:20 +01:00
Merge pull request #136 from jussisaurio/labeling-system-coalesce
This commit is contained in:
48
Cargo.lock
generated
48
Cargo.lock
generated
@@ -342,6 +342,15 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
@@ -777,6 +786,12 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
@@ -958,9 +973,11 @@ dependencies = [
|
||||
"log",
|
||||
"mimalloc",
|
||||
"ordered-multimap",
|
||||
"polling",
|
||||
"pprof",
|
||||
"rstest",
|
||||
"rusqlite",
|
||||
"rustix",
|
||||
"sieve-cache",
|
||||
"sqlite3-parser",
|
||||
"thiserror",
|
||||
@@ -1229,6 +1246,21 @@ dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi 0.4.0",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pprof"
|
||||
version = "0.12.1"
|
||||
@@ -1756,6 +1788,22 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
|
||||
[[package]]
|
||||
name = "uncased"
|
||||
version = "0.9.10"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AggFunc {
|
||||
Avg,
|
||||
@@ -24,3 +26,31 @@ impl AggFunc {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SingleRowFunc {
|
||||
Coalesce,
|
||||
}
|
||||
|
||||
pub enum Func {
|
||||
Agg(AggFunc),
|
||||
SingleRow(SingleRowFunc),
|
||||
}
|
||||
|
||||
impl FromStr for Func {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"avg" => Ok(Func::Agg(AggFunc::Avg)),
|
||||
"count" => Ok(Func::Agg(AggFunc::Count)),
|
||||
"group_concat" => Ok(Func::Agg(AggFunc::GroupConcat)),
|
||||
"max" => Ok(Func::Agg(AggFunc::Max)),
|
||||
"min" => Ok(Func::Agg(AggFunc::Min)),
|
||||
"string_agg" => Ok(Func::Agg(AggFunc::StringAgg)),
|
||||
"sum" => Ok(Func::Agg(AggFunc::Sum)),
|
||||
"total" => Ok(Func::Agg(AggFunc::Total)),
|
||||
"coalesce" => Ok(Func::SingleRow(SingleRowFunc::Coalesce)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::function::AggFunc;
|
||||
use crate::function::{AggFunc, Func, SingleRowFunc};
|
||||
use crate::pager::Pager;
|
||||
use crate::schema::{Column, Schema, Table};
|
||||
use crate::sqlite3_ondisk::{DatabaseHeader, MIN_PAGE_CACHE_SIZE};
|
||||
@@ -42,7 +42,7 @@ struct SrcTable {
|
||||
}
|
||||
|
||||
struct ColumnInfo {
|
||||
func: Option<AggFunc>,
|
||||
func: Option<Func>,
|
||||
args: Option<Vec<ast::Expr>>,
|
||||
columns_to_allocate: usize, /* number of result columns this col will result on */
|
||||
}
|
||||
@@ -57,7 +57,10 @@ impl ColumnInfo {
|
||||
}
|
||||
|
||||
pub fn is_aggregation_function(&self) -> bool {
|
||||
self.func.is_some()
|
||||
match self.func {
|
||||
Some(Func::Agg(_)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +135,9 @@ fn build_select(schema: &Schema, select: ast::Select) -> Result<Select> {
|
||||
|
||||
let table = Table::BTree(table);
|
||||
let column_info = analyze_columns(&columns, &joins);
|
||||
let exist_aggregation = column_info.iter().any(|info| info.func.is_some());
|
||||
let exist_aggregation = column_info
|
||||
.iter()
|
||||
.any(|info| info.is_aggregation_function());
|
||||
Ok(Select {
|
||||
columns,
|
||||
column_info,
|
||||
@@ -150,7 +155,9 @@ fn build_select(schema: &Schema, select: ast::Select) -> Result<Select> {
|
||||
..
|
||||
} => {
|
||||
let column_info = analyze_columns(&columns, &Vec::new());
|
||||
let exist_aggregation = column_info.iter().any(|info| info.func.is_some());
|
||||
let exist_aggregation = column_info
|
||||
.iter()
|
||||
.any(|info| info.is_aggregation_function());
|
||||
Ok(Select {
|
||||
columns,
|
||||
column_info,
|
||||
@@ -169,10 +176,12 @@ fn build_select(schema: &Schema, select: ast::Select) -> Result<Select> {
|
||||
fn translate_select(mut select: Select) -> Result<Program> {
|
||||
let mut program = ProgramBuilder::new();
|
||||
let init_label = program.allocate_label();
|
||||
program.add_label(init_label, program.offset());
|
||||
program.emit_insn(Insn::Init {
|
||||
target_pc: init_label,
|
||||
});
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::Init {
|
||||
target_pc: init_label,
|
||||
},
|
||||
init_label,
|
||||
);
|
||||
let start_offset = program.offset();
|
||||
|
||||
let limit_info = if let Some(limit) = &select.limit {
|
||||
@@ -186,10 +195,12 @@ fn translate_select(mut select: Select) -> Result<Program> {
|
||||
};
|
||||
let goto_label = program.allocate_label();
|
||||
if num == 0 {
|
||||
program.add_label(goto_label, program.offset());
|
||||
program.emit_insn(Insn::Goto {
|
||||
target_pc: goto_label,
|
||||
});
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::Goto {
|
||||
target_pc: goto_label,
|
||||
},
|
||||
goto_label,
|
||||
);
|
||||
}
|
||||
Some(LimitInfo {
|
||||
limit_reg,
|
||||
@@ -224,7 +235,7 @@ fn translate_select(mut select: Select) -> Result<Program> {
|
||||
if select.exist_aggregation {
|
||||
let mut target = register_start;
|
||||
for info in &select.column_info {
|
||||
if let Some(func) = &info.func {
|
||||
if let Some(Func::Agg(func)) = &info.func {
|
||||
program.emit_insn(Insn::AggFinal {
|
||||
register: target,
|
||||
func: func.clone(),
|
||||
@@ -271,11 +282,13 @@ fn emit_limit_insn(limit_info: &Option<LimitInfo>, program: &mut ProgramBuilder)
|
||||
}
|
||||
let limit_info = limit_info.as_ref().unwrap();
|
||||
if limit_info.num > 0 {
|
||||
program.add_label(limit_info.goto_label, program.offset());
|
||||
program.emit_insn(Insn::DecrJumpZero {
|
||||
reg: limit_info.limit_reg,
|
||||
target_pc: limit_info.goto_label,
|
||||
});
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::DecrJumpZero {
|
||||
reg: limit_info.limit_reg,
|
||||
target_pc: limit_info.goto_label,
|
||||
},
|
||||
limit_info.goto_label,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,11 +300,13 @@ fn insert_where_clause_instructions(
|
||||
let where_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, &select, w, where_reg)?;
|
||||
let label = program.allocate_label();
|
||||
program.add_label(label, program.offset());
|
||||
program.emit_insn(Insn::IfNot {
|
||||
reg: where_reg,
|
||||
target_pc: label, // jump to 'next row' instruction if where not matched
|
||||
});
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::IfNot {
|
||||
reg: where_reg,
|
||||
target_pc: label, // jump to 'next row' instruction if where not matched
|
||||
},
|
||||
label,
|
||||
);
|
||||
Ok(Some((label, where_reg))) // We emit a placeholder because we determine the jump target later (after we know where the 'cursor next' instruction is)
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -347,11 +362,13 @@ fn translate_table_open_loop(program: &mut ProgramBuilder, loop_info: &mut LoopI
|
||||
cursor_id: loop_info.open_cursor,
|
||||
});
|
||||
let rewind_await_label = program.allocate_label();
|
||||
program.emit_insn(Insn::RewindAwait {
|
||||
cursor_id: loop_info.open_cursor,
|
||||
pc_if_empty: rewind_await_label,
|
||||
});
|
||||
program.add_label(rewind_await_label, program.offset() - 1);
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::RewindAwait {
|
||||
cursor_id: loop_info.open_cursor,
|
||||
pc_if_empty: rewind_await_label,
|
||||
},
|
||||
rewind_await_label,
|
||||
);
|
||||
loop_info.rewind_label = rewind_await_label;
|
||||
loop_info.rewind_offset = program.offset() - 1;
|
||||
}
|
||||
@@ -472,16 +489,9 @@ fn analyze_expr(expr: &Expr, column_info_out: &mut ColumnInfo) {
|
||||
args,
|
||||
filter_over: _,
|
||||
} => {
|
||||
let func_type = match normalize_ident(name.0.as_str()).as_str() {
|
||||
"avg" => Some(AggFunc::Avg),
|
||||
"count" => Some(AggFunc::Count),
|
||||
"group_concat" => Some(AggFunc::GroupConcat),
|
||||
"max" => Some(AggFunc::Max),
|
||||
"min" => Some(AggFunc::Min),
|
||||
"string_agg" => Some(AggFunc::StringAgg),
|
||||
"sum" => Some(AggFunc::Sum),
|
||||
"total" => Some(AggFunc::Total),
|
||||
_ => None,
|
||||
let func_type = match normalize_ident(name.0.as_str()).as_str().parse() {
|
||||
Ok(func) => Some(func),
|
||||
Err(_) => None,
|
||||
};
|
||||
if func_type.is_none() {
|
||||
let args = args.as_ref().unwrap();
|
||||
@@ -563,7 +573,60 @@ fn translate_expr(
|
||||
ast::Expr::Collate(_, _) => todo!(),
|
||||
ast::Expr::DoublyQualified(_, _, _) => todo!(),
|
||||
ast::Expr::Exists(_) => todo!(),
|
||||
ast::Expr::FunctionCall { .. } => todo!(),
|
||||
ast::Expr::FunctionCall {
|
||||
name,
|
||||
distinctness: _,
|
||||
args,
|
||||
filter_over: _,
|
||||
} => {
|
||||
let func_type: Option<Func> = match normalize_ident(name.0.as_str()).as_str().parse() {
|
||||
Ok(func) => Some(func),
|
||||
Err(_) => None,
|
||||
};
|
||||
match func_type {
|
||||
Some(Func::Agg(_)) => {
|
||||
anyhow::bail!("Parse error: aggregation function in non-aggregation context")
|
||||
}
|
||||
Some(Func::SingleRow(srf)) => {
|
||||
match srf {
|
||||
SingleRowFunc::Coalesce => {
|
||||
let args = if let Some(args) = args {
|
||||
if args.len() < 2 {
|
||||
anyhow::bail!(
|
||||
"Parse error: coalesce function with less than 2 arguments"
|
||||
);
|
||||
}
|
||||
args
|
||||
} else {
|
||||
anyhow::bail!("Parse error: coalesce function with no arguments");
|
||||
};
|
||||
|
||||
// coalesce function is implemented as a series of not null checks
|
||||
// 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)?;
|
||||
if index < args.len() - 1 {
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::NotNull {
|
||||
reg,
|
||||
target_pc: label_coalesce_end,
|
||||
},
|
||||
label_coalesce_end,
|
||||
);
|
||||
}
|
||||
}
|
||||
program.preassign_label_to_next_insn(label_coalesce_end);
|
||||
|
||||
Ok(target_register)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
anyhow::bail!("Parse error: unknown function {}", name.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Expr::FunctionCallStar { .. } => todo!(),
|
||||
ast::Expr::Id(ident) => {
|
||||
// let (idx, col) = table.unwrap().get_column(&ident.0).unwrap();
|
||||
@@ -614,7 +677,12 @@ fn translate_expr(
|
||||
}
|
||||
ast::Literal::Blob(_) => todo!(),
|
||||
ast::Literal::Keyword(_) => todo!(),
|
||||
ast::Literal::Null => todo!(),
|
||||
ast::Literal::Null => {
|
||||
program.emit_insn(Insn::Null {
|
||||
dest: target_register,
|
||||
});
|
||||
Ok(target_register)
|
||||
}
|
||||
ast::Literal::CurrentDate => todo!(),
|
||||
ast::Literal::CurrentTime => todo!(),
|
||||
ast::Literal::CurrentTimestamp => todo!(),
|
||||
@@ -668,185 +736,190 @@ fn translate_aggregation(
|
||||
let empty_args = &Vec::<ast::Expr>::new();
|
||||
let args = info.args.as_ref().unwrap_or(empty_args);
|
||||
let dest = match func {
|
||||
AggFunc::Avg => {
|
||||
if args.len() != 1 {
|
||||
anyhow::bail!("Parse error: avg bad number of arguments");
|
||||
Func::SingleRow(_) => anyhow::bail!("Parse error: single row function in aggregation"),
|
||||
Func::Agg(agg_func) => match agg_func {
|
||||
AggFunc::Avg => {
|
||||
if args.len() != 1 {
|
||||
anyhow::bail!("Parse error: avg bad number of arguments");
|
||||
}
|
||||
let expr = &args[0];
|
||||
let expr_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, select, expr, expr_reg)?;
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Avg,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
let expr = &args[0];
|
||||
let expr_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, select, expr, expr_reg)?;
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Avg,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
AggFunc::Count => {
|
||||
let expr_reg = if args.is_empty() {
|
||||
program.alloc_register()
|
||||
} else {
|
||||
AggFunc::Count => {
|
||||
let expr_reg = if args.is_empty() {
|
||||
program.alloc_register()
|
||||
} else {
|
||||
let expr = &args[0];
|
||||
let expr_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, select, expr, expr_reg);
|
||||
expr_reg
|
||||
};
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Count,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
AggFunc::GroupConcat => {
|
||||
if args.len() != 1 && args.len() != 2 {
|
||||
anyhow::bail!("Parse error: group_concat bad number of arguments");
|
||||
}
|
||||
|
||||
let expr_reg = program.alloc_register();
|
||||
let delimiter_reg = program.alloc_register();
|
||||
|
||||
let expr = &args[0];
|
||||
let delimiter_expr: ast::Expr;
|
||||
|
||||
if args.len() == 2 {
|
||||
match &args[1] {
|
||||
ast::Expr::Id(ident) => {
|
||||
if ident.0.starts_with("\"") {
|
||||
delimiter_expr =
|
||||
ast::Expr::Literal(Literal::String(ident.0.to_string()));
|
||||
} else {
|
||||
delimiter_expr = args[1].clone();
|
||||
}
|
||||
}
|
||||
ast::Expr::Literal(Literal::String(s)) => {
|
||||
delimiter_expr = ast::Expr::Literal(Literal::String(s.to_string()));
|
||||
}
|
||||
_ => anyhow::bail!("Incorrect delimiter parameter"),
|
||||
};
|
||||
} else {
|
||||
delimiter_expr = ast::Expr::Literal(Literal::String(String::from("\",\"")));
|
||||
}
|
||||
|
||||
if let Err(error) = translate_expr(program, select, expr, expr_reg) {
|
||||
anyhow::bail!(error);
|
||||
}
|
||||
if let Err(error) = translate_expr(program, select, &delimiter_expr, delimiter_reg)
|
||||
{
|
||||
anyhow::bail!(error);
|
||||
}
|
||||
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: delimiter_reg,
|
||||
func: AggFunc::GroupConcat,
|
||||
});
|
||||
|
||||
target_register
|
||||
}
|
||||
AggFunc::Max => {
|
||||
if args.len() != 1 {
|
||||
anyhow::bail!("Parse error: max bad number of arguments");
|
||||
}
|
||||
let expr = &args[0];
|
||||
let expr_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, select, expr, expr_reg);
|
||||
expr_reg
|
||||
};
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Count,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
AggFunc::GroupConcat => {
|
||||
if args.len() != 1 && args.len() != 2 {
|
||||
anyhow::bail!("Parse error: group_concat bad number of arguments");
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Max,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
AggFunc::Min => {
|
||||
if args.len() != 1 {
|
||||
anyhow::bail!("Parse error: min bad number of arguments");
|
||||
}
|
||||
let expr = &args[0];
|
||||
let expr_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, select, expr, expr_reg);
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Min,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
AggFunc::StringAgg => {
|
||||
if args.len() != 2 {
|
||||
anyhow::bail!("Parse error: string_agg bad number of arguments");
|
||||
}
|
||||
|
||||
let expr_reg = program.alloc_register();
|
||||
let delimiter_reg = program.alloc_register();
|
||||
|
||||
let expr = &args[0];
|
||||
let delimiter_expr: ast::Expr;
|
||||
|
||||
let expr_reg = program.alloc_register();
|
||||
let delimiter_reg = program.alloc_register();
|
||||
|
||||
let expr = &args[0];
|
||||
let delimiter_expr: ast::Expr;
|
||||
|
||||
if args.len() == 2 {
|
||||
match &args[1] {
|
||||
ast::Expr::Id(ident) => {
|
||||
if ident.0.starts_with("\"") {
|
||||
delimiter_expr = ast::Expr::Literal(Literal::String(ident.0.to_string()));
|
||||
anyhow::bail!("Parse error: no such column: \",\" - should this be a string literal in single-quotes?");
|
||||
} else {
|
||||
delimiter_expr = args[1].clone();
|
||||
}
|
||||
},
|
||||
}
|
||||
ast::Expr::Literal(Literal::String(s)) => {
|
||||
delimiter_expr = ast::Expr::Literal(Literal::String(s.to_string()));
|
||||
},
|
||||
}
|
||||
_ => anyhow::bail!("Incorrect delimiter parameter"),
|
||||
};
|
||||
} else {
|
||||
delimiter_expr = ast::Expr::Literal(Literal::String(String::from("\",\"")));
|
||||
}
|
||||
|
||||
if let Err(error) = translate_expr(program, select, expr, expr_reg) {
|
||||
anyhow::bail!(error);
|
||||
}
|
||||
if let Err(error) = translate_expr(program, select, &delimiter_expr, delimiter_reg) {
|
||||
anyhow::bail!(error);
|
||||
}
|
||||
if let Err(error) = translate_expr(program, select, expr, expr_reg) {
|
||||
anyhow::bail!(error);
|
||||
}
|
||||
if let Err(error) = translate_expr(program, select, &delimiter_expr, delimiter_reg)
|
||||
{
|
||||
anyhow::bail!(error);
|
||||
}
|
||||
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: delimiter_reg,
|
||||
func: AggFunc::GroupConcat,
|
||||
});
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: delimiter_reg,
|
||||
func: AggFunc::StringAgg,
|
||||
});
|
||||
|
||||
target_register
|
||||
}
|
||||
AggFunc::Max => {
|
||||
if args.len() != 1 {
|
||||
anyhow::bail!("Parse error: max bad number of arguments");
|
||||
target_register
|
||||
}
|
||||
let expr = &args[0];
|
||||
let expr_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, select, expr, expr_reg);
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Max,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
AggFunc::Min => {
|
||||
if args.len() != 1 {
|
||||
anyhow::bail!("Parse error: min bad number of arguments");
|
||||
AggFunc::Sum => {
|
||||
if args.len() != 1 {
|
||||
anyhow::bail!("Parse error: sum bad number of arguments");
|
||||
}
|
||||
let expr = &args[0];
|
||||
let expr_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, select, expr, expr_reg)?;
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Sum,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
let expr = &args[0];
|
||||
let expr_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, select, expr, expr_reg);
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Min,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
AggFunc::StringAgg => {
|
||||
if args.len() != 2 {
|
||||
anyhow::bail!("Parse error: string_agg bad number of arguments");
|
||||
AggFunc::Total => {
|
||||
if args.len() != 1 {
|
||||
anyhow::bail!("Parse error: total bad number of arguments");
|
||||
}
|
||||
let expr = &args[0];
|
||||
let expr_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, select, expr, expr_reg)?;
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Total,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
|
||||
|
||||
let expr_reg = program.alloc_register();
|
||||
let delimiter_reg = program.alloc_register();
|
||||
|
||||
let expr = &args[0];
|
||||
let delimiter_expr: ast::Expr;
|
||||
|
||||
match &args[1] {
|
||||
ast::Expr::Id(ident) => {
|
||||
if ident.0.starts_with("\"") {
|
||||
anyhow::bail!("Parse error: no such column: \",\" - should this be a string literal in single-quotes?");
|
||||
} else {
|
||||
delimiter_expr = args[1].clone();
|
||||
}
|
||||
},
|
||||
ast::Expr::Literal(Literal::String(s)) => {
|
||||
delimiter_expr = ast::Expr::Literal(Literal::String(s.to_string()));
|
||||
},
|
||||
_ => anyhow::bail!("Incorrect delimiter parameter"),
|
||||
};
|
||||
|
||||
if let Err(error) = translate_expr(program, select, expr, expr_reg) {
|
||||
anyhow::bail!(error);
|
||||
}
|
||||
if let Err(error) = translate_expr(program, select, &delimiter_expr, delimiter_reg) {
|
||||
anyhow::bail!(error);
|
||||
}
|
||||
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: delimiter_reg,
|
||||
func: AggFunc::StringAgg,
|
||||
});
|
||||
|
||||
target_register
|
||||
},
|
||||
AggFunc::Sum => {
|
||||
if args.len() != 1 {
|
||||
anyhow::bail!("Parse error: sum bad number of arguments");
|
||||
}
|
||||
let expr = &args[0];
|
||||
let expr_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, select, expr, expr_reg)?;
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Sum,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
AggFunc::Total => {
|
||||
if args.len() != 1 {
|
||||
anyhow::bail!("Parse error: total bad number of arguments");
|
||||
}
|
||||
let expr = &args[0];
|
||||
let expr_reg = program.alloc_register();
|
||||
let _ = translate_expr(program, select, expr, expr_reg)?;
|
||||
program.emit_insn(Insn::AggStep {
|
||||
acc_reg: target_register,
|
||||
col: expr_reg,
|
||||
delimiter: 0,
|
||||
func: AggFunc::Total,
|
||||
});
|
||||
target_register
|
||||
}
|
||||
};
|
||||
Ok(dest)
|
||||
}
|
||||
@@ -859,10 +932,12 @@ fn translate_pragma(
|
||||
) -> Result<Program> {
|
||||
let mut program = ProgramBuilder::new();
|
||||
let init_label = program.allocate_label();
|
||||
program.add_label(init_label, program.offset());
|
||||
program.emit_insn(Insn::Init {
|
||||
target_pc: init_label,
|
||||
});
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::Init {
|
||||
target_pc: init_label,
|
||||
},
|
||||
init_label,
|
||||
);
|
||||
let start_offset = program.offset();
|
||||
match body {
|
||||
None => {
|
||||
|
||||
81
core/vdbe.rs
81
core/vdbe.rs
@@ -6,7 +6,7 @@ use crate::types::{AggContext, Cursor, CursorResult, OwnedRecord, OwnedValue, Re
|
||||
use anyhow::Result;
|
||||
use std::borrow::BorrowMut;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::collections::BTreeMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub type BranchOffset = i64;
|
||||
@@ -21,6 +21,15 @@ pub enum Insn {
|
||||
Init {
|
||||
target_pc: BranchOffset,
|
||||
},
|
||||
// Set NULL in the given register.
|
||||
Null {
|
||||
dest: usize,
|
||||
},
|
||||
// If the given register is not NULL, jump to the given PC.
|
||||
NotNull {
|
||||
reg: usize,
|
||||
target_pc: BranchOffset,
|
||||
},
|
||||
// Compare two registers and jump to the given PC if they are equal.
|
||||
Eq {
|
||||
lhs: usize,
|
||||
@@ -218,6 +227,7 @@ pub struct ProgramBuilder {
|
||||
// Each lable has a list of InsnRefereces that must
|
||||
// be resolved. Lists are indexed by: label.abs() - 1
|
||||
unresolved_labels: Vec<Vec<InsnReference>>,
|
||||
next_insn_label: Option<BranchOffset>,
|
||||
}
|
||||
|
||||
impl ProgramBuilder {
|
||||
@@ -228,6 +238,7 @@ impl ProgramBuilder {
|
||||
next_free_cursor_id: 0,
|
||||
insns: Vec::new(),
|
||||
unresolved_labels: Vec::new(),
|
||||
next_insn_label: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,14 +264,17 @@ impl ProgramBuilder {
|
||||
cursor
|
||||
}
|
||||
|
||||
pub fn emit_placeholder(&mut self) -> usize {
|
||||
let offset = self.insns.len();
|
||||
self.insns.push(Insn::Halt);
|
||||
offset
|
||||
}
|
||||
|
||||
pub fn emit_insn(&mut self, insn: Insn) {
|
||||
self.insns.push(insn);
|
||||
if let Some(label) = self.next_insn_label {
|
||||
self.next_insn_label = None;
|
||||
self.resolve_label(label, (self.insns.len() - 1) as BranchOffset);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_insn_with_label_dependency(&mut self, insn: Insn, label: BranchOffset) {
|
||||
self.insns.push(insn);
|
||||
self.add_label_dependency(label, (self.insns.len() - 1) as BranchOffset);
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> BranchOffset {
|
||||
@@ -273,11 +287,18 @@ impl ProgramBuilder {
|
||||
self.next_free_label
|
||||
}
|
||||
|
||||
// Effectively a GOTO <next insn> without the need to emit an explicit GOTO instruction.
|
||||
// Useful when you know you need to jump to "the next part", but the exact offset is unknowable
|
||||
// at the time of emitting the instruction.
|
||||
pub fn preassign_label_to_next_insn(&mut self, label: BranchOffset) {
|
||||
self.next_insn_label = Some(label);
|
||||
}
|
||||
|
||||
fn label_to_index(&self, label: BranchOffset) -> usize {
|
||||
(label.abs() - 1) as usize
|
||||
}
|
||||
|
||||
pub fn add_label(&mut self, label: BranchOffset, insn_reference: BranchOffset) {
|
||||
fn add_label_dependency(&mut self, label: BranchOffset, insn_reference: BranchOffset) {
|
||||
assert!(insn_reference >= 0);
|
||||
assert!(label < 0);
|
||||
let label_index = self.label_to_index(label);
|
||||
@@ -382,6 +403,10 @@ impl ProgramBuilder {
|
||||
assert!(*pc_if_next < 0);
|
||||
*pc_if_next = to_offset;
|
||||
}
|
||||
Insn::NotNull { reg, target_pc } => {
|
||||
assert!(*target_pc < 0);
|
||||
*target_pc = to_offset;
|
||||
}
|
||||
_ => {
|
||||
todo!("missing resolve_label for {:?}", insn);
|
||||
}
|
||||
@@ -464,6 +489,22 @@ impl Program {
|
||||
Insn::Init { target_pc } => {
|
||||
state.pc = *target_pc;
|
||||
}
|
||||
Insn::Null { dest } => {
|
||||
state.registers[*dest] = OwnedValue::Null;
|
||||
state.pc += 1;
|
||||
}
|
||||
Insn::NotNull { reg, target_pc } => {
|
||||
let reg = *reg;
|
||||
let target_pc = *target_pc;
|
||||
match &state.registers[reg] {
|
||||
OwnedValue::Null => {
|
||||
state.pc += 1;
|
||||
}
|
||||
_ => {
|
||||
state.pc = target_pc;
|
||||
}
|
||||
}
|
||||
}
|
||||
Insn::Eq {
|
||||
lhs,
|
||||
rhs,
|
||||
@@ -853,8 +894,7 @@ impl Program {
|
||||
}
|
||||
}
|
||||
}
|
||||
AggFunc::GroupConcat |
|
||||
AggFunc::StringAgg => OwnedValue::Agg(Box::new(
|
||||
AggFunc::GroupConcat | AggFunc::StringAgg => OwnedValue::Agg(Box::new(
|
||||
AggContext::GroupConcat(OwnedValue::Text(Rc::new("".to_string()))),
|
||||
)),
|
||||
};
|
||||
@@ -957,8 +997,7 @@ impl Program {
|
||||
}
|
||||
}
|
||||
}
|
||||
AggFunc::GroupConcat |
|
||||
AggFunc::StringAgg => {
|
||||
AggFunc::GroupConcat | AggFunc::StringAgg => {
|
||||
let col = state.registers[*col].clone();
|
||||
let delimiter = state.registers[*delimiter].clone();
|
||||
let OwnedValue::Agg(agg) = state.registers[*acc_reg].borrow_mut()
|
||||
@@ -1110,6 +1149,24 @@ fn insn_to_str(addr: BranchOffset, insn: &Insn, indent: String) -> String {
|
||||
0,
|
||||
format!("Start at {}", target_pc),
|
||||
),
|
||||
Insn::Null { dest } => (
|
||||
"Null",
|
||||
*dest as i32,
|
||||
0,
|
||||
0,
|
||||
OwnedValue::Text(Rc::new("".to_string())),
|
||||
0,
|
||||
format!("r[{}]=NULL", dest),
|
||||
),
|
||||
Insn::NotNull { reg, target_pc } => (
|
||||
"NotNull",
|
||||
*reg as i32,
|
||||
*target_pc as i32,
|
||||
0,
|
||||
OwnedValue::Text(Rc::new("".to_string())),
|
||||
0,
|
||||
format!("r[{}] -> {}", reg, target_pc),
|
||||
),
|
||||
Insn::Eq {
|
||||
lhs,
|
||||
rhs,
|
||||
|
||||
@@ -146,3 +146,31 @@ do_execsql_test where-clause-no-table-unary-true {
|
||||
do_execsql_test where-clause-no-table-unary-false {
|
||||
select 1 where 0;
|
||||
} {}
|
||||
|
||||
do_execsql_test coalesce {
|
||||
select coalesce(NULL, 1);
|
||||
} {1}
|
||||
|
||||
do_execsql_test coalesce-2 {
|
||||
select coalesce(NULL, NULL, 1);
|
||||
} {1}
|
||||
|
||||
do_execsql_test coalesce-null {
|
||||
select coalesce(NULL, NULL, NULL);
|
||||
} {NULL}
|
||||
|
||||
do_execsql_test coalesce-first {
|
||||
select coalesce(1, 2, 3);
|
||||
} {1}
|
||||
|
||||
do_execsql_test coalesce-from-table {
|
||||
select coalesce(NULL, 1) from users limit 1;
|
||||
} {1}
|
||||
|
||||
do_execsql_test coalesce-from-table-column {
|
||||
select coalesce(NULL, age) from users where age = 94 limit 1;
|
||||
} {94}
|
||||
|
||||
do_execsql_test coalesce-from-table-multiple-columns {
|
||||
select coalesce(NULL, age), coalesce(NULL, id) from users where age = 94 limit 1;
|
||||
} {94|1}
|
||||
|
||||
Reference in New Issue
Block a user