mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-06 16:54:23 +01:00
Merge 'Add support for CASE expressions.' from Alex Miller
There's two forms of case: CASE (WHEN [bool expr] THEN [value])+ (ELSE [value])? END which checks a series of boolean conditions, and: CASE expr (WHEN [expr] THEN [value})+ (ELSE [value])? END Which checks a series of equality conditions. This implements support for both. Note that the ELSE is optional, and will be equivalent to `ELSE null` if not specified. sqlite3 gives the implementation as: ``` sqlite> explain select case a WHEN a THEN b WHEN c THEN d ELSE 0 END from casetest; addr opcode p1 p2 p3 p4 p5 comment ---- ------------- ---- ---- ---- ------------- -- ------------- 0 Init 0 16 0 0 Start at 16 1 OpenRead 0 3 0 4 0 root=3 iDb=0; casetest 2 Rewind 0 15 0 0 3 Column 0 0 2 0 r[2]= cursor 0 column 0 4 Column 0 0 3 0 r[3]= cursor 0 column 0 5 Ne 3 8 2 BINARY-8 83 if r[2]!=r[3] goto 8 6 Column 0 1 1 0 r[1]= cursor 0 column 1 7 Goto 0 13 0 0 8 Column 0 2 3 0 r[3]= cursor 0 column 2 9 Ne 3 12 2 BINARY-8 83 if r[2]!=r[3] goto 12 10 Column 0 3 1 0 r[1]= cursor 0 column 3 11 Goto 0 13 0 0 12 Integer 0 1 0 0 r[1]=0 13 ResultRow 1 1 0 0 output=r[1] 14 Next 0 3 0 1 15 Halt 0 0 0 0 16 Transaction 0 0 2 0 1 usesStmtJournal=0 17 Goto 0 1 0 0 ``` and after this patch, limbo gives: ``` addr opcode p1 p2 p3 p4 p5 comment ---- ----------------- ---- ---- ---- ------------- -- ------- 0 Init 0 19 0 0 Start at 19 1 OpenReadAsync 0 4 0 0 table=casetest, root=4 2 OpenReadAwait 0 0 0 0 3 RewindAsync 0 0 0 0 4 RewindAwait 0 18 0 0 Rewind table casetest 5 Column 0 0 2 0 r[2]=casetest.a 6 Column 0 0 3 0 r[3]=casetest.a 7 Ne 2 3 10 0 if r[2]!=r[3] goto 10 8 Column 0 1 1 0 r[1]=casetest.b 9 Goto 0 15 0 0 10 Column 0 2 3 0 r[3]=casetest.c 11 Ne 2 3 14 0 if r[2]!=r[3] goto 14 12 Column 0 3 1 0 r[1]=casetest.d 13 Goto 0 15 0 0 14 Integer 0 1 0 0 r[1]=0 15 ResultRow 1 1 0 0 output=r[1] 16 NextAsync 0 0 0 0 17 NextAwait 0 5 0 0 18 Halt 0 0 0 0 19 Transaction 0 0 0 0 20 Goto 0 1 0 0 ``` And then as there's nowhere to annotate this new support in COMPAT.md, I added a corresponding heading for SELECT expressions and what is/isn't supported. Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #425
This commit is contained in:
28
COMPAT.md
28
COMPAT.md
@@ -68,6 +68,33 @@ This document describes the SQLite compatibility status of Limbo:
|
||||
| VACUUM | No | |
|
||||
| WITH clause | No | |
|
||||
|
||||
### SELECT Expressions
|
||||
|
||||
Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html).
|
||||
|
||||
| Syntax | Status | Comment |
|
||||
|------------------------------|---------|---------|
|
||||
| literals | Yes | |
|
||||
| schema.table.column | Partial | Schemas aren't supported |
|
||||
| unary operator | Partial | `-` supported, `+~` aren't |
|
||||
| binary operator | Partial | Only `%`, `!<`, and `!>` are unsupported |
|
||||
| agg() FILTER (WHERE ...) | No | Is incorrectly ignored |
|
||||
| ... OVER (...) | No | Is incorrectly ignored |
|
||||
| (expr) | Yes | |
|
||||
| CAST (expr AS type) | Yes | |
|
||||
| COLLATE | No | |
|
||||
| (NOT) LIKE | No | |
|
||||
| (NOT) GLOB | No | |
|
||||
| (NOT) REGEXP | No | |
|
||||
| (NOT) MATCH | No | |
|
||||
| IS (NOT) | No | |
|
||||
| IS (NOT) DISTINCT FROM | No | |
|
||||
| (NOT) BETWEEN ... AND ... | No | |
|
||||
| (NOT) IN (subquery) | No | |
|
||||
| (NOT) EXISTS (subquery) | No | |
|
||||
| CASE WHEN THEN ELSE END | Yes | |
|
||||
| RAISE | No | |
|
||||
|
||||
## SQL functions
|
||||
|
||||
### Scalar functions
|
||||
@@ -147,7 +174,6 @@ This document describes the SQLite compatibility status of Limbo:
|
||||
| sum(X) | Yes | |
|
||||
| total(X) | Yes | |
|
||||
|
||||
|
||||
### Date and time functions
|
||||
|
||||
| Function | Status | Comment |
|
||||
|
||||
@@ -698,7 +698,102 @@ pub fn translate_expr(
|
||||
}
|
||||
Ok(target_register)
|
||||
}
|
||||
ast::Expr::Case { .. } => todo!(),
|
||||
ast::Expr::Case {
|
||||
base,
|
||||
when_then_pairs,
|
||||
else_expr,
|
||||
} => {
|
||||
// There's two forms of CASE, one which checks a base expression for equality
|
||||
// against the WHEN values, and returns the corresponding THEN value if it matches:
|
||||
// CASE 2 WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'many' END
|
||||
// And one which evaluates a series of boolean predicates:
|
||||
// CASE WHEN is_good THEN 'good' WHEN is_bad THEN 'bad' ELSE 'okay' END
|
||||
// This just changes which sort of branching instruction to issue, after we
|
||||
// generate the expression if needed.
|
||||
let return_label = program.allocate_label();
|
||||
let mut next_case_label = program.allocate_label();
|
||||
// Only allocate a reg to hold the base expression if one was provided.
|
||||
// And base_reg then becomes the flag we check to see which sort of
|
||||
// case statement we're processing.
|
||||
let base_reg = base.as_ref().map(|_| program.alloc_register());
|
||||
let expr_reg = program.alloc_register();
|
||||
if let Some(base_expr) = base {
|
||||
translate_expr(
|
||||
program,
|
||||
referenced_tables,
|
||||
base_expr,
|
||||
base_reg.unwrap(),
|
||||
precomputed_exprs_to_registers,
|
||||
)?;
|
||||
};
|
||||
for (when_expr, then_expr) in when_then_pairs {
|
||||
translate_expr(
|
||||
program,
|
||||
referenced_tables,
|
||||
when_expr,
|
||||
expr_reg,
|
||||
precomputed_exprs_to_registers,
|
||||
)?;
|
||||
match base_reg {
|
||||
// CASE 1 WHEN 0 THEN 0 ELSE 1 becomes 1==0, Ne branch to next clause
|
||||
Some(base_reg) => program.emit_insn_with_label_dependency(
|
||||
Insn::Ne {
|
||||
lhs: base_reg,
|
||||
rhs: expr_reg,
|
||||
target_pc: next_case_label,
|
||||
},
|
||||
next_case_label,
|
||||
),
|
||||
// CASE WHEN 0 THEN 0 ELSE 1 becomes ifnot 0 branch to next clause
|
||||
None => program.emit_insn_with_label_dependency(
|
||||
Insn::IfNot {
|
||||
reg: expr_reg,
|
||||
target_pc: next_case_label,
|
||||
null_reg: 1,
|
||||
},
|
||||
next_case_label,
|
||||
),
|
||||
};
|
||||
// THEN...
|
||||
translate_expr(
|
||||
program,
|
||||
referenced_tables,
|
||||
then_expr,
|
||||
target_register,
|
||||
precomputed_exprs_to_registers,
|
||||
)?;
|
||||
program.emit_insn_with_label_dependency(
|
||||
Insn::Goto {
|
||||
target_pc: return_label,
|
||||
},
|
||||
return_label,
|
||||
);
|
||||
// This becomes either the next WHEN, or in the last WHEN/THEN, we're
|
||||
// assured to have at least one instruction corresponding to the ELSE immediately follow.
|
||||
program.preassign_label_to_next_insn(next_case_label);
|
||||
next_case_label = program.allocate_label();
|
||||
}
|
||||
match else_expr {
|
||||
Some(expr) => {
|
||||
translate_expr(
|
||||
program,
|
||||
referenced_tables,
|
||||
expr,
|
||||
target_register,
|
||||
precomputed_exprs_to_registers,
|
||||
)?;
|
||||
}
|
||||
// If ELSE isn't specified, it means ELSE null.
|
||||
None => {
|
||||
program.emit_insn(Insn::Null {
|
||||
dest: target_register,
|
||||
dest_end: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
program.resolve_label(return_label, program.offset());
|
||||
Ok(target_register)
|
||||
}
|
||||
ast::Expr::Cast { expr, type_name } => {
|
||||
let type_name = type_name.as_ref().unwrap(); // TODO: why is this optional?
|
||||
let reg_expr = program.alloc_register();
|
||||
|
||||
@@ -599,6 +599,7 @@ impl ProgramState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Program {
|
||||
pub max_registers: usize,
|
||||
pub insns: Vec<Insn>,
|
||||
|
||||
@@ -57,4 +57,20 @@ do_execsql_test seekrowid {
|
||||
|
||||
do_execsql_test select_parenthesized {
|
||||
select (price + 100) from products limit 1;
|
||||
} {179.0}
|
||||
} {179.0}
|
||||
|
||||
do_execsql_test select_case_base_else {
|
||||
select case when 0 then 'false' when 1 then 'true' else 'null' end;
|
||||
} {true}
|
||||
|
||||
do_execsql_test select_case_noelse_null {
|
||||
select case when 0 then 0 end;
|
||||
} {}
|
||||
|
||||
do_execsql_test select_base_case_else {
|
||||
select case 1 when 0 then 'zero' when 1 then 'one' else 'two' end;
|
||||
} {one}
|
||||
|
||||
do_execsql_test select_base_case_noelse_null {
|
||||
select case 'null else' when 0 then 0 when 1 then 1 end;
|
||||
} {}
|
||||
|
||||
Reference in New Issue
Block a user