Merge pull request #136 from jussisaurio/labeling-system-coalesce

This commit is contained in:
Pekka Enberg
2024-07-13 23:38:13 +03:00
committed by GitHub
5 changed files with 451 additions and 213 deletions

48
Cargo.lock generated
View File

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

View File

@@ -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(()),
}
}
}

View File

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

View File

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

View File

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