use crate::error::{Error, Result};
use crate::exec::eval::Affinity;
use crate::sql::ast::{BinaryOp, Expr, Literal, OrderTerm, ResultColumn, Select};
use crate::value::{Collation, Value};
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)] pub enum Op {
Integer { value: i64, dest: usize },
Real { value: f64, dest: usize },
Str { value: String, dest: usize },
Blob { value: Vec<u8>, dest: usize },
Null { dest: usize },
Arith {
op: BinaryOp,
lhs: usize,
rhs: usize,
dest: usize,
},
Bitwise {
op: BinaryOp,
lhs: usize,
rhs: usize,
dest: usize,
},
Is {
is: bool,
lhs: usize,
rhs: usize,
dest: usize,
la: Option<Affinity>,
ra: Option<Affinity>,
},
Truthy {
want: bool,
not: bool,
operand: usize,
dest: usize,
},
Like {
glob: bool,
lhs: usize,
rhs: usize,
dest: usize,
},
Json {
as_text: bool,
lhs: usize,
rhs: usize,
dest: usize,
},
Func {
name: String,
arg_start: usize,
arg_count: usize,
dest: usize,
},
Concat { lhs: usize, rhs: usize, dest: usize },
Compare {
op: BinaryOp,
lhs: usize,
rhs: usize,
dest: usize,
la: Option<Affinity>,
ra: Option<Affinity>,
coll: Collation,
},
And { lhs: usize, rhs: usize, dest: usize },
Or { lhs: usize, rhs: usize, dest: usize },
Not { reg: usize, dest: usize },
IsNull {
reg: usize,
negated: bool,
dest: usize,
},
Copy { src: usize, dest: usize },
Cast {
reg: usize,
type_name: String,
dest: usize,
},
Goto { target: usize },
IfFalse { reg: usize, target: usize },
Rewind { target: usize },
Column { col: usize, dest: usize },
Next { target: usize },
RewindC { cursor: usize, target: usize },
ColumnC {
cursor: usize,
col: usize,
dest: usize,
},
NextC { cursor: usize, target: usize },
NullRow { cursor: usize },
MarkMatched { cursor: usize },
IfMatched { cursor: usize, target: usize },
DecrJumpZero { reg: usize, target: usize },
IfPosDecr { reg: usize, target: usize },
Negate { reg: usize, dest: usize },
BitNot { reg: usize, dest: usize },
ResultRow { start: usize, count: usize },
DistinctCheck {
start: usize,
count: usize,
target: usize,
},
SorterInsert {
row_start: usize,
row_count: usize,
key_start: usize,
key_count: usize,
},
SorterSort { keys: Vec<SortKey> },
SorterRewind { target: usize },
SorterRow { start: usize, count: usize },
SorterNext { target: usize },
AggStep {
slot: usize,
kind: AggKind,
arg: Option<usize>,
distinct: bool,
filter: Option<usize>,
order: Vec<AggOrderKey>,
},
AggFinal {
slot: usize,
kind: AggKind,
dest: usize,
},
GroupStep {
key_start: usize,
key_count: usize,
aggs: Vec<AggSpec>,
},
GroupEmit {
outputs: Vec<GroupOut>,
agg_kinds: Vec<AggKind>,
},
GroupFinalize {
agg_kinds: Vec<AggKind>,
target: usize,
},
GroupKey { key: usize, dest: usize },
GroupAgg { slot: usize, dest: usize },
GroupNext { target: usize },
Halt,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AggSpec {
pub kind: AggKind,
pub arg: Option<usize>,
pub distinct: bool,
pub filter: Option<usize>,
pub order: Vec<AggOrderKey>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GroupOut {
Key(usize),
Agg(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum AggKind {
CountStar,
Count,
Sum,
Total,
Avg,
Min,
Max,
GroupConcat,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SortKey {
pub descending: bool,
pub nulls_first: Option<bool>,
pub collation: Collation,
}
#[derive(Debug, Clone, Default)]
struct AggAcc {
vals: Vec<Value>,
count: i64,
keys: Vec<Vec<Value>>,
dirs: Vec<(bool, Option<bool>)>,
}
type Group = (Vec<Value>, Vec<AggAcc>);
type AggCallSpec = (AggKind, Option<Expr>, bool, Option<Expr>, Vec<OrderTerm>);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AggOrderKey {
pub reg: usize,
pub descending: bool,
pub nulls_first: Option<bool>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Program {
pub ops: Vec<Op>,
pub n_registers: usize,
pub columns: Vec<String>,
}
impl Program {
pub fn explain_rows(&self) -> Vec<(usize, String, String)> {
self.ops
.iter()
.enumerate()
.map(|(addr, op)| {
let detail = alloc::format!("{op:?}");
let opcode = detail
.split([' ', '{'])
.next()
.unwrap_or("")
.trim()
.to_string();
(addr, opcode, detail)
})
.collect()
}
}
fn is_pure_scalar_fn(name: &str, argc: usize) -> bool {
let n = name.to_ascii_lowercase();
match n.as_str() {
"lower" | "upper" | "length" | "octet_length" | "trim" | "ltrim" | "rtrim" | "substr"
| "substring" | "replace" | "instr" | "hex" | "unhex" | "quote" | "char" | "unicode"
| "concat" | "concat_ws" | "soundex" => true,
"abs" | "round" | "sign" | "ceil" | "ceiling" | "floor" | "trunc" | "exp" | "ln"
| "log" | "log2" | "log10" | "sqrt" | "pow" | "power" | "mod" | "sin" | "cos" | "tan"
| "asin" | "acos" | "atan" | "atan2" | "sinh" | "cosh" | "tanh" | "radians" | "degrees"
| "pi" => true,
"typeof" | "nullif" | "zeroblob" | "likelihood" | "likely" | "unlikely" => true,
"coalesce" | "ifnull" => argc >= 1,
"glob" | "like" => argc == 2,
"json" | "json_valid" | "json_type" | "json_array_length" | "json_extract" | "jsonb" => {
true
}
"min" | "max" => argc >= 2,
_ => false,
}
}
pub fn compile_const_select(sel: &Select) -> Result<Program> {
if sel.from.is_some()
|| sel.where_clause.is_some()
|| !sel.group_by.is_empty()
|| sel.having.is_some()
|| !sel.compound.is_empty()
|| !sel.order_by.is_empty()
|| sel.limit.is_some()
|| sel.offset.is_some()
|| sel.distinct
{
return Err(Error::Unsupported("VDBE spike: only constant SELECT lists"));
}
if sel.columns.is_empty() {
return Err(Error::Unsupported("VDBE spike: empty SELECT list"));
}
let count = sel.columns.len();
let mut c = Compiler {
ops: Vec::new(),
next_reg: count,
columns: Vec::new(),
tables: Vec::new(),
affinities: Vec::new(),
collations: Vec::new(),
bindings: Vec::new(),
forbid_raw_columns: false,
rowid_index: None,
cursor_boundaries: None,
};
let mut columns = Vec::new();
for (i, rc) in sel.columns.iter().enumerate() {
let ResultColumn::Expr {
expr,
alias,
source,
} = rc
else {
return Err(Error::Unsupported("VDBE spike: only scalar result columns"));
};
c.compile_expr_into(expr, i)?;
columns.push(result_label(expr, alias, source, i));
}
c.ops.push(Op::ResultRow { start: 0, count });
c.ops.push(Op::Halt);
Ok(Program {
ops: c.ops,
n_registers: c.next_reg,
columns,
})
}
fn result_label(
expr: &Expr,
alias: &Option<String>,
source: &Option<String>,
idx: usize,
) -> String {
if let Some(a) = alias {
return a.clone();
}
match expr {
Expr::Column { column, .. } => column.clone(),
_ => source
.clone()
.unwrap_or_else(|| alloc::format!("col{}", idx + 1)),
}
}
fn unparen(e: &Expr) -> &Expr {
match e {
Expr::Paren(inner) => unparen(inner),
_ => e,
}
}
fn fold_const_int(e: &Expr) -> Option<i64> {
fn pure(e: &Expr) -> bool {
match e {
Expr::Literal(_) => true,
Expr::Paren(x) | Expr::Cast { expr: x, .. } => pure(x),
Expr::Unary { expr, .. } => pure(expr),
Expr::Binary { left, right, .. } => pure(left) && pure(right),
_ => false,
}
}
if !pure(e) {
return None;
}
let v = crate::exec::eval::eval(
e,
&crate::exec::eval::EvalCtx::rowless(&crate::exec::eval::Params::default()),
)
.ok()?;
Some(crate::exec::eval::to_i64(&v))
}
fn is_aggregate_expr(expr: &Expr) -> bool {
matches!(
expr,
Expr::Function { name, args, star, .. }
if crate::exec::func::is_aggregate_call(name, args.len(), *star)
)
}
fn compile_order_keys(c: &mut Compiler, order: &[OrderTerm]) -> Result<Vec<AggOrderKey>> {
order
.iter()
.map(|t| {
let reg = c.compile_expr(&t.expr)?;
Ok(AggOrderKey {
reg,
descending: t.descending,
nulls_first: t.nulls_first,
})
})
.collect()
}
fn agg_kind_distinct(expr: &Expr) -> Option<AggCallSpec> {
let Expr::Function {
name,
distinct,
args,
star,
filter,
order_by,
over,
} = expr
else {
return None;
};
if over.is_some() {
return None;
}
let filter = filter.as_ref().map(|f| (**f).clone());
let order = order_by.clone();
let arg = args.first().cloned();
let kind = match name.to_ascii_lowercase().as_str() {
"count" if *star => {
return (!*distinct && order.is_empty()).then_some((
AggKind::CountStar,
None,
false,
filter,
Vec::new(),
));
}
"count" if args.len() == 1 => AggKind::Count,
"sum" if args.len() == 1 => AggKind::Sum,
"total" if args.len() == 1 => AggKind::Total,
"avg" if args.len() == 1 => AggKind::Avg,
"min" if args.len() == 1 => AggKind::Min,
"max" if args.len() == 1 => AggKind::Max,
"group_concat" if args.len() == 1 => AggKind::GroupConcat,
_ => return None,
};
if !order.is_empty() && (*distinct || kind != AggKind::GroupConcat) {
return None;
}
Some((kind, arg, *distinct, filter, order))
}
fn compile_aggregate_select(
sel: &Select,
columns: &[String],
tables: &[String],
affinities: &[Affinity],
collations: &[Collation],
projections: &[(Expr, String)],
) -> Result<Program> {
if !sel.order_by.is_empty()
|| sel.limit.is_some()
|| sel.offset.is_some()
|| sel.distinct
|| sel.having.is_some()
{
return Err(Error::Unsupported("VDBE: bare aggregate only"));
}
if collations.iter().any(|c| *c != Collation::Binary) {
return Err(Error::Unsupported(
"VDBE: non-BINARY collation in aggregate",
));
}
let mut slots: Vec<AggCallSpec> = Vec::new();
for (e, _) in projections {
match agg_kind_distinct(e) {
Some(spec) => slots.push(spec),
None => return Err(Error::Unsupported("VDBE: unsupported aggregate")),
}
}
let count = projections.len();
let mut c = Compiler {
ops: Vec::new(),
next_reg: count,
columns: columns.to_vec(),
tables: tables.to_vec(),
affinities: affinities.to_vec(),
collations: collations.to_vec(),
bindings: Vec::new(),
forbid_raw_columns: false,
rowid_index: None,
cursor_boundaries: None,
};
let rewind = c.ops.len();
c.ops.push(Op::Rewind { target: 0 });
let body = c.ops.len();
let skip = match &sel.where_clause {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
for (slot, (kind, arg, distinct, filter, order)) in slots.iter().enumerate() {
let arg_reg = match arg {
Some(expr) => Some(c.compile_expr(expr)?),
None => None,
};
let filter_reg = match filter {
Some(expr) => Some(c.compile_expr(expr)?),
None => None,
};
let order_keys = compile_order_keys(&mut c, order)?;
c.ops.push(Op::AggStep {
slot,
kind: *kind,
arg: arg_reg,
distinct: *distinct,
filter: filter_reg,
order: order_keys,
});
}
let next = c.ops.len();
c.ops.push(Op::Next { target: body });
if let Some(at) = skip {
if let Op::IfFalse { target, .. } = &mut c.ops[at] {
*target = next;
}
}
let end = c.ops.len();
if let Op::Rewind { target } = &mut c.ops[rewind] {
*target = end;
}
for (slot, (kind, _, _, _, _)) in slots.iter().enumerate() {
c.ops.push(Op::AggFinal {
slot,
kind: *kind,
dest: slot,
});
}
c.ops.push(Op::ResultRow { start: 0, count });
c.ops.push(Op::Halt);
Ok(Program {
ops: c.ops,
n_registers: c.next_reg,
columns: projections.iter().map(|(_, l)| l.clone()).collect(),
})
}
fn collect_aggregates(expr: &Expr, out: &mut Vec<Expr>) {
if is_aggregate_expr(expr) {
if !out.iter().any(|e| e == expr) {
out.push(expr.clone());
}
return;
}
match expr {
Expr::Paren(inner)
| Expr::Unary { expr: inner, .. }
| Expr::IsNull { expr: inner, .. }
| Expr::Cast { expr: inner, .. } => collect_aggregates(inner, out),
Expr::Binary { left, right, .. } => {
collect_aggregates(left, out);
collect_aggregates(right, out);
}
Expr::Function { args, .. } => {
for a in args {
collect_aggregates(a, out);
}
}
Expr::Case {
operand,
when_then,
else_result,
} => {
if let Some(o) = operand {
collect_aggregates(o, out);
}
for (w, t) in when_then {
collect_aggregates(w, out);
collect_aggregates(t, out);
}
if let Some(e) = else_result {
collect_aggregates(e, out);
}
}
_ => {}
}
}
fn emit_group_fold(
c: &mut Compiler,
sel: &Select,
group_cols: &[usize],
agg_specs: &[AggCallSpec],
) -> Result<()> {
let bounds = c.cursor_boundaries.clone();
let key_start = c.next_reg;
for _ in group_cols {
c.alloc();
}
let mut rewind_at: Vec<usize> = Vec::new();
let mut single_rewind: Option<usize> = None;
match &bounds {
Some(b) => {
rewind_at = alloc::vec![0usize; b.len()];
for (i, slot) in rewind_at.iter_mut().enumerate() {
*slot = c.ops.len();
c.ops.push(Op::RewindC {
cursor: i,
target: 0,
});
}
}
None => {
single_rewind = Some(c.ops.len());
c.ops.push(Op::Rewind { target: 0 });
}
}
let body = c.ops.len();
let skip = match &sel.where_clause {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
for (k, &ci) in group_cols.iter().enumerate() {
match &bounds {
Some(b) => {
let (cursor, col) = Compiler::cursor_of(b, ci);
c.ops.push(Op::ColumnC {
cursor,
col,
dest: key_start + k,
});
}
None => c.ops.push(Op::Column {
col: ci,
dest: key_start + k,
}),
}
}
let mut aggs: Vec<AggSpec> = Vec::new();
for (kind, arg, distinct, filter, order) in agg_specs {
let arg_reg = match arg {
Some(expr) => Some(c.compile_expr(expr)?),
None => None,
};
let filter_reg = match filter {
Some(expr) => Some(c.compile_expr(expr)?),
None => None,
};
let order_keys = compile_order_keys(c, order)?;
aggs.push(AggSpec {
kind: *kind,
arg: arg_reg,
distinct: *distinct,
filter: filter_reg,
order: order_keys,
});
}
c.ops.push(Op::GroupStep {
key_start,
key_count: group_cols.len(),
aggs,
});
match &bounds {
Some(b) => {
let n = b.len();
let mut next_at = alloc::vec![0usize; n];
for i in (0..n).rev() {
next_at[i] = c.ops.len();
let target = if i == n - 1 { body } else { rewind_at[i + 1] };
c.ops.push(Op::NextC { cursor: i, target });
}
let end = c.ops.len();
for i in 0..n {
let target = if i == 0 { end } else { next_at[i - 1] };
if let Op::RewindC { target: t, .. } = &mut c.ops[rewind_at[i]] {
*t = target;
}
}
if let Some(at) = skip {
if let Op::IfFalse { target, .. } = &mut c.ops[at] {
*target = next_at[n - 1];
}
}
}
None => {
let next = c.ops.len();
c.ops.push(Op::Next { target: body });
if let Some(at) = skip {
if let Op::IfFalse { target, .. } = &mut c.ops[at] {
*target = next;
}
}
let end = c.ops.len();
if let Some(rw) = single_rewind {
if let Op::Rewind { target } = &mut c.ops[rw] {
*target = end;
}
}
}
}
Ok(())
}
fn compile_group_select(
sel: &Select,
columns: &[String],
tables: &[String],
affinities: &[Affinity],
collations: &[Collation],
projections: &[(Expr, String)],
boundaries: Option<&[usize]>,
) -> Result<Program> {
if sel.distinct {
return Err(Error::Unsupported("VDBE: GROUP BY + DISTINCT"));
}
if collations.iter().any(|c| *c != Collation::Binary) {
return Err(Error::Unsupported("VDBE: non-BINARY collation in GROUP BY"));
}
let has_having = sel.having.is_some();
let has_order = !sel.order_by.is_empty();
let has_limit = sel.limit.is_some() || sel.offset.is_some();
let mut c = Compiler {
ops: Vec::new(),
next_reg: 0,
columns: columns.to_vec(),
tables: tables.to_vec(),
affinities: affinities.to_vec(),
collations: collations.to_vec(),
bindings: Vec::new(),
forbid_raw_columns: false,
rowid_index: None,
cursor_boundaries: boundaries.map(|b| b.to_vec()),
};
let mut group_cols: Vec<usize> = Vec::new();
for g in &sel.group_by {
match g {
Expr::Column { table, column } => {
group_cols.push(c.resolve_column(table.as_deref(), column)?)
}
_ => return Err(Error::Unsupported("VDBE: GROUP BY column refs only")),
}
}
if !has_having && !has_order && !has_limit {
return compile_group_emit(
sel,
columns,
tables,
affinities,
collations,
projections,
&group_cols,
boundaries,
);
}
let mut agg_exprs: Vec<Expr> = Vec::new();
for (e, _) in projections {
collect_aggregates(e, &mut agg_exprs);
}
if let Some(h) = &sel.having {
collect_aggregates(h, &mut agg_exprs);
}
for term in &sel.order_by {
collect_aggregates(&term.expr, &mut agg_exprs);
}
let mut agg_specs: Vec<AggCallSpec> = Vec::new();
for e in &agg_exprs {
match agg_kind_distinct(e) {
Some(spec) => agg_specs.push(spec),
None => return Err(Error::Unsupported("VDBE: unsupported aggregate")),
}
}
emit_group_fold(&mut c, sel, &group_cols, &agg_specs)?;
let gkey_start = c.next_reg;
for _ in &group_cols {
c.alloc();
}
let gagg_start = c.next_reg;
for _ in &agg_specs {
c.alloc();
}
for (k, &ci) in group_cols.iter().enumerate() {
c.bindings.push((
Expr::Column {
table: None,
column: columns[ci].clone(),
},
gkey_start + k,
));
if let Some(t) = tables.get(ci) {
c.bindings.push((
Expr::Column {
table: Some(t.clone()),
column: columns[ci].clone(),
},
gkey_start + k,
));
}
}
for (j, e) in agg_exprs.iter().enumerate() {
c.bindings.push((e.clone(), gagg_start + j));
}
let count = projections.len();
let out_start = c.next_reg;
for _ in 0..count {
c.alloc();
}
let limit_reg = match &sel.limit {
None => None,
Some(e) => match fold_const_int(e) {
Some(n) if n < 0 => None,
Some(n) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: n, dest: r });
Some(r)
}
None => return Err(Error::Unsupported("VDBE: only constant integer LIMIT")),
},
};
let offset_reg = match &sel.offset {
None => None,
Some(e) => match fold_const_int(e) {
Some(n) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: n, dest: r });
Some(r)
}
None => return Err(Error::Unsupported("VDBE: only constant integer OFFSET")),
},
};
let mut key_specs: Vec<(Expr, SortKey)> = Vec::new();
for term in &sel.order_by {
let expr = match &term.expr {
Expr::Literal(Literal::Integer(k)) if *k >= 1 && (*k as usize) <= count => {
projections[*k as usize - 1].0.clone()
}
Expr::Literal(Literal::Integer(_)) => {
return Err(Error::Unsupported("VDBE: ORDER BY ordinal out of range"))
}
Expr::Column {
table: None,
column,
} if !columns.iter().any(|c| c.eq_ignore_ascii_case(column))
&& projections
.iter()
.any(|(_, l)| l.eq_ignore_ascii_case(column)) =>
{
projections
.iter()
.find(|(_, l)| l.eq_ignore_ascii_case(column))
.map(|(e, _)| e.clone())
.unwrap()
}
other => other.clone(),
};
let collation = c.col_collation(&expr).unwrap_or_default();
key_specs.push((
expr,
SortKey {
descending: term.descending,
nulls_first: term.nulls_first,
collation,
},
));
}
let key_start2 = c.next_reg;
for _ in &key_specs {
c.alloc();
}
let limit_skip = limit_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfFalse { reg: r, target: 0 });
at
});
let gfin = c.ops.len();
c.ops.push(Op::GroupFinalize {
agg_kinds: agg_specs.iter().map(|(k, _, _, _, _)| *k).collect(),
target: 0,
});
let gbody = c.ops.len();
for k in 0..group_cols.len() {
c.ops.push(Op::GroupKey {
key: k,
dest: gkey_start + k,
});
}
for j in 0..agg_specs.len() {
c.ops.push(Op::GroupAgg {
slot: j,
dest: gagg_start + j,
});
}
c.forbid_raw_columns = true;
for (i, (expr, _)) in projections.iter().enumerate() {
c.compile_expr_into(expr, out_start + i)?;
}
for (i, (expr, label)) in projections.iter().enumerate() {
let is_bare_col = matches!(expr, Expr::Column { .. });
if !is_bare_col && !columns.iter().any(|c| c.eq_ignore_ascii_case(label)) {
c.bindings.push((
Expr::Column {
table: None,
column: label.clone(),
},
out_start + i,
));
}
}
let having_skip = match &sel.having {
Some(h) => {
let preg = c.compile_expr(h)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
let mut limit_done = None;
let mut offset_skip = None;
if has_order {
for (j, (expr, _)) in key_specs.iter().enumerate() {
c.compile_expr_into(expr, key_start2 + j)?;
}
c.ops.push(Op::SorterInsert {
row_start: out_start,
row_count: count,
key_start: key_start2,
key_count: key_specs.len(),
});
} else {
offset_skip = offset_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfPosDecr { reg: r, target: 0 });
at
});
c.ops.push(Op::ResultRow {
start: out_start,
count,
});
if let Some(r) = limit_reg {
limit_done = Some(c.ops.len());
c.ops.push(Op::DecrJumpZero { reg: r, target: 0 });
}
}
let gnext = c.ops.len();
c.ops.push(Op::GroupNext { target: gbody });
if let Some(at) = having_skip {
if let Op::IfFalse { target, .. } = &mut c.ops[at] {
*target = gnext;
}
}
if let Some(at) = offset_skip {
if let Op::IfPosDecr { target, .. } = &mut c.ops[at] {
*target = gnext;
}
}
let gend = c.ops.len();
if let Op::GroupFinalize { target, .. } = &mut c.ops[gfin] {
*target = gend;
}
if let Some(at) = limit_done {
if let Op::DecrJumpZero { target, .. } = &mut c.ops[at] {
*target = gend;
}
}
if has_order {
c.ops.push(Op::SorterSort {
keys: key_specs.iter().map(|(_, k)| k.clone()).collect(),
});
let srewind = c.ops.len();
c.ops.push(Op::SorterRewind { target: 0 });
let ebody = c.ops.len();
let eoffset = offset_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfPosDecr { reg: r, target: 0 });
at
});
c.ops.push(Op::SorterRow {
start: out_start,
count,
});
c.ops.push(Op::ResultRow {
start: out_start,
count,
});
let elimit = limit_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::DecrJumpZero { reg: r, target: 0 });
at
});
let snext = c.ops.len();
c.ops.push(Op::SorterNext { target: ebody });
let eend = c.ops.len();
if let Op::SorterRewind { target } = &mut c.ops[srewind] {
*target = eend;
}
if let Some(at) = eoffset {
if let Op::IfPosDecr { target, .. } = &mut c.ops[at] {
*target = snext;
}
}
if let Some(at) = elimit {
if let Op::DecrJumpZero { target, .. } = &mut c.ops[at] {
*target = eend;
}
}
}
let final_end = c.ops.len();
if let Some(at) = limit_skip {
if let Op::IfFalse { target, .. } = &mut c.ops[at] {
*target = final_end;
}
}
c.ops.push(Op::Halt);
Ok(Program {
ops: c.ops,
n_registers: c.next_reg,
columns: projections.iter().map(|(_, l)| l.clone()).collect(),
})
}
#[allow(clippy::too_many_arguments)] fn compile_group_emit(
sel: &Select,
columns: &[String],
tables: &[String],
affinities: &[Affinity],
collations: &[Collation],
projections: &[(Expr, String)],
group_cols: &[usize],
boundaries: Option<&[usize]>,
) -> Result<Program> {
let mut c = Compiler {
ops: Vec::new(),
next_reg: 0,
columns: columns.to_vec(),
tables: tables.to_vec(),
affinities: affinities.to_vec(),
collations: collations.to_vec(),
bindings: Vec::new(),
forbid_raw_columns: false,
rowid_index: None,
cursor_boundaries: boundaries.map(|b| b.to_vec()),
};
let mut outputs: Vec<GroupOut> = Vec::new();
let mut agg_specs: Vec<AggCallSpec> = Vec::new();
for (e, _) in projections {
if is_aggregate_expr(e) {
match agg_kind_distinct(e) {
Some(spec) => {
outputs.push(GroupOut::Agg(agg_specs.len()));
agg_specs.push(spec);
}
None => return Err(Error::Unsupported("VDBE: unsupported aggregate")),
}
} else if let Expr::Column { table, column } = e {
let ci = c.resolve_column(table.as_deref(), column)?;
match group_cols.iter().position(|&g| g == ci) {
Some(k) => outputs.push(GroupOut::Key(k)),
None => return Err(Error::Unsupported("VDBE: non-grouped column")),
}
} else {
return Err(Error::Unsupported(
"VDBE: GROUP BY output must be key or aggregate",
));
}
}
emit_group_fold(&mut c, sel, group_cols, &agg_specs)?;
c.ops.push(Op::GroupEmit {
outputs,
agg_kinds: agg_specs.iter().map(|(k, _, _, _, _)| *k).collect(),
});
c.ops.push(Op::Halt);
Ok(Program {
ops: c.ops,
n_registers: c.next_reg,
columns: projections.iter().map(|(_, l)| l.clone()).collect(),
})
}
pub fn compile_table_select(
sel: &Select,
columns: &[String],
tables: &[String],
affinities: &[Affinity],
collations: &[Collation],
rowid: bool,
) -> Result<Program> {
if !sel.compound.is_empty() {
return Err(Error::Unsupported("VDBE: only plain table projections"));
}
let projections = expand_projections(sel, columns, tables)?;
if matches!(&sel.limit, Some(Expr::Literal(Literal::Integer(0)))) {
return Ok(Program {
ops: alloc::vec![Op::Halt],
n_registers: projections.len(),
columns: projections.iter().map(|(_, l)| l.clone()).collect(),
});
}
if !sel.group_by.is_empty() {
return compile_group_select(
sel,
columns,
tables,
affinities,
collations,
&projections,
None,
);
}
if projections.iter().any(|(e, _)| is_aggregate_expr(e)) {
return compile_aggregate_select(
sel,
columns,
tables,
affinities,
collations,
&projections,
);
}
if sel.having.is_some() {
return Err(Error::Unsupported("VDBE: HAVING without GROUP BY"));
}
let count = projections.len();
let mut affinities = affinities.to_vec();
let mut collations = collations.to_vec();
let rowid_index = if rowid {
affinities.push(Affinity::Integer);
collations.push(Collation::Binary);
Some(columns.len())
} else {
None
};
let mut c = Compiler {
ops: Vec::new(),
next_reg: count,
columns: columns.to_vec(),
tables: tables.to_vec(),
affinities,
collations,
bindings: Vec::new(),
forbid_raw_columns: false,
rowid_index,
cursor_boundaries: None,
};
let limit_reg = match &sel.limit {
None => None,
Some(e) => match fold_const_int(e) {
Some(n) if n < 0 => None,
Some(n) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: n, dest: r });
Some(r)
}
None => return Err(Error::Unsupported("VDBE: only constant integer LIMIT")),
},
};
let offset_reg = match &sel.offset {
None => None,
Some(e) => match fold_const_int(e) {
Some(n) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: n, dest: r });
Some(r)
}
None => return Err(Error::Unsupported("VDBE: only constant integer OFFSET")),
},
};
let ordering = !sel.order_by.is_empty();
let key_specs = if ordering {
build_sort_keys(&c, sel, columns, &projections, count)?
} else {
Vec::new()
};
let key_start = c.next_reg;
for _ in &key_specs {
c.alloc();
}
let rewind = c.ops.len();
c.ops.push(Op::Rewind { target: 0 });
let limit_skip = if ordering {
None
} else {
limit_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfFalse { reg: r, target: 0 });
at
})
};
let body = c.ops.len();
let skip = match &sel.where_clause {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
for (i, (expr, _)) in projections.iter().enumerate() {
c.compile_expr_into(expr, i)?;
}
if sel.distinct && c.collations.iter().any(|cl| *cl != Collation::Binary) {
return Err(Error::Unsupported(
"VDBE: non-BINARY collation with DISTINCT",
));
}
let distinct_skip = if sel.distinct {
let at = c.ops.len();
c.ops.push(Op::DistinctCheck {
start: 0,
count,
target: 0,
});
Some(at)
} else {
None
};
let offset_skip = if ordering {
None
} else {
offset_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfPosDecr { reg: r, target: 0 });
at
})
};
let mut limit_done = None;
if ordering {
for (j, (expr, _)) in key_specs.iter().enumerate() {
c.compile_expr_into(expr, key_start + j)?;
}
c.ops.push(Op::SorterInsert {
row_start: 0,
row_count: count,
key_start,
key_count: key_specs.len(),
});
} else {
c.ops.push(Op::ResultRow { start: 0, count });
if let Some(r) = limit_reg {
limit_done = Some(c.ops.len());
c.ops.push(Op::DecrJumpZero { reg: r, target: 0 });
}
}
let next = c.ops.len();
c.ops.push(Op::Next { target: body });
if let Some(at) = skip {
if let Op::IfFalse { target, .. } = &mut c.ops[at] {
*target = next; }
}
if let Some(at) = offset_skip {
if let Op::IfPosDecr { target, .. } = &mut c.ops[at] {
*target = next; }
}
if let Some(at) = distinct_skip {
if let Op::DistinctCheck { target, .. } = &mut c.ops[at] {
*target = next; }
}
let end = c.ops.len();
if let Op::Rewind { target } = &mut c.ops[rewind] {
*target = end;
}
if let Some(at) = limit_skip {
if let Op::IfFalse { target, .. } = &mut c.ops[at] {
*target = end;
}
}
if let Some(at) = limit_done {
if let Op::DecrJumpZero { target, .. } = &mut c.ops[at] {
*target = end;
}
}
if ordering {
c.ops.push(Op::SorterSort {
keys: key_specs.iter().map(|(_, k)| k.clone()).collect(),
});
let srewind = c.ops.len();
c.ops.push(Op::SorterRewind { target: 0 });
let ebody = c.ops.len();
let eoffset = offset_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfPosDecr { reg: r, target: 0 });
at
});
c.ops.push(Op::SorterRow { start: 0, count });
c.ops.push(Op::ResultRow { start: 0, count });
let elimit = limit_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::DecrJumpZero { reg: r, target: 0 });
at
});
let snext = c.ops.len();
c.ops.push(Op::SorterNext { target: ebody });
let eend = c.ops.len();
if let Op::SorterRewind { target } = &mut c.ops[srewind] {
*target = eend;
}
if let Some(at) = eoffset {
if let Op::IfPosDecr { target, .. } = &mut c.ops[at] {
*target = snext; }
}
if let Some(at) = elimit {
if let Op::DecrJumpZero { target, .. } = &mut c.ops[at] {
*target = eend;
}
}
}
c.ops.push(Op::Halt);
Ok(Program {
ops: c.ops,
n_registers: c.next_reg,
columns: projections.into_iter().map(|(_, l)| l).collect(),
})
}
fn expand_projections(
sel: &Select,
columns: &[String],
tables: &[String],
) -> Result<Vec<(Expr, String)>> {
let mut projections: Vec<(Expr, String)> = Vec::new();
for rc in &sel.columns {
match rc {
ResultColumn::Wildcard => {
for name in columns {
projections.push((
Expr::Column {
table: None,
column: name.clone(),
},
name.clone(),
));
}
}
ResultColumn::Expr {
expr,
alias,
source,
} => {
let label = result_label(expr, alias, source, projections.len());
projections.push((expr.clone(), label));
}
ResultColumn::TableWildcard(q) => {
let mut any = false;
for (i, name) in columns.iter().enumerate() {
if tables.get(i).is_some_and(|t| t.eq_ignore_ascii_case(q)) {
projections.push((
Expr::Column {
table: Some(q.clone()),
column: name.clone(),
},
name.clone(),
));
any = true;
}
}
if !any {
return Err(Error::Unsupported("VDBE: unknown table.* qualifier"));
}
}
}
}
if projections.is_empty() {
return Err(Error::Unsupported("VDBE: empty projection"));
}
Ok(projections)
}
fn build_sort_keys(
c: &Compiler,
sel: &Select,
columns: &[String],
projections: &[(Expr, String)],
count: usize,
) -> Result<Vec<(Expr, SortKey)>> {
let mut key_specs: Vec<(Expr, SortKey)> = Vec::new();
for term in &sel.order_by {
let expr = match &term.expr {
Expr::Literal(Literal::Integer(k)) if *k >= 1 && (*k as usize) <= count => {
projections[*k as usize - 1].0.clone()
}
Expr::Literal(Literal::Integer(_)) => {
return Err(Error::Unsupported("VDBE: ORDER BY ordinal out of range"))
}
Expr::Column {
table: None,
column,
} if !columns.iter().any(|c| c.eq_ignore_ascii_case(column))
&& projections
.iter()
.any(|(_, l)| l.eq_ignore_ascii_case(column)) =>
{
projections
.iter()
.find(|(_, l)| l.eq_ignore_ascii_case(column))
.map(|(e, _)| e.clone())
.unwrap()
}
other => other.clone(),
};
let collation = c.col_collation(&expr).unwrap_or_default();
key_specs.push((
expr,
SortKey {
descending: term.descending,
nulls_first: term.nulls_first,
collation,
},
));
}
Ok(key_specs)
}
pub fn compile_join2(
sel: &Select,
columns: &[String],
tables: &[String],
affinities: &[Affinity],
collations: &[Collation],
boundaries: &[usize],
) -> Result<Program> {
let n = boundaries.len();
debug_assert!(n >= 2 && boundaries[n - 1] == columns.len());
if !sel.compound.is_empty() || !sel.group_by.is_empty() || sel.having.is_some() {
return Err(Error::Unsupported("VDBE: join shape not nested-loopable"));
}
if sel.distinct && collations.iter().any(|cl| *cl != Collation::Binary) {
return Err(Error::Unsupported(
"VDBE: non-BINARY collation with DISTINCT",
));
}
let projections = expand_projections(sel, columns, tables)?;
if projections.iter().any(|(e, _)| is_aggregate_expr(e)) {
return Err(Error::Unsupported("VDBE: aggregate over a join"));
}
let count = projections.len();
if matches!(&sel.limit, Some(Expr::Literal(Literal::Integer(0)))) {
return Ok(Program {
ops: alloc::vec![Op::Halt],
n_registers: count,
columns: projections.into_iter().map(|(_, l)| l).collect(),
});
}
let mut c = Compiler {
ops: Vec::new(),
next_reg: count,
columns: columns.to_vec(),
tables: tables.to_vec(),
affinities: affinities.to_vec(),
collations: collations.to_vec(),
bindings: Vec::new(),
forbid_raw_columns: false,
rowid_index: None,
cursor_boundaries: Some(boundaries.to_vec()),
};
let limit_reg = match &sel.limit {
None => None,
Some(e) => match fold_const_int(e) {
Some(n) if n < 0 => None,
Some(n) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: n, dest: r });
Some(r)
}
None => return Err(Error::Unsupported("VDBE: only constant integer LIMIT")),
},
};
let offset_reg = match &sel.offset {
None => None,
Some(e) => match fold_const_int(e) {
Some(n) if n <= 0 => None,
Some(n) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: n, dest: r });
Some(r)
}
None => return Err(Error::Unsupported("VDBE: only constant integer OFFSET")),
},
};
let ordering = !sel.order_by.is_empty();
let key_specs = if ordering {
build_sort_keys(&c, sel, columns, &projections, count)?
} else {
Vec::new()
};
let key_start = c.next_reg;
for _ in &key_specs {
c.alloc();
}
let mut rewind_at = alloc::vec![0usize; n];
for (i, slot) in rewind_at.iter_mut().enumerate() {
*slot = c.ops.len();
c.ops.push(Op::RewindC {
cursor: i,
target: 0,
});
}
let body = c.ops.len();
let skip = match &sel.where_clause {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
for (i, (expr, _)) in projections.iter().enumerate() {
c.compile_expr_into(expr, i)?;
}
let distinct_skip = if sel.distinct {
let at = c.ops.len();
c.ops.push(Op::DistinctCheck {
start: 0,
count,
target: 0,
});
Some(at)
} else {
None
};
let (offset_skip, limit_done) = if ordering {
for (j, (expr, _)) in key_specs.iter().enumerate() {
c.compile_expr_into(expr, key_start + j)?;
}
c.ops.push(Op::SorterInsert {
row_start: 0,
row_count: count,
key_start,
key_count: key_specs.len(),
});
(None, None)
} else {
let offset_skip = offset_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfPosDecr { reg: r, target: 0 });
at
});
c.ops.push(Op::ResultRow { start: 0, count });
let limit_done = limit_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::DecrJumpZero { reg: r, target: 0 });
at
});
(offset_skip, limit_done)
};
let mut next_at = alloc::vec![0usize; n];
for i in (0..n).rev() {
next_at[i] = c.ops.len();
let target = if i == n - 1 { body } else { rewind_at[i + 1] };
c.ops.push(Op::NextC { cursor: i, target });
}
let elimit = if ordering {
c.ops.push(Op::SorterSort {
keys: key_specs.iter().map(|(_, k)| k.clone()).collect(),
});
let srewind = c.ops.len();
c.ops.push(Op::SorterRewind { target: 0 });
let ebody = c.ops.len();
let eoffset = offset_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfPosDecr { reg: r, target: 0 });
at
});
c.ops.push(Op::SorterRow { start: 0, count });
c.ops.push(Op::ResultRow { start: 0, count });
let elimit = limit_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::DecrJumpZero { reg: r, target: 0 });
at
});
let snext = c.ops.len();
c.ops.push(Op::SorterNext { target: ebody });
let eend = c.ops.len();
if let Op::SorterRewind { target } = &mut c.ops[srewind] {
*target = eend;
}
if let Some(at) = eoffset {
if let Op::IfPosDecr { target, .. } = &mut c.ops[at] {
*target = snext;
}
}
elimit
} else {
None
};
let end = c.ops.len();
c.ops.push(Op::Halt);
for i in 0..n {
let target = if i == 0 { end } else { next_at[i - 1] };
if let Op::RewindC { target: t, .. } = &mut c.ops[rewind_at[i]] {
*t = target;
}
}
let inner_next = next_at[n - 1];
if let Some(at) = skip {
if let Op::IfFalse { target, .. } = &mut c.ops[at] {
*target = inner_next;
}
}
if let Some(at) = distinct_skip {
if let Op::DistinctCheck { target, .. } = &mut c.ops[at] {
*target = inner_next; }
}
if let Some(at) = offset_skip {
if let Op::IfPosDecr { target, .. } = &mut c.ops[at] {
*target = inner_next;
}
}
if let Some(at) = limit_done {
if let Op::DecrJumpZero { target, .. } = &mut c.ops[at] {
*target = end;
}
}
if let Some(at) = elimit {
if let Op::DecrJumpZero { target, .. } = &mut c.ops[at] {
*target = end;
}
}
Ok(Program {
ops: c.ops,
n_registers: c.next_reg,
columns: projections.into_iter().map(|(_, l)| l).collect(),
})
}
pub fn compile_aggregate_join(
sel: &Select,
columns: &[String],
tables: &[String],
affinities: &[Affinity],
collations: &[Collation],
boundaries: &[usize],
) -> Result<Program> {
let n = boundaries.len();
debug_assert!(n >= 2 && boundaries[n - 1] == columns.len());
if !sel.compound.is_empty()
|| !sel.group_by.is_empty()
|| sel.having.is_some()
|| !sel.order_by.is_empty()
|| sel.limit.is_some()
|| sel.offset.is_some()
|| sel.distinct
{
return Err(Error::Unsupported("VDBE: bare aggregate join only"));
}
if collations.iter().any(|cl| *cl != Collation::Binary) {
return Err(Error::Unsupported(
"VDBE: non-BINARY collation in aggregate",
));
}
let projections = expand_projections(sel, columns, tables)?;
let mut slots: Vec<AggCallSpec> = Vec::new();
for (e, _) in &projections {
match agg_kind_distinct(e) {
Some(spec) => slots.push(spec),
None => return Err(Error::Unsupported("VDBE: unsupported aggregate")),
}
}
let count = projections.len();
let mut c = Compiler {
ops: Vec::new(),
next_reg: count,
columns: columns.to_vec(),
tables: tables.to_vec(),
affinities: affinities.to_vec(),
collations: collations.to_vec(),
bindings: Vec::new(),
forbid_raw_columns: false,
rowid_index: None,
cursor_boundaries: Some(boundaries.to_vec()),
};
let mut rewind_at = alloc::vec![0usize; n];
for (i, slot) in rewind_at.iter_mut().enumerate() {
*slot = c.ops.len();
c.ops.push(Op::RewindC {
cursor: i,
target: 0,
});
}
let body = c.ops.len();
let skip = match &sel.where_clause {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
for (slot, (kind, arg, distinct, filter, order)) in slots.iter().enumerate() {
let arg_reg = match arg {
Some(expr) => Some(c.compile_expr(expr)?),
None => None,
};
let filter_reg = match filter {
Some(expr) => Some(c.compile_expr(expr)?),
None => None,
};
let order_keys = compile_order_keys(&mut c, order)?;
c.ops.push(Op::AggStep {
slot,
kind: *kind,
arg: arg_reg,
distinct: *distinct,
filter: filter_reg,
order: order_keys,
});
}
let mut next_at = alloc::vec![0usize; n];
for i in (0..n).rev() {
next_at[i] = c.ops.len();
let target = if i == n - 1 { body } else { rewind_at[i + 1] };
c.ops.push(Op::NextC { cursor: i, target });
}
let end = c.ops.len();
for i in 0..n {
let target = if i == 0 { end } else { next_at[i - 1] };
if let Op::RewindC { target: t, .. } = &mut c.ops[rewind_at[i]] {
*t = target;
}
}
if let Some(at) = skip {
if let Op::IfFalse { target, .. } = &mut c.ops[at] {
*target = next_at[n - 1];
}
}
for (slot, (kind, _, _, _, _)) in slots.iter().enumerate() {
c.ops.push(Op::AggFinal {
slot,
kind: *kind,
dest: slot,
});
}
c.ops.push(Op::ResultRow { start: 0, count });
c.ops.push(Op::Halt);
Ok(Program {
ops: c.ops,
n_registers: c.next_reg,
columns: projections.into_iter().map(|(_, l)| l).collect(),
})
}
pub fn compile_group_join(
sel: &Select,
columns: &[String],
tables: &[String],
affinities: &[Affinity],
collations: &[Collation],
boundaries: &[usize],
) -> Result<Program> {
let n = boundaries.len();
debug_assert!(n >= 2 && boundaries[n - 1] == columns.len());
if !sel.compound.is_empty() || sel.group_by.is_empty() {
return Err(Error::Unsupported("VDBE: GROUP BY join requires GROUP BY"));
}
let projections = expand_projections(sel, columns, tables)?;
compile_group_select(
sel,
columns,
tables,
affinities,
collations,
&projections,
Some(boundaries),
)
}
#[allow(clippy::too_many_arguments)]
pub fn compile_left_join2(
sel: &Select,
columns: &[String],
tables: &[String],
affinities: &[Affinity],
collations: &[Collation],
n_left: usize,
on: &Option<Expr>,
) -> Result<Program> {
if !sel.compound.is_empty() || !sel.group_by.is_empty() || sel.having.is_some() {
return Err(Error::Unsupported(
"VDBE: left-join shape not nested-loopable",
));
}
if sel.distinct && collations.iter().any(|cl| *cl != Collation::Binary) {
return Err(Error::Unsupported(
"VDBE: non-BINARY collation with DISTINCT",
));
}
let projections = expand_projections(sel, columns, tables)?;
if projections.iter().any(|(e, _)| is_aggregate_expr(e)) {
return Err(Error::Unsupported("VDBE: aggregate over a left join"));
}
let count = projections.len();
if matches!(&sel.limit, Some(Expr::Literal(Literal::Integer(0)))) {
return Ok(Program {
ops: alloc::vec![Op::Halt],
n_registers: count,
columns: projections.into_iter().map(|(_, l)| l).collect(),
});
}
let mut c = Compiler {
ops: Vec::new(),
next_reg: count,
columns: columns.to_vec(),
tables: tables.to_vec(),
affinities: affinities.to_vec(),
collations: collations.to_vec(),
bindings: Vec::new(),
forbid_raw_columns: false,
rowid_index: None,
cursor_boundaries: Some(alloc::vec![n_left, columns.len()]),
};
let limit_reg = match &sel.limit {
None => None,
Some(e) => match fold_const_int(e) {
Some(n) if n < 0 => None,
Some(n) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: n, dest: r });
Some(r)
}
None => return Err(Error::Unsupported("VDBE: only constant integer LIMIT")),
},
};
let offset_reg = match &sel.offset {
None => None,
Some(e) => match fold_const_int(e) {
Some(n) if n <= 0 => None,
Some(n) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: n, dest: r });
Some(r)
}
None => return Err(Error::Unsupported("VDBE: only constant integer OFFSET")),
},
};
let matched = c.alloc();
let ordering = !sel.order_by.is_empty();
let key_specs = if ordering {
build_sort_keys(&c, sel, columns, &projections, count)?
} else {
Vec::new()
};
let key_start = c.next_reg;
for _ in &key_specs {
c.alloc();
}
let rewind0 = c.ops.len();
c.ops.push(Op::RewindC {
cursor: 0,
target: 0,
});
let loop0 = c.ops.len();
c.ops.push(Op::Integer {
value: 0,
dest: matched,
});
let rewind1 = c.ops.len();
c.ops.push(Op::RewindC {
cursor: 1,
target: 0,
});
let body = c.ops.len();
let on_skip = match on {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
c.ops.push(Op::Integer {
value: 1,
dest: matched,
});
let where_skip_m = match &sel.where_clause {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
for (i, (expr, _)) in projections.iter().enumerate() {
c.compile_expr_into(expr, i)?;
}
let distinct_skip_m = if sel.distinct {
let at = c.ops.len();
c.ops.push(Op::DistinctCheck {
start: 0,
count,
target: 0,
});
Some(at)
} else {
None
};
let (offset_skip_m, limit_m) = if ordering {
for (j, (expr, _)) in key_specs.iter().enumerate() {
c.compile_expr_into(expr, key_start + j)?;
}
c.ops.push(Op::SorterInsert {
row_start: 0,
row_count: count,
key_start,
key_count: key_specs.len(),
});
(None, None)
} else {
let offset_skip_m = offset_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfPosDecr { reg: r, target: 0 });
at
});
c.ops.push(Op::ResultRow { start: 0, count });
let limit_m = limit_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::DecrJumpZero { reg: r, target: 0 });
at
});
(offset_skip_m, limit_m)
};
let next1 = c.ops.len();
c.ops.push(Op::NextC {
cursor: 1,
target: body,
});
let after_inner = c.ops.len();
c.ops.push(Op::IfFalse {
reg: matched,
target: 0,
}); let goto_next0 = c.ops.len();
c.ops.push(Op::Goto { target: 0 });
let null_pad = c.ops.len();
c.ops.push(Op::NullRow { cursor: 1 });
let where_skip_n = match &sel.where_clause {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
for (i, (expr, _)) in projections.iter().enumerate() {
c.compile_expr_into(expr, i)?;
}
let distinct_skip_n = if sel.distinct {
let at = c.ops.len();
c.ops.push(Op::DistinctCheck {
start: 0,
count,
target: 0,
});
Some(at)
} else {
None
};
let (offset_skip_n, limit_n) = if ordering {
for (j, (expr, _)) in key_specs.iter().enumerate() {
c.compile_expr_into(expr, key_start + j)?;
}
c.ops.push(Op::SorterInsert {
row_start: 0,
row_count: count,
key_start,
key_count: key_specs.len(),
});
(None, None)
} else {
let offset_skip_n = offset_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfPosDecr { reg: r, target: 0 });
at
});
c.ops.push(Op::ResultRow { start: 0, count });
let limit_n = limit_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::DecrJumpZero { reg: r, target: 0 });
at
});
(offset_skip_n, limit_n)
};
let next0 = c.ops.len();
c.ops.push(Op::NextC {
cursor: 0,
target: loop0,
});
let scan_done = c.ops.len();
let elimit = if ordering {
c.ops.push(Op::SorterSort {
keys: key_specs.iter().map(|(_, k)| k.clone()).collect(),
});
let srewind = c.ops.len();
c.ops.push(Op::SorterRewind { target: 0 });
let ebody = c.ops.len();
let eoffset = offset_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfPosDecr { reg: r, target: 0 });
at
});
c.ops.push(Op::SorterRow { start: 0, count });
c.ops.push(Op::ResultRow { start: 0, count });
let elimit = limit_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::DecrJumpZero { reg: r, target: 0 });
at
});
let snext = c.ops.len();
c.ops.push(Op::SorterNext { target: ebody });
let eend = c.ops.len();
if let Op::SorterRewind { target } = &mut c.ops[srewind] {
*target = eend;
}
if let Some(at) = eoffset {
if let Op::IfPosDecr { target, .. } = &mut c.ops[at] {
*target = snext;
}
}
elimit
} else {
None
};
let end = c.ops.len();
c.ops.push(Op::Halt);
let set = |ops: &mut [Op], at: usize, tgt: usize| match &mut ops[at] {
Op::IfFalse { target, .. }
| Op::IfPosDecr { target, .. }
| Op::DecrJumpZero { target, .. }
| Op::Goto { target }
| Op::DistinctCheck { target, .. }
| Op::RewindC { target, .. } => *target = tgt,
_ => {}
};
set(&mut c.ops, rewind0, scan_done); set(&mut c.ops, rewind1, after_inner); if let Some(at) = on_skip {
set(&mut c.ops, at, next1); }
if let Some(at) = where_skip_m {
set(&mut c.ops, at, next1); }
if let Some(at) = distinct_skip_m {
set(&mut c.ops, at, next1); }
if let Some(at) = offset_skip_m {
set(&mut c.ops, at, next1);
}
if let Some(at) = limit_m {
set(&mut c.ops, at, end);
}
set(&mut c.ops, after_inner, null_pad); set(&mut c.ops, goto_next0, next0); if let Some(at) = where_skip_n {
set(&mut c.ops, at, next0); }
if let Some(at) = distinct_skip_n {
set(&mut c.ops, at, next0); }
if let Some(at) = offset_skip_n {
set(&mut c.ops, at, next0);
}
if let Some(at) = limit_n {
set(&mut c.ops, at, end);
}
if let Some(at) = elimit {
set(&mut c.ops, at, end);
}
Ok(Program {
ops: c.ops,
n_registers: c.next_reg,
columns: projections.into_iter().map(|(_, l)| l).collect(),
})
}
fn emit_output_row(
c: &mut Compiler,
projections: &[(Expr, String)],
count: usize,
offset_reg: Option<usize>,
limit_reg: Option<usize>,
sorter: Option<(usize, &[(Expr, SortKey)])>,
distinct: bool,
) -> Result<(Option<usize>, Option<usize>, Option<usize>)> {
for (i, (expr, _)) in projections.iter().enumerate() {
c.compile_expr_into(expr, i)?;
}
let distinct_skip = if distinct {
let at = c.ops.len();
c.ops.push(Op::DistinctCheck {
start: 0,
count,
target: 0,
});
Some(at)
} else {
None
};
if let Some((key_start, key_specs)) = sorter {
for (j, (expr, _)) in key_specs.iter().enumerate() {
c.compile_expr_into(expr, key_start + j)?;
}
c.ops.push(Op::SorterInsert {
row_start: 0,
row_count: count,
key_start,
key_count: key_specs.len(),
});
return Ok((None, None, distinct_skip));
}
let offset_skip = offset_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfPosDecr { reg: r, target: 0 });
at
});
c.ops.push(Op::ResultRow { start: 0, count });
let limit_done = limit_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::DecrJumpZero { reg: r, target: 0 });
at
});
Ok((offset_skip, limit_done, distinct_skip))
}
#[allow(clippy::too_many_arguments)]
pub fn compile_full_join2(
sel: &Select,
columns: &[String],
tables: &[String],
affinities: &[Affinity],
collations: &[Collation],
n_left: usize,
on: &Option<Expr>,
) -> Result<Program> {
if !sel.compound.is_empty() || !sel.group_by.is_empty() || sel.having.is_some() {
return Err(Error::Unsupported(
"VDBE: full-join shape not nested-loopable",
));
}
if sel.distinct && collations.iter().any(|cl| *cl != Collation::Binary) {
return Err(Error::Unsupported(
"VDBE: non-BINARY collation with DISTINCT",
));
}
let projections = expand_projections(sel, columns, tables)?;
if projections.iter().any(|(e, _)| is_aggregate_expr(e)) {
return Err(Error::Unsupported("VDBE: aggregate over a full join"));
}
let count = projections.len();
if matches!(&sel.limit, Some(Expr::Literal(Literal::Integer(0)))) {
return Ok(Program {
ops: alloc::vec![Op::Halt],
n_registers: count,
columns: projections.into_iter().map(|(_, l)| l).collect(),
});
}
let mut c = Compiler {
ops: Vec::new(),
next_reg: count,
columns: columns.to_vec(),
tables: tables.to_vec(),
affinities: affinities.to_vec(),
collations: collations.to_vec(),
bindings: Vec::new(),
forbid_raw_columns: false,
rowid_index: None,
cursor_boundaries: Some(alloc::vec![n_left, columns.len()]),
};
let limit_reg = match &sel.limit {
None => None,
Some(e) => match fold_const_int(e) {
Some(n) if n < 0 => None,
Some(n) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: n, dest: r });
Some(r)
}
None => return Err(Error::Unsupported("VDBE: only constant integer LIMIT")),
},
};
let offset_reg = match &sel.offset {
None => None,
Some(e) => match fold_const_int(e) {
Some(n) if n <= 0 => None,
Some(n) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: n, dest: r });
Some(r)
}
None => return Err(Error::Unsupported("VDBE: only constant integer OFFSET")),
},
};
let matched = c.alloc();
let ordering = !sel.order_by.is_empty();
let key_specs = if ordering {
build_sort_keys(&c, sel, columns, &projections, count)?
} else {
Vec::new()
};
let key_start = c.next_reg;
for _ in &key_specs {
c.alloc();
}
let sorter = if ordering {
Some((key_start, key_specs.as_slice()))
} else {
None
};
let set = |ops: &mut [Op], at: usize, tgt: usize| match &mut ops[at] {
Op::IfFalse { target, .. }
| Op::IfPosDecr { target, .. }
| Op::DecrJumpZero { target, .. }
| Op::Goto { target }
| Op::IfMatched { target, .. }
| Op::DistinctCheck { target, .. }
| Op::RewindC { target, .. } => *target = tgt,
_ => {}
};
let rewind0 = c.ops.len();
c.ops.push(Op::RewindC {
cursor: 0,
target: 0,
});
let loop0 = c.ops.len();
c.ops.push(Op::Integer {
value: 0,
dest: matched,
});
let rewind1 = c.ops.len();
c.ops.push(Op::RewindC {
cursor: 1,
target: 0,
});
let body = c.ops.len();
let on_skip = match on {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
c.ops.push(Op::Integer {
value: 1,
dest: matched,
});
c.ops.push(Op::MarkMatched { cursor: 1 });
let where_m = match &sel.where_clause {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
let (offset_m, limit_m, distinct_m) = emit_output_row(
&mut c,
&projections,
count,
offset_reg,
limit_reg,
sorter,
sel.distinct,
)?;
let next1 = c.ops.len();
c.ops.push(Op::NextC {
cursor: 1,
target: body,
});
let after_inner = c.ops.len();
c.ops.push(Op::IfFalse {
reg: matched,
target: 0,
});
let goto_next0 = c.ops.len();
c.ops.push(Op::Goto { target: 0 });
let null_pad = c.ops.len();
c.ops.push(Op::NullRow { cursor: 1 });
let where_lnull = match &sel.where_clause {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
let (offset_lnull, limit_lnull, distinct_lnull) = emit_output_row(
&mut c,
&projections,
count,
offset_reg,
limit_reg,
sorter,
sel.distinct,
)?;
let next0 = c.ops.len();
c.ops.push(Op::NextC {
cursor: 0,
target: loop0,
});
let pass2 = c.ops.len();
c.ops.push(Op::NullRow { cursor: 0 });
let rewind1b = c.ops.len();
c.ops.push(Op::RewindC {
cursor: 1,
target: 0,
});
let loop2 = c.ops.len();
let if_matched = c.ops.len();
c.ops.push(Op::IfMatched {
cursor: 1,
target: 0,
});
let where_rnull = match &sel.where_clause {
Some(pred) => {
let preg = c.compile_expr(pred)?;
let at = c.ops.len();
c.ops.push(Op::IfFalse {
reg: preg,
target: 0,
});
Some(at)
}
None => None,
};
let (offset_rnull, limit_rnull, distinct_rnull) = emit_output_row(
&mut c,
&projections,
count,
offset_reg,
limit_reg,
sorter,
sel.distinct,
)?;
let next2 = c.ops.len();
c.ops.push(Op::NextC {
cursor: 1,
target: loop2,
});
let scan_done = c.ops.len();
let elimit = if ordering {
c.ops.push(Op::SorterSort {
keys: key_specs.iter().map(|(_, k)| k.clone()).collect(),
});
let srewind = c.ops.len();
c.ops.push(Op::SorterRewind { target: 0 });
let ebody = c.ops.len();
let eoffset = offset_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::IfPosDecr { reg: r, target: 0 });
at
});
c.ops.push(Op::SorterRow { start: 0, count });
c.ops.push(Op::ResultRow { start: 0, count });
let elimit = limit_reg.map(|r| {
let at = c.ops.len();
c.ops.push(Op::DecrJumpZero { reg: r, target: 0 });
at
});
let snext = c.ops.len();
c.ops.push(Op::SorterNext { target: ebody });
let eend = c.ops.len();
if let Op::SorterRewind { target } = &mut c.ops[srewind] {
*target = eend;
}
if let Some(at) = eoffset {
if let Op::IfPosDecr { target, .. } = &mut c.ops[at] {
*target = snext;
}
}
elimit
} else {
None
};
let end = c.ops.len();
c.ops.push(Op::Halt);
set(&mut c.ops, rewind0, pass2); set(&mut c.ops, rewind1, after_inner); if let Some(at) = on_skip {
set(&mut c.ops, at, next1);
}
if let Some(at) = where_m {
set(&mut c.ops, at, next1);
}
if let Some(at) = distinct_m {
set(&mut c.ops, at, next1); }
if let Some(at) = offset_m {
set(&mut c.ops, at, next1);
}
if let Some(at) = limit_m {
set(&mut c.ops, at, end);
}
set(&mut c.ops, after_inner, null_pad);
set(&mut c.ops, goto_next0, next0);
if let Some(at) = where_lnull {
set(&mut c.ops, at, next0);
}
if let Some(at) = distinct_lnull {
set(&mut c.ops, at, next0); }
if let Some(at) = offset_lnull {
set(&mut c.ops, at, next0);
}
if let Some(at) = limit_lnull {
set(&mut c.ops, at, end);
}
set(&mut c.ops, rewind1b, scan_done); set(&mut c.ops, if_matched, next2); if let Some(at) = where_rnull {
set(&mut c.ops, at, next2);
}
if let Some(at) = distinct_rnull {
set(&mut c.ops, at, next2); }
if let Some(at) = offset_rnull {
set(&mut c.ops, at, next2);
}
if let Some(at) = limit_rnull {
set(&mut c.ops, at, end);
}
if let Some(at) = elimit {
set(&mut c.ops, at, end);
}
Ok(Program {
ops: c.ops,
n_registers: c.next_reg,
columns: projections.into_iter().map(|(_, l)| l).collect(),
})
}
struct Compiler {
ops: Vec<Op>,
next_reg: usize,
columns: Vec<String>,
tables: Vec<String>,
affinities: Vec<Affinity>,
collations: Vec<Collation>,
bindings: Vec<(Expr, usize)>,
forbid_raw_columns: bool,
rowid_index: Option<usize>,
cursor_boundaries: Option<Vec<usize>>,
}
impl Compiler {
fn cursor_of(boundaries: &[usize], idx: usize) -> (usize, usize) {
let mut prev = 0;
for (cur, &end) in boundaries.iter().enumerate() {
if idx < end {
return (cur, idx - prev);
}
prev = end;
}
let last = boundaries.len().saturating_sub(1);
(
last,
idx - boundaries.get(last.wrapping_sub(1)).copied().unwrap_or(0),
)
}
}
impl Compiler {
fn alloc(&mut self) -> usize {
let r = self.next_reg;
self.next_reg += 1;
r
}
fn resolve_column(&self, table: Option<&str>, column: &str) -> Result<usize> {
let mut found = None;
for (i, c) in self.columns.iter().enumerate() {
if !c.eq_ignore_ascii_case(column) {
continue;
}
if let Some(t) = table {
if !self
.tables
.get(i)
.is_some_and(|tn| tn.eq_ignore_ascii_case(t))
{
continue;
}
}
if found.is_some() {
return Err(Error::Unsupported("VDBE: ambiguous column reference"));
}
found = Some(i);
}
if found.is_none() {
if let Some(ri) = self.rowid_index {
let is_alias = matches!(
column.to_ascii_lowercase().as_str(),
"rowid" | "_rowid_" | "oid"
);
let table_ok = table.is_none_or(|t| {
self.tables
.first()
.is_some_and(|tn| tn.eq_ignore_ascii_case(t))
});
if is_alias && table_ok {
return Ok(ri);
}
}
}
found.ok_or(Error::Unsupported("VDBE: unresolved column reference"))
}
fn expr_affinity(&self, expr: &Expr) -> Option<Affinity> {
match expr {
Expr::Column { table, column } => self
.resolve_column(table.as_deref(), column)
.ok()
.and_then(|i| self.affinities.get(i).copied()),
Expr::Cast { type_name, .. } => Some(Affinity::from_type(Some(type_name))),
Expr::Paren(e) => self.expr_affinity(e),
Expr::Collate { expr, .. } => self.expr_affinity(expr),
_ => None,
}
}
fn explicit_collation(&self, expr: &Expr) -> Option<Collation> {
match expr {
Expr::Paren(e) => self.explicit_collation(e),
Expr::Collate { expr, collation } => {
Collation::parse(collation).or_else(|| self.explicit_collation(expr))
}
_ => None,
}
}
fn implicit_collation(&self, expr: &Expr) -> Option<Collation> {
match expr {
Expr::Column { table, column } => self
.resolve_column(table.as_deref(), column)
.ok()
.and_then(|i| self.collations.get(i).copied()),
Expr::Paren(e) | Expr::Collate { expr: e, .. } => self.implicit_collation(e),
_ => None,
}
}
fn col_collation(&self, expr: &Expr) -> Option<Collation> {
self.explicit_collation(expr)
.or_else(|| self.implicit_collation(expr))
}
fn compare_collation(&self, left: &Expr, right: &Expr) -> Collation {
self.explicit_collation(left)
.or_else(|| self.explicit_collation(right))
.or_else(|| self.implicit_collation(left))
.or_else(|| self.implicit_collation(right))
.unwrap_or_default()
}
fn push_compare(
&mut self,
op: BinaryOp,
lhs: usize,
left: &Expr,
rhs: usize,
right: &Expr,
dest: usize,
) {
let coll = self.compare_collation(left, right);
self.push_compare_coll(op, lhs, left, rhs, right, dest, coll);
}
#[allow(clippy::too_many_arguments)]
fn push_compare_coll(
&mut self,
op: BinaryOp,
lhs: usize,
left: &Expr,
rhs: usize,
right: &Expr,
dest: usize,
coll: Collation,
) {
let ra = self.expr_affinity(right);
self.push_compare_coll_ra(op, lhs, left, rhs, ra, dest, coll);
}
#[allow(clippy::too_many_arguments)]
fn push_compare_coll_ra(
&mut self,
op: BinaryOp,
lhs: usize,
left: &Expr,
rhs: usize,
ra: Option<Affinity>,
dest: usize,
coll: Collation,
) {
let la = self.expr_affinity(left);
self.ops.push(Op::Compare {
op,
lhs,
rhs,
dest,
la,
ra,
coll,
});
}
fn compile_expr(&mut self, expr: &Expr) -> Result<usize> {
let dest = self.alloc();
self.compile_expr_into(expr, dest)?;
Ok(dest)
}
fn compile_expr_into(&mut self, expr: &Expr, dest: usize) -> Result<()> {
if let Some(&(_, src)) = self.bindings.iter().find(|(e, _)| e == expr) {
if src != dest {
self.ops.push(Op::Copy { src, dest });
}
return Ok(());
}
match expr {
Expr::Literal(l) => {
let op = match l {
Literal::Integer(i) => Op::Integer { value: *i, dest },
Literal::Real(r) => Op::Real { value: *r, dest },
Literal::Str(s) => Op::Str {
value: s.clone(),
dest,
},
Literal::Null => Op::Null { dest },
Literal::Boolean(b) => Op::Integer {
value: *b as i64,
dest,
},
Literal::Blob(b) => Op::Blob {
value: b.clone(),
dest,
},
};
self.ops.push(op);
Ok(())
}
Expr::Column { table, column } => {
if self.forbid_raw_columns {
return Err(Error::Unsupported("VDBE: bare column in grouped output"));
}
let idx = self.resolve_column(table.as_deref(), column)?;
match &self.cursor_boundaries {
Some(bounds) => {
let (cursor, col) = Self::cursor_of(bounds, idx);
self.ops.push(Op::ColumnC { cursor, col, dest });
}
None => self.ops.push(Op::Column { col: idx, dest }),
}
Ok(())
}
Expr::Paren(inner) => self.compile_expr_into(inner, dest),
Expr::Unary {
op: crate::sql::ast::UnaryOp::Negate,
expr: inner,
} => {
let r = self.compile_expr(inner)?;
self.ops.push(Op::Negate { reg: r, dest });
Ok(())
}
Expr::Unary {
op: crate::sql::ast::UnaryOp::Not,
expr: inner,
} => {
let r = self.compile_expr(inner)?;
self.ops.push(Op::Not { reg: r, dest });
Ok(())
}
Expr::Unary {
op: crate::sql::ast::UnaryOp::BitNot,
expr: inner,
} => {
let r = self.compile_expr(inner)?;
self.ops.push(Op::BitNot { reg: r, dest });
Ok(())
}
Expr::Unary {
op: crate::sql::ast::UnaryOp::Identity,
expr: inner,
} => self.compile_expr_into(inner, dest),
Expr::IsNull {
expr: inner,
negated,
} => {
let r = self.compile_expr(inner)?;
self.ops.push(Op::IsNull {
reg: r,
negated: *negated,
dest,
});
Ok(())
}
Expr::Binary { op, left, right } => {
let l = self.compile_expr(left)?;
let r = self.compile_expr(right)?;
use BinaryOp::*;
match op {
Add | Sub | Mul | Div | Mod => {
self.ops.push(Op::Arith {
op: *op,
lhs: l,
rhs: r,
dest,
});
Ok(())
}
BitAnd | BitOr | LShift | RShift => {
self.ops.push(Op::Bitwise {
op: *op,
lhs: l,
rhs: r,
dest,
});
Ok(())
}
Concat => {
self.ops.push(Op::Concat {
lhs: l,
rhs: r,
dest,
});
Ok(())
}
Eq | NotEq | Lt | LtEq | Gt | GtEq => {
self.push_compare(*op, l, left, r, right, dest);
Ok(())
}
And => {
self.ops.push(Op::And {
lhs: l,
rhs: r,
dest,
});
Ok(())
}
Or => {
self.ops.push(Op::Or {
lhs: l,
rhs: r,
dest,
});
Ok(())
}
Is | IsNot => {
if let Expr::Literal(Literal::Boolean(want)) = unparen(right) {
self.ops.push(Op::Truthy {
want: *want,
not: matches!(op, IsNot),
operand: l,
dest,
});
} else {
self.ops.push(Op::Is {
is: matches!(op, Is),
lhs: l,
rhs: r,
dest,
la: self.expr_affinity(left),
ra: self.expr_affinity(right),
});
}
Ok(())
}
Like | Glob => {
self.ops.push(Op::Like {
glob: matches!(op, Glob),
lhs: l,
rhs: r,
dest,
});
Ok(())
}
JsonExtract | JsonExtractText => {
self.ops.push(Op::Json {
as_text: matches!(op, JsonExtractText),
lhs: l,
rhs: r,
dest,
});
Ok(())
}
}
}
Expr::Cast {
expr: inner,
type_name,
} => {
let r = self.compile_expr(inner)?;
self.ops.push(Op::Cast {
reg: r,
type_name: type_name.clone(),
dest,
});
Ok(())
}
Expr::Collate { expr: inner, .. } => self.compile_expr_into(inner, dest),
Expr::Case {
operand,
when_then,
else_result,
} => self.compile_case(operand.as_deref(), when_then, else_result.as_deref(), dest),
Expr::Between {
expr: inner,
low,
high,
negated,
} => {
let x = self.compile_expr(inner)?;
let lo = self.compile_expr(low)?;
let hi = self.compile_expr(high)?;
let ge = self.alloc();
self.push_compare(BinaryOp::GtEq, x, inner, lo, low, ge);
let le = self.alloc();
self.push_compare(BinaryOp::LtEq, x, inner, hi, high, le);
if *negated {
let both = self.alloc();
self.ops.push(Op::And {
lhs: ge,
rhs: le,
dest: both,
});
self.ops.push(Op::Not { reg: both, dest });
} else {
self.ops.push(Op::And {
lhs: ge,
rhs: le,
dest,
});
}
Ok(())
}
Expr::InList {
expr: inner,
list,
negated,
candidate_affinity,
} => {
let x = self.compile_expr(inner)?;
let in_coll = if list.len() == 1 {
self.compare_collation(inner, &list[0])
} else {
self.col_collation(inner).unwrap_or_default()
};
let cand_ra = candidate_affinity
.as_deref()
.map(|t| Affinity::from_type(Some(t)));
let acc = self.alloc();
self.ops.push(Op::Integer {
value: 0,
dest: acc,
});
for elem in list {
let e = self.compile_expr(elem)?;
let eq = self.alloc();
self.push_compare_coll_ra(BinaryOp::Eq, x, inner, e, cand_ra, eq, in_coll);
let next = self.alloc();
self.ops.push(Op::Or {
lhs: acc,
rhs: eq,
dest: next,
});
self.ops.push(Op::Copy {
src: next,
dest: acc,
});
}
if *negated {
self.ops.push(Op::Not { reg: acc, dest });
} else {
self.ops.push(Op::Copy { src: acc, dest });
}
Ok(())
}
Expr::Function {
name,
distinct,
args,
star,
filter,
order_by,
over,
} if !*distinct
&& !*star
&& filter.is_none()
&& order_by.is_empty()
&& over.is_none()
&& is_pure_scalar_fn(name, args.len()) =>
{
if args.iter().any(|a| self.explicit_collation(a).is_some()) {
return Err(Error::Unsupported("VDBE: COLLATE in a function argument"));
}
let arg_start = self.next_reg;
for _ in 0..args.len() {
self.alloc();
}
for (i, a) in args.iter().enumerate() {
self.compile_expr_into(a, arg_start + i)?;
}
self.ops.push(Op::Func {
name: name.clone(),
arg_start,
arg_count: args.len(),
dest,
});
Ok(())
}
_ => Err(Error::Unsupported("VDBE spike: this expression")),
}
}
fn compile_case(
&mut self,
operand: Option<&Expr>,
when_then: &[(Expr, Expr)],
else_result: Option<&Expr>,
dest: usize,
) -> Result<()> {
let operand_reg = match operand {
Some(o) => Some(self.compile_expr(o)?),
None => None,
};
let mut end_jumps = Vec::new();
for (when, then) in when_then {
let cond = match operand_reg {
Some(oreg) => {
let wreg = self.compile_expr(when)?;
let c = self.alloc();
self.push_compare(BinaryOp::Eq, oreg, operand.unwrap(), wreg, when, c);
c
}
None => self.compile_expr(when)?,
};
let skip = self.ops.len();
self.ops.push(Op::IfFalse {
reg: cond,
target: 0,
});
self.compile_expr_into(then, dest)?;
end_jumps.push(self.ops.len());
self.ops.push(Op::Goto { target: 0 });
let here = self.ops.len();
if let Op::IfFalse { target, .. } = &mut self.ops[skip] {
*target = here;
}
}
match else_result {
Some(e) => self.compile_expr_into(e, dest)?,
None => self.ops.push(Op::Null { dest }),
}
let end = self.ops.len();
for j in end_jumps {
if let Op::Goto { target } = &mut self.ops[j] {
*target = end;
}
}
Ok(())
}
}
pub fn run(program: &Program) -> Result<Vec<Vec<Value>>> {
run_rows(program, &[])
}
pub fn run_rows(program: &Program, table_rows: &[Vec<Value>]) -> Result<Vec<Vec<Value>>> {
run_rows_multi(program, &[table_rows])
}
pub fn run_rows_multi(program: &Program, rowsets: &[&[Vec<Value>]]) -> Result<Vec<Vec<Value>>> {
let mut regs: Vec<Value> = alloc::vec![Value::Null; program.n_registers];
let mut out = Vec::new();
let table_rows: &[Vec<Value>] = rowsets.first().copied().unwrap_or(&[]);
let mut cursor: usize = 0; let mut positions: Vec<usize> = alloc::vec![0; rowsets.len().max(1)];
let mut null_flags: Vec<bool> = alloc::vec![false; rowsets.len().max(1)];
let mut matched_rows: Vec<Vec<bool>> = rowsets
.iter()
.map(|rs| alloc::vec![false; rs.len()])
.collect();
if matched_rows.is_empty() {
matched_rows.push(Vec::new());
}
let mut sorter: Vec<(Vec<Value>, Vec<Value>)> = Vec::new();
let mut scursor: usize = 0;
let mut seen: Vec<Vec<Value>> = Vec::new();
let mut agg: Vec<AggAcc> = Vec::new();
let mut groups: Vec<Group> = Vec::new();
let mut emit_groups: Vec<(Vec<Value>, Vec<Value>)> = Vec::new();
let mut gcursor: usize = 0;
let mut pc = 0usize;
while pc < program.ops.len() {
let op = &program.ops[pc];
pc += 1;
match op {
Op::Rewind { target } => {
cursor = 0;
if table_rows.is_empty() {
pc = *target;
}
}
Op::Column { col, dest } => {
regs[*dest] = table_rows
.get(cursor)
.and_then(|r| r.get(*col))
.cloned()
.unwrap_or(Value::Null);
}
Op::Next { target } => {
cursor += 1;
if cursor < table_rows.len() {
pc = *target;
}
}
Op::RewindC { cursor: c, target } => {
positions[*c] = 0;
null_flags[*c] = false;
if rowsets.get(*c).is_none_or(|rs| rs.is_empty()) {
pc = *target;
}
}
Op::ColumnC {
cursor: c,
col,
dest,
} => {
regs[*dest] = if null_flags[*c] {
Value::Null
} else {
rowsets
.get(*c)
.and_then(|rs| rs.get(positions[*c]))
.and_then(|r| r.get(*col))
.cloned()
.unwrap_or(Value::Null)
};
}
Op::NullRow { cursor: c } => {
null_flags[*c] = true;
}
Op::MarkMatched { cursor: c } => {
if let Some(row) = matched_rows
.get_mut(*c)
.and_then(|m| m.get_mut(positions[*c]))
{
*row = true;
}
}
Op::IfMatched { cursor: c, target } => {
if matched_rows
.get(*c)
.and_then(|m| m.get(positions[*c]))
.copied()
.unwrap_or(false)
{
pc = *target;
}
}
Op::NextC { cursor: c, target } => {
positions[*c] += 1;
if positions[*c] < rowsets.get(*c).map_or(0, |rs| rs.len()) {
pc = *target;
}
}
Op::DecrJumpZero { reg, target } => {
let n = match ®s[*reg] {
Value::Integer(i) => *i,
other => crate::exec::eval::to_i64(other),
};
regs[*reg] = Value::Integer(n - 1);
if n - 1 <= 0 {
pc = *target;
}
}
Op::IfPosDecr { reg, target } => {
let n = match ®s[*reg] {
Value::Integer(i) => *i,
other => crate::exec::eval::to_i64(other),
};
if n > 0 {
regs[*reg] = Value::Integer(n - 1);
pc = *target;
}
}
Op::Goto { target } => {
pc = *target;
}
Op::IfFalse { reg, target } => {
if crate::exec::eval::truth(®s[*reg]) != Some(true) {
pc = *target;
}
}
Op::Copy { src, dest } => regs[*dest] = regs[*src].clone(),
Op::Cast {
reg,
type_name,
dest,
} => {
regs[*dest] = crate::exec::eval::cast(regs[*reg].clone(), type_name);
}
Op::Integer { value, dest } => regs[*dest] = Value::Integer(*value),
Op::Real { value, dest } => regs[*dest] = Value::Real(*value),
Op::Str { value, dest } => regs[*dest] = Value::Text(value.clone()),
Op::Blob { value, dest } => regs[*dest] = Value::Blob(value.clone()),
Op::Null { dest } => regs[*dest] = Value::Null,
Op::Negate { reg, dest } => {
regs[*dest] = match crate::exec::eval::to_number(®s[*reg]) {
Value::Integer(i) => i
.checked_neg()
.map(Value::Integer)
.unwrap_or(Value::Real(-(i as f64))),
Value::Real(r) => Value::Real(-r),
_ => Value::Null,
};
}
Op::BitNot { reg, dest } => {
regs[*dest] = match ®s[*reg] {
Value::Null => Value::Null,
v => Value::Integer(!crate::exec::eval::to_i64(v)),
};
}
Op::Arith { op, lhs, rhs, dest } => {
regs[*dest] = crate::exec::eval::arithmetic_values(*op, ®s[*lhs], ®s[*rhs]);
}
Op::Bitwise { op, lhs, rhs, dest } => {
regs[*dest] = crate::exec::eval::bitwise_values(*op, ®s[*lhs], ®s[*rhs]);
}
Op::Is {
is,
lhs,
rhs,
dest,
la,
ra,
} => {
let (l, r) = crate::exec::eval::apply_comparison_affinity(
regs[*lhs].clone(),
*la,
regs[*rhs].clone(),
*ra,
);
regs[*dest] = crate::exec::eval::is_values(*is, &l, &r);
}
Op::Truthy {
want,
not,
operand,
dest,
} => {
let t = crate::exec::eval::truth(®s[*operand]) == Some(*want);
regs[*dest] = Value::Integer((t ^ *not) as i64);
}
Op::Like {
glob,
lhs,
rhs,
dest,
} => {
regs[*dest] = crate::exec::eval::like_glob_values(*glob, ®s[*lhs], ®s[*rhs]);
}
Op::Json {
as_text,
lhs,
rhs,
dest,
} => {
regs[*dest] = crate::exec::json::arrow(®s[*lhs], ®s[*rhs], *as_text)?;
}
Op::Func {
name,
arg_start,
arg_count,
dest,
} => {
use crate::sql::ast::{Expr, Literal};
let lit_args: Vec<Expr> = regs[*arg_start..*arg_start + *arg_count]
.iter()
.map(|v| {
Expr::Literal(match v {
Value::Null => Literal::Null,
Value::Integer(i) => Literal::Integer(*i),
Value::Real(r) => Literal::Real(*r),
Value::Text(s) => Literal::Str(s.clone()),
Value::Blob(b) => Literal::Blob(b.clone()),
})
})
.collect();
let params = crate::exec::eval::Params::default();
let ctx = crate::exec::eval::EvalCtx::rowless(¶ms);
regs[*dest] = crate::exec::func::eval_scalar(name, &lit_args, false, &ctx)?;
}
Op::Concat { lhs, rhs, dest } => {
regs[*dest] = crate::exec::eval::concat_values(®s[*lhs], ®s[*rhs]);
}
Op::Compare {
op,
lhs,
rhs,
dest,
la,
ra,
coll,
} => {
let (l, r) = crate::exec::eval::apply_comparison_affinity(
regs[*lhs].clone(),
*la,
regs[*rhs].clone(),
*ra,
);
regs[*dest] = crate::exec::eval::compare_op(*op, &l, &r, *coll);
}
Op::And { lhs, rhs, dest } => {
regs[*dest] = three_valued_and(®s[*lhs], ®s[*rhs]);
}
Op::Or { lhs, rhs, dest } => {
regs[*dest] = three_valued_or(®s[*lhs], ®s[*rhs]);
}
Op::Not { reg, dest } => {
regs[*dest] = match crate::exec::eval::truth(®s[*reg]) {
Some(b) => Value::Integer(!b as i64),
None => Value::Null,
};
}
Op::IsNull { reg, negated, dest } => {
let is_null = matches!(regs[*reg], Value::Null);
regs[*dest] = Value::Integer((is_null != *negated) as i64);
}
Op::ResultRow { start, count } => {
out.push(regs[*start..*start + *count].to_vec());
}
Op::DistinctCheck {
start,
count,
target,
} => {
let row = ®s[*start..*start + *count];
let dup = seen.iter().any(|prev| {
prev.len() == row.len() && prev.iter().zip(row).all(|(a, b)| distinct_eq(a, b))
});
if dup {
pc = *target;
} else {
seen.push(row.to_vec());
}
}
Op::SorterInsert {
row_start,
row_count,
key_start,
key_count,
} => {
let row = regs[*row_start..*row_start + *row_count].to_vec();
let keys = regs[*key_start..*key_start + *key_count].to_vec();
sorter.push((keys, row));
}
Op::SorterSort { keys } => {
sorter.sort_by(|a, b| {
for (i, k) in keys.iter().enumerate() {
let ord = crate::exec::cmp_order(
&a.0[i],
&b.0[i],
k.descending,
k.nulls_first,
k.collation,
);
if ord != core::cmp::Ordering::Equal {
return ord;
}
}
core::cmp::Ordering::Equal
});
}
Op::SorterRewind { target } => {
scursor = 0;
if sorter.is_empty() {
pc = *target;
}
}
Op::SorterRow { start, count } => {
if let Some((_, row)) = sorter.get(scursor) {
for (i, v) in row.iter().take(*count).enumerate() {
regs[*start + i] = v.clone();
}
}
}
Op::SorterNext { target } => {
scursor += 1;
if scursor < sorter.len() {
pc = *target;
}
}
Op::AggStep {
slot,
kind,
arg,
distinct,
filter,
order,
} => {
if *slot >= agg.len() {
agg.resize(*slot + 1, AggAcc::default());
}
let pass = filter.is_none_or(|f| crate::exec::eval::truth(®s[f]) == Some(true));
if !pass {
} else if *kind == AggKind::CountStar {
agg[*slot].count += 1;
} else if let Some(r) = arg {
if !matches!(regs[*r], Value::Null) {
let v = regs[*r].clone();
if !*distinct || !agg[*slot].vals.iter().any(|p| distinct_eq(p, &v)) {
agg[*slot].vals.push(v);
push_order_key(&mut agg[*slot], order, ®s);
}
}
}
}
Op::AggFinal { slot, kind, dest } => {
let acc = match agg.get_mut(*slot) {
Some(e) => core::mem::take(e),
None => AggAcc::default(),
};
regs[*dest] = finalize_agg(*kind, acc)?;
}
Op::GroupStep {
key_start,
key_count,
aggs,
} => {
let key = regs[*key_start..*key_start + *key_count].to_vec();
let gi = match groups.iter().position(|(k, _)| {
k.len() == key.len() && k.iter().zip(&key).all(|(a, b)| distinct_eq(a, b))
}) {
Some(i) => i,
None => {
groups.push((key, alloc::vec![AggAcc::default(); aggs.len()]));
groups.len() - 1
}
};
for (j, spec) in aggs.iter().enumerate() {
let pass = spec
.filter
.is_none_or(|f| crate::exec::eval::truth(®s[f]) == Some(true));
if !pass {
continue;
}
if spec.kind == AggKind::CountStar {
groups[gi].1[j].count += 1;
} else if let Some(r) = spec.arg {
if !matches!(regs[r], Value::Null) {
let v = regs[r].clone();
if !spec.distinct
|| !groups[gi].1[j].vals.iter().any(|p| distinct_eq(p, &v))
{
groups[gi].1[j].vals.push(v);
push_order_key(&mut groups[gi].1[j], &spec.order, ®s);
}
}
}
}
}
Op::GroupEmit { outputs, agg_kinds } => {
for (key, accs) in groups.drain(..) {
let finals: Vec<Value> = agg_kinds
.iter()
.zip(accs)
.map(|(k, acc)| finalize_agg(*k, acc))
.collect::<Result<_>>()?;
let row: Vec<Value> = outputs
.iter()
.map(|o| match o {
GroupOut::Key(i) => key[*i].clone(),
GroupOut::Agg(j) => finals[*j].clone(),
})
.collect();
out.push(row);
}
}
Op::GroupFinalize { agg_kinds, target } => {
emit_groups.clear();
for (key, accs) in groups.drain(..) {
let finals: Vec<Value> = agg_kinds
.iter()
.zip(accs)
.map(|(k, acc)| finalize_agg(*k, acc))
.collect::<Result<_>>()?;
emit_groups.push((key, finals));
}
gcursor = 0;
if emit_groups.is_empty() {
pc = *target;
}
}
Op::GroupKey { key, dest } => {
regs[*dest] = emit_groups
.get(gcursor)
.and_then(|(k, _)| k.get(*key))
.cloned()
.unwrap_or(Value::Null);
}
Op::GroupAgg { slot, dest } => {
regs[*dest] = emit_groups
.get(gcursor)
.and_then(|(_, a)| a.get(*slot))
.cloned()
.unwrap_or(Value::Null);
}
Op::GroupNext { target } => {
gcursor += 1;
if gcursor < emit_groups.len() {
pc = *target;
}
}
Op::Halt => break,
}
}
Ok(out)
}
fn push_order_key(acc: &mut AggAcc, order: &[AggOrderKey], regs: &[Value]) {
if order.is_empty() {
return;
}
if acc.dirs.is_empty() {
acc.dirs = order
.iter()
.map(|k| (k.descending, k.nulls_first))
.collect();
}
acc.keys
.push(order.iter().map(|k| regs[k.reg].clone()).collect());
}
fn cmp_key_rows(a: &[Value], b: &[Value], dirs: &[(bool, Option<bool>)]) -> core::cmp::Ordering {
use core::cmp::Ordering;
for (k, &(descending, nulls_first)) in dirs.iter().enumerate() {
let (x, y) = (&a[k], &b[k]);
let nulls_first = nulls_first.unwrap_or(!descending);
let ord = match (x, y) {
(Value::Null, Value::Null) => Ordering::Equal,
(Value::Null, _) => {
return if nulls_first {
Ordering::Less
} else {
Ordering::Greater
};
}
(_, Value::Null) => {
return if nulls_first {
Ordering::Greater
} else {
Ordering::Less
};
}
_ => {
let base = crate::value::cmp_values_coll(x, y, crate::value::Collation::Binary);
if descending {
base.reverse()
} else {
base
}
}
};
if ord != Ordering::Equal {
return ord;
}
}
Ordering::Equal
}
fn finalize_agg(kind: AggKind, acc: AggAcc) -> Result<Value> {
use crate::exec::eval;
use core::cmp::Ordering;
let AggAcc {
vals,
count: star,
keys,
dirs,
} = acc;
Ok(match kind {
AggKind::CountStar => Value::Integer(star),
AggKind::Count => Value::Integer(vals.len() as i64),
AggKind::Sum => {
if vals.is_empty() {
Value::Null
} else if vals.iter().all(|v| matches!(v, Value::Integer(_))) {
let mut acc: i64 = 0;
let mut overflow = false;
for v in &vals {
if let Value::Integer(i) = v {
match acc.checked_add(*i) {
Some(s) => acc = s,
None => {
overflow = true;
break;
}
}
}
}
if overflow {
return Err(Error::Error("integer overflow".into()));
} else {
Value::Integer(acc)
}
} else {
Value::Real(vals.iter().map(eval::to_f64).sum())
}
}
AggKind::Total => Value::Real(vals.iter().map(eval::to_f64).sum()),
AggKind::Avg => {
if vals.is_empty() {
Value::Null
} else {
let sum: f64 = vals.iter().map(eval::to_f64).sum();
Value::Real(sum / vals.len() as f64)
}
}
AggKind::Min => vals
.into_iter()
.reduce(|a, b| {
if eval::compare(&b, &a) == Ordering::Less {
b
} else {
a
}
})
.unwrap_or(Value::Null),
AggKind::Max => vals
.into_iter()
.reduce(|a, b| {
if eval::compare(&b, &a) == Ordering::Greater {
b
} else {
a
}
})
.unwrap_or(Value::Null),
AggKind::GroupConcat => {
if vals.is_empty() {
Value::Null
} else if keys.is_empty() {
let parts: Vec<String> = vals.iter().map(eval::to_text).collect();
Value::Text(parts.join(","))
} else {
let mut idx: Vec<usize> = (0..vals.len()).collect();
idx.sort_by(|&i, &j| cmp_key_rows(&keys[i], &keys[j], &dirs));
let parts: Vec<String> = idx.iter().map(|&i| eval::to_text(&vals[i])).collect();
Value::Text(parts.join(","))
}
}
})
}
fn distinct_eq(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Null, Value::Null) => true,
(Value::Null, _) | (_, Value::Null) => false,
_ => {
crate::value::cmp_values_coll(a, b, crate::value::Collation::Binary)
== core::cmp::Ordering::Equal
}
}
}
fn three_valued_and(a: &Value, b: &Value) -> Value {
use crate::exec::eval::truth;
match (truth(a), truth(b)) {
(Some(false), _) | (_, Some(false)) => Value::Integer(0),
(Some(true), Some(true)) => Value::Integer(1),
_ => Value::Null,
}
}
fn three_valued_or(a: &Value, b: &Value) -> Value {
use crate::exec::eval::truth;
match (truth(a), truth(b)) {
(Some(true), _) | (_, Some(true)) => Value::Integer(1),
(Some(false), Some(false)) => Value::Integer(0),
_ => Value::Null,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sql::ast::Statement;
use crate::sql::parse_one;
use alloc::vec;
fn run_sql(sql: &str) -> Vec<Vec<Value>> {
let Statement::Select(sel) = parse_one(sql).unwrap() else {
panic!("not a select")
};
let prog = compile_const_select(&sel).unwrap();
run(&prog).unwrap()
}
#[test]
fn arithmetic_and_concat() {
assert_eq!(run_sql("SELECT 1 + 2 * 3"), vec![vec![Value::Integer(7)]]);
assert_eq!(
run_sql("SELECT 10 - 4, 8 / 2"),
vec![vec![Value::Integer(6), Value::Integer(4)]]
);
assert_eq!(
run_sql("SELECT 'a' || 'b' || 'c'"),
vec![vec![Value::Text("abc".into())]]
);
assert_eq!(
run_sql("SELECT -5, 3.5"),
vec![vec![Value::Integer(-5), Value::Real(3.5)]]
);
}
#[test]
fn rejects_unsupported() {
let Statement::Select(sel) = parse_one("SELECT * FROM t").unwrap() else {
panic!()
};
assert!(compile_const_select(&sel).is_err());
}
#[test]
fn nested_loop_join_two_cursors() {
let Statement::Select(sel) =
parse_one("SELECT a.x, b.q FROM a, b WHERE a.x = b.p").unwrap()
else {
panic!()
};
let columns = vec![
"x".to_string(),
"y".to_string(),
"p".to_string(),
"q".to_string(),
];
let tables = vec![
"a".to_string(),
"a".to_string(),
"b".to_string(),
"b".to_string(),
];
let aff = vec![Affinity::Blob; 4];
let coll = vec![Collation::Binary; 4];
let prog = compile_join2(&sel, &columns, &tables, &aff, &coll, &[2, 4]).unwrap();
let left: Vec<Vec<Value>> = vec![
vec![Value::Integer(1), Value::Text("a".into())],
vec![Value::Integer(2), Value::Text("b".into())],
];
let right: Vec<Vec<Value>> = vec![
vec![Value::Integer(1), Value::Text("P".into())],
vec![Value::Integer(2), Value::Text("Q".into())],
vec![Value::Integer(2), Value::Text("R".into())],
];
assert_eq!(
run_rows_multi(&prog, &[&left, &right]).unwrap(),
vec![
vec![Value::Integer(1), Value::Text("P".into())],
vec![Value::Integer(2), Value::Text("Q".into())],
vec![Value::Integer(2), Value::Text("R".into())],
]
);
assert_eq!(prog.columns.len(), 2);
assert!(run_rows_multi(&prog, &[&left, &[]]).unwrap().is_empty());
assert!(run_rows_multi(&prog, &[&[], &right]).unwrap().is_empty());
}
#[test]
fn left_join_null_pads_unmatched_rows() {
let Statement::Select(sel) =
parse_one("SELECT a.x, b.q FROM a LEFT JOIN b ON a.x = b.p").unwrap()
else {
panic!()
};
let on = sel.from.as_ref().unwrap().joins[0].on.clone();
let columns = vec![
"x".to_string(),
"y".to_string(),
"p".to_string(),
"q".to_string(),
];
let tables = vec![
"a".to_string(),
"a".to_string(),
"b".to_string(),
"b".to_string(),
];
let aff = vec![Affinity::Blob; 4];
let coll = vec![Collation::Binary; 4];
let prog = compile_left_join2(&sel, &columns, &tables, &aff, &coll, 2, &on).unwrap();
let left: Vec<Vec<Value>> = vec![
vec![Value::Integer(1), Value::Text("a".into())],
vec![Value::Integer(2), Value::Text("b".into())],
vec![Value::Integer(3), Value::Text("c".into())],
];
let right: Vec<Vec<Value>> = vec![
vec![Value::Integer(1), Value::Text("P".into())],
vec![Value::Integer(2), Value::Text("Q".into())],
];
assert_eq!(
run_rows_multi(&prog, &[&left, &right]).unwrap(),
vec![
vec![Value::Integer(1), Value::Text("P".into())],
vec![Value::Integer(2), Value::Text("Q".into())],
vec![Value::Integer(3), Value::Null],
]
);
assert_eq!(
run_rows_multi(&prog, &[&left, &[]]).unwrap(),
vec![
vec![Value::Integer(1), Value::Null],
vec![Value::Integer(2), Value::Null],
vec![Value::Integer(3), Value::Null],
]
);
}
#[test]
fn full_join_two_pass_null_pads_both_sides() {
let Statement::Select(sel) =
parse_one("SELECT a.x, b.p FROM a FULL JOIN b ON a.x = b.p").unwrap()
else {
panic!()
};
let on = sel.from.as_ref().unwrap().joins[0].on.clone();
let columns = vec!["x".to_string(), "p".to_string()];
let tables = vec!["a".to_string(), "b".to_string()];
let aff = vec![Affinity::Blob; 2];
let coll = vec![Collation::Binary; 2];
let prog = compile_full_join2(&sel, &columns, &tables, &aff, &coll, 1, &on).unwrap();
let left: Vec<Vec<Value>> = vec![
vec![Value::Integer(1)],
vec![Value::Integer(2)],
vec![Value::Integer(3)],
];
let right: Vec<Vec<Value>> = vec![
vec![Value::Integer(2)],
vec![Value::Integer(3)],
vec![Value::Integer(4)],
];
assert_eq!(
run_rows_multi(&prog, &[&left, &right]).unwrap(),
vec![
vec![Value::Integer(1), Value::Null],
vec![Value::Integer(2), Value::Integer(2)],
vec![Value::Integer(3), Value::Integer(3)],
vec![Value::Null, Value::Integer(4)],
]
);
assert_eq!(
run_rows_multi(&prog, &[&[], &right]).unwrap(),
vec![
vec![Value::Null, Value::Integer(2)],
vec![Value::Null, Value::Integer(3)],
vec![Value::Null, Value::Integer(4)],
]
);
}
#[test]
fn nested_loop_join_bails_on_aggregate_and_order_by() {
let cols = vec!["x".to_string(), "p".to_string()];
let tabs = vec!["a".to_string(), "b".to_string()];
let aff = vec![Affinity::Blob; 2];
let coll = vec![Collation::Binary; 2];
for sql in [
"SELECT count(*) FROM a, b",
"SELECT a.x FROM a, b GROUP BY a.x",
"SELECT a.x, count(*) FROM a, b GROUP BY a.x HAVING count(*) > 1",
] {
let Statement::Select(sel) = parse_one(sql).unwrap() else {
panic!()
};
assert!(
compile_join2(&sel, &cols, &tabs, &aff, &coll, &[1, 2]).is_err(),
"{sql} should bail to the cross-product path"
);
}
for sql in [
"SELECT DISTINCT a.x FROM a, b",
"SELECT a.x FROM a, b ORDER BY a.x",
"SELECT DISTINCT a.x FROM a, b ORDER BY a.x DESC LIMIT 2",
] {
let Statement::Select(sel) = parse_one(sql).unwrap() else {
panic!()
};
assert!(
compile_join2(&sel, &cols, &tabs, &aff, &coll, &[1, 2]).is_ok(),
"{sql} should compile on the VDBE"
);
}
}
#[test]
fn bare_aggregate_join_compiles_and_bails_correctly() {
let cols = vec!["x".to_string(), "p".to_string()];
let tabs = vec!["a".to_string(), "b".to_string()];
let aff = vec![Affinity::Blob; 2];
let coll = vec![Collation::Binary; 2];
for sql in [
"SELECT count(*) FROM a, b",
"SELECT sum(a.x), max(b.p) FROM a, b",
"SELECT group_concat(b.p) FROM a, b WHERE a.x = b.p",
"SELECT count(DISTINCT a.x) FROM a, b",
"SELECT sum(DISTINCT b.p) FROM a, b WHERE a.x = b.p",
] {
let Statement::Select(sel) = parse_one(sql).unwrap() else {
panic!()
};
assert!(
compile_aggregate_join(&sel, &cols, &tabs, &aff, &coll, &[1, 2]).is_ok(),
"{sql} should compile as an aggregate join"
);
}
for sql in [
"SELECT a.x FROM a, b", "SELECT count(*) FROM a, b GROUP BY a.x", "SELECT count(*) FROM a, b ORDER BY 1", "SELECT count(*) FROM a, b LIMIT 1", ] {
let Statement::Select(sel) = parse_one(sql).unwrap() else {
panic!()
};
assert!(
compile_aggregate_join(&sel, &cols, &tabs, &aff, &coll, &[1, 2]).is_err(),
"{sql} should bail from the aggregate-join path"
);
}
}
#[test]
fn group_by_join_compiles_and_bails_correctly() {
let cols = vec!["x".to_string(), "p".to_string()];
let tabs = vec!["a".to_string(), "b".to_string()];
let aff = vec![Affinity::Blob; 2];
let coll = vec![Collation::Binary; 2];
for sql in [
"SELECT a.x, count(*) FROM a, b GROUP BY a.x",
"SELECT x, sum(p) FROM a, b GROUP BY x",
"SELECT a.x FROM a, b GROUP BY a.x",
"SELECT a.x, count(*) FROM a, b GROUP BY a.x HAVING count(*) > 1",
"SELECT a.x, count(*) FROM a, b GROUP BY a.x ORDER BY a.x",
"SELECT a.x, count(*) FROM a, b GROUP BY a.x LIMIT 1",
"SELECT a.x, count(*) AS n FROM a, b GROUP BY a.x ORDER BY n DESC LIMIT 2 OFFSET 1",
] {
let Statement::Select(sel) = parse_one(sql).unwrap() else {
panic!()
};
assert!(
compile_group_join(&sel, &cols, &tabs, &aff, &coll, &[1, 2]).is_ok(),
"{sql} should compile as a GROUP BY join"
);
}
for sql in [
"SELECT DISTINCT a.x, count(*) FROM a, b GROUP BY a.x",
"SELECT a.x, b.p FROM a, b GROUP BY a.x", "SELECT count(*) FROM a, b", ] {
let Statement::Select(sel) = parse_one(sql).unwrap() else {
panic!()
};
assert!(
compile_group_join(&sel, &cols, &tabs, &aff, &coll, &[1, 2]).is_err(),
"{sql} should bail from the GROUP BY join path"
);
}
}
}