use crate::error::{Error, Result};
use crate::sql::ast::{BinaryOp, Expr, Literal, ResultColumn, Select};
use crate::value::Value;
use alloc::string::String;
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 },
Null { dest: usize },
Arith {
op: BinaryOp,
lhs: usize,
rhs: usize,
dest: usize,
},
Concat { lhs: usize, rhs: usize, dest: usize },
Compare {
op: BinaryOp,
lhs: usize,
rhs: usize,
dest: usize,
},
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 },
DecrJumpZero { reg: usize, target: usize },
IfPosDecr { reg: usize, target: usize },
Negate { 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>,
},
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>,
}
#[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>,
}
type AggAcc = (Vec<Value>, i64);
type Group = (Vec<Value>, Vec<AggAcc>);
#[derive(Debug, Clone, PartialEq)]
pub struct Program {
pub ops: Vec<Op>,
pub n_registers: usize,
pub columns: Vec<String>,
}
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.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(),
bindings: Vec::new(),
};
let mut columns = Vec::new();
for (i, rc) in sel.columns.iter().enumerate() {
let ResultColumn::Expr { expr, alias, .. } = rc else {
return Err(Error::Unsupported("VDBE spike: only scalar result columns"));
};
c.compile_expr_into(expr, i)?;
columns.push(
alias
.clone()
.unwrap_or_else(|| alloc::format!("col{}", i + 1)),
);
}
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 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 agg_kind(expr: &Expr) -> Option<(AggKind, Option<Expr>)> {
let Expr::Function {
name,
distinct,
args,
star,
filter,
order_by,
over,
} = expr
else {
return None;
};
if *distinct || filter.is_some() || !order_by.is_empty() || over.is_some() {
return None;
}
let arg = args.first().cloned();
let kind = match name.to_ascii_lowercase().as_str() {
"count" if *star => return Some((AggKind::CountStar, None)),
"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,
};
Some((kind, arg))
}
fn compile_aggregate_select(
sel: &Select,
columns: &[String],
projections: &[(Expr, String)],
) -> Result<Program> {
if !sel.order_by.is_empty() || sel.limit.is_some() || sel.offset.is_some() || sel.distinct {
return Err(Error::Unsupported("VDBE: bare aggregate only"));
}
let mut slots: Vec<(AggKind, Option<Expr>)> = Vec::new();
for (e, _) in projections {
match agg_kind(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(),
bindings: Vec::new(),
};
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)) in slots.iter().enumerate() {
let arg_reg = match arg {
Some(expr) => Some(c.compile_expr(expr)?),
None => None,
};
c.ops.push(Op::AggStep {
slot,
kind: *kind,
arg: arg_reg,
});
}
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 compile_group_select(
sel: &Select,
columns: &[String],
projections: &[(Expr, String)],
) -> Result<Program> {
if sel.distinct {
return Err(Error::Unsupported("VDBE: GROUP BY + DISTINCT"));
}
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 col_index = |name: &str| columns.iter().position(|c| c.eq_ignore_ascii_case(name));
let mut group_cols: Vec<usize> = Vec::new();
for g in &sel.group_by {
match g {
Expr::Column { column, .. } => match col_index(column) {
Some(i) => group_cols.push(i),
None => return Err(Error::Unsupported("VDBE: GROUP BY unknown column")),
},
_ => return Err(Error::Unsupported("VDBE: GROUP BY column refs only")),
}
}
if !has_having && !has_order && !has_limit {
return compile_group_emit(sel, columns, projections, &group_cols);
}
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<(AggKind, Option<Expr>)> = Vec::new();
for e in &agg_exprs {
match agg_kind(e) {
Some(spec) => agg_specs.push(spec),
None => return Err(Error::Unsupported("VDBE: unsupported aggregate")),
}
}
let mut c = Compiler {
ops: Vec::new(),
next_reg: 0,
columns: columns.to_vec(),
bindings: Vec::new(),
};
let key_start = c.next_reg;
for _ in &group_cols {
c.alloc();
}
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 (k, &ci) in group_cols.iter().enumerate() {
c.ops.push(Op::Column {
col: ci,
dest: key_start + k,
});
}
let mut aggs: Vec<AggSpec> = Vec::new();
for (kind, arg) in &agg_specs {
let arg_reg = match arg {
Some(expr) => Some(c.compile_expr(expr)?),
None => None,
};
aggs.push(AggSpec {
kind: *kind,
arg: arg_reg,
});
}
c.ops.push(Op::GroupStep {
key_start,
key_count: group_cols.len(),
aggs,
});
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;
}
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() {
let bind_expr = Expr::Column {
table: None,
column: columns[ci].clone(),
};
c.bindings.push((bind_expr, 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(Expr::Literal(Literal::Integer(n))) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: *n, dest: r });
Some(r)
}
Some(_) => return Err(Error::Unsupported("VDBE: only constant integer LIMIT")),
};
let offset_reg = match &sel.offset {
None => None,
Some(Expr::Literal(Literal::Integer(n))) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: *n, dest: r });
Some(r)
}
Some(_) => 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::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(),
};
key_specs.push((
expr,
SortKey {
descending: term.descending,
nulls_first: term.nulls_first,
},
));
}
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,
});
}
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(),
})
}
fn compile_group_emit(
sel: &Select,
columns: &[String],
projections: &[(Expr, String)],
group_cols: &[usize],
) -> Result<Program> {
let col_index = |name: &str| columns.iter().position(|c| c.eq_ignore_ascii_case(name));
let mut outputs: Vec<GroupOut> = Vec::new();
let mut agg_specs: Vec<(AggKind, Option<Expr>)> = Vec::new();
for (e, _) in projections {
if is_aggregate_expr(e) {
match agg_kind(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 { column, .. } = e {
match col_index(column).and_then(|ci| 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",
));
}
}
let mut c = Compiler {
ops: Vec::new(),
next_reg: 0,
columns: columns.to_vec(),
bindings: Vec::new(),
};
let key_start = c.next_reg;
for _ in group_cols {
c.alloc();
}
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 (k, &ci) in group_cols.iter().enumerate() {
c.ops.push(Op::Column {
col: ci,
dest: key_start + k,
});
}
let mut aggs: Vec<AggSpec> = Vec::new();
for (kind, arg) in &agg_specs {
let arg_reg = match arg {
Some(expr) => Some(c.compile_expr(expr)?),
None => None,
};
aggs.push(AggSpec {
kind: *kind,
arg: arg_reg,
});
}
c.ops.push(Op::GroupStep {
key_start,
key_count: group_cols.len(),
aggs,
});
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;
}
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]) -> Result<Program> {
if !sel.compound.is_empty() {
return Err(Error::Unsupported("VDBE: only plain table projections"));
}
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, .. } => {
let label = alias.clone().unwrap_or_else(|| match expr {
Expr::Column { column, .. } => column.clone(),
_ => alloc::format!("col{}", projections.len() + 1),
});
projections.push((expr.clone(), label));
}
ResultColumn::TableWildcard(_) => {
return Err(Error::Unsupported("VDBE: table.* not yet supported"))
}
}
}
if projections.is_empty() {
return Err(Error::Unsupported("VDBE: empty projection"));
}
if !sel.group_by.is_empty() {
return compile_group_select(sel, columns, &projections);
}
if projections.iter().any(|(e, _)| is_aggregate_expr(e)) {
return compile_aggregate_select(sel, columns, &projections);
}
let count = projections.len();
let mut c = Compiler {
ops: Vec::new(),
next_reg: count,
columns: columns.to_vec(),
bindings: Vec::new(),
};
let limit_reg = match &sel.limit {
None => None,
Some(Expr::Literal(Literal::Integer(n))) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: *n, dest: r });
Some(r)
}
Some(_) => return Err(Error::Unsupported("VDBE: only constant integer LIMIT")),
};
let offset_reg = match &sel.offset {
None => None,
Some(Expr::Literal(Literal::Integer(n))) => {
let r = c.alloc();
c.ops.push(Op::Integer { value: *n, dest: r });
Some(r)
}
Some(_) => return Err(Error::Unsupported("VDBE: only constant integer OFFSET")),
};
let ordering = !sel.order_by.is_empty();
let mut key_specs: Vec<(Expr, SortKey)> = Vec::new();
if ordering {
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::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(),
};
key_specs.push((
expr,
SortKey {
descending: term.descending,
nulls_first: term.nulls_first,
},
));
}
}
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)?;
}
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(),
})
}
struct Compiler {
ops: Vec<Op>,
next_reg: usize,
columns: Vec<String>,
bindings: Vec<(Expr, usize)>,
}
impl Compiler {
fn alloc(&mut self) -> usize {
let r = self.next_reg;
self.next_reg += 1;
r
}
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(_) => {
return Err(Error::Unsupported("VDBE spike: blob literals"))
}
};
self.ops.push(op);
Ok(())
}
Expr::Column { column, .. } => {
let idx = self
.columns
.iter()
.position(|c| c.eq_ignore_ascii_case(column))
.ok_or_else(|| Error::Error(alloc::format!("no such column: {column}")))?;
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::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(())
}
Concat => {
self.ops.push(Op::Concat {
lhs: l,
rhs: r,
dest,
});
Ok(())
}
Eq | NotEq | Lt | LtEq | Gt | GtEq => {
self.ops.push(Op::Compare {
op: *op,
lhs: l,
rhs: r,
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(())
}
_ => Err(Error::Unsupported("VDBE spike: this operator")),
}
}
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::Case {
operand,
when_then,
else_result,
} => self.compile_case(operand.as_deref(), when_then, else_result.as_deref(), dest),
_ => 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.ops.push(Op::Compare {
op: BinaryOp::Eq,
lhs: oreg,
rhs: wreg,
dest: 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>>> {
let mut regs: Vec<Value> = alloc::vec![Value::Null; program.n_registers];
let mut out = Vec::new();
let mut cursor: usize = 0; 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::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::Null { dest } => regs[*dest] = Value::Null,
Op::Negate { reg, dest } => {
regs[*dest] = match crate::exec::eval::to_number(®s[*reg]) {
Value::Integer(i) => Value::Integer(i.wrapping_neg()),
Value::Real(r) => Value::Real(-r),
_ => Value::Null,
};
}
Op::Arith { op, lhs, rhs, dest } => {
regs[*dest] = crate::exec::eval::arithmetic_values(*op, ®s[*lhs], ®s[*rhs]);
}
Op::Concat { lhs, rhs, dest } => {
regs[*dest] =
if matches!(regs[*lhs], Value::Null) || matches!(regs[*rhs], Value::Null) {
Value::Null
} else {
let mut s = crate::exec::eval::to_text(®s[*lhs]);
s.push_str(&crate::exec::eval::to_text(®s[*rhs]));
Value::Text(s)
};
}
Op::Compare { op, lhs, rhs, dest } => {
regs[*dest] = crate::exec::eval::compare_op(
*op,
®s[*lhs],
®s[*rhs],
crate::value::Collation::Binary,
);
}
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,
crate::value::Collation::Binary,
);
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 } => {
if *slot >= agg.len() {
agg.resize(*slot + 1, (Vec::new(), 0));
}
if *kind == AggKind::CountStar {
agg[*slot].1 += 1;
} else if let Some(r) = arg {
if !matches!(regs[*r], Value::Null) {
let v = regs[*r].clone();
agg[*slot].0.push(v);
}
}
}
Op::AggFinal { slot, kind, dest } => {
let (vals, star) = match agg.get_mut(*slot) {
Some(e) => core::mem::take(e),
None => (Vec::new(), 0),
};
regs[*dest] = finalize_agg(*kind, vals, star)?;
}
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![(Vec::new(), 0); aggs.len()]));
groups.len() - 1
}
};
for (j, spec) in aggs.iter().enumerate() {
if spec.kind == AggKind::CountStar {
groups[gi].1[j].1 += 1;
} else if let Some(r) = spec.arg {
if !matches!(regs[r], Value::Null) {
let v = regs[r].clone();
groups[gi].1[j].0.push(v);
}
}
}
}
Op::GroupEmit { outputs, agg_kinds } => {
for (key, accs) in groups.drain(..) {
let finals: Vec<Value> = agg_kinds
.iter()
.zip(accs)
.map(|(k, (vals, star))| finalize_agg(*k, vals, star))
.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, (vals, star))| finalize_agg(*k, vals, star))
.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 finalize_agg(kind: AggKind, vals: Vec<Value>, star: i64) -> Result<Value> {
use crate::exec::eval;
use core::cmp::Ordering;
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 {
let parts: Vec<String> = vals.iter().map(eval::to_text).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());
}
}