Merge 'Implement support for iif()' from Alex Miller

In sqlite, iif() looks like:
```
sqlite> create table iiftest(a int, b int, c int);
sqlite> explain select iif(a,b,c) from iiftest;
addr  opcode         p1    p2    p3    p4             p5  comment
----  -------------  ----  ----  ----  -------------  --  -------------
0     Init           0     11    0                    0   Start at 11
1     OpenRead       0     2     0     3              0   root=2 iDb=0; iiftest
2     Rewind         0     10    0                    0
3       Column         0     0     2                    0   r[2]= cursor 0 column 0
4       IfNot          2     7     1                    0
5       Column         0     1     1                    0   r[1]= cursor 0 column 1
6       Goto           0     8     0                    0
7       Column         0     2     1                    0   r[1]= cursor 0 column 2
8       ResultRow      1     1     0                    0   output=r[1]
9     Next           0     3     0                    1
10    Halt           0     0     0                    0
11    Transaction    0     0     1     0              1   usesStmtJournal=0
12    Goto           0     1     0                    0
```
And with this change, in limbo it looks like:
```
addr  opcode             p1    p2    p3    p4             p5  comment
----  -----------------  ----  ----  ----  -------------  --  -------
0     Init               0     14    0                    0   Start at 14
1     OpenReadAsync      0     2     0                    0   table=iiftest, root=2
2     OpenReadAwait      0     0     0                    0
3     RewindAsync        0     0     0                    0
4     RewindAwait        0     13    0                    0   Rewind table iiftest
5       Column           0     0     2                    0   r[2]=iiftest.a
6       IfNot            2     9     1                    0   if !r[2] goto 9
7       Column           0     1     1                    0   r[1]=iiftest.b
8       Goto             0     10    0                    0
9       Column           0     2     1                    0   r[1]=iiftest.c
10      ResultRow        1     1     0                    0   output=r[1]
11    NextAsync          0     0     0                    0
12    NextAwait          0     5     0                    0
13    Halt               0     0     0                    0
14    Transaction        0     0     0                    0
15    Goto               0     1     0                    0
```
---
Rust commentary overly welcome, as idk what I'm doing in this language.

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #424
This commit is contained in:
Pekka Enberg
2024-12-12 10:46:22 +02:00
5 changed files with 69 additions and 7 deletions

View File

@@ -111,7 +111,7 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html).
| glob(X,Y) | Yes | |
| hex(X) | Yes | |
| ifnull(X,Y) | Yes | |
| iif(X,Y,Z) | No | |
| iif(X,Y,Z) | Yes | |
| instr(X,Y) | Yes | |
| last_insert_rowid() | Yes | |
| length(X) | Yes | |

View File

@@ -56,6 +56,7 @@ pub enum ScalarFunc {
ConcatWs,
Glob,
IfNull,
Iif,
Instr,
Like,
Abs,
@@ -98,6 +99,7 @@ impl Display for ScalarFunc {
ScalarFunc::ConcatWs => "concat_ws".to_string(),
ScalarFunc::Glob => "glob".to_string(),
ScalarFunc::IfNull => "ifnull".to_string(),
ScalarFunc::Iif => "iif".to_string(),
ScalarFunc::Instr => "instr".to_string(),
ScalarFunc::Like => "like(2)".to_string(),
ScalarFunc::Abs => "abs".to_string(),
@@ -178,6 +180,7 @@ impl Func {
"concat_ws" => Ok(Func::Scalar(ScalarFunc::ConcatWs)),
"glob" => Ok(Func::Scalar(ScalarFunc::Glob)),
"ifnull" => Ok(Func::Scalar(ScalarFunc::IfNull)),
"iif" => Ok(Func::Scalar(ScalarFunc::Iif)),
"instr" => Ok(Func::Scalar(ScalarFunc::Instr)),
"like" => Ok(Func::Scalar(ScalarFunc::Like)),
"abs" => Ok(Func::Scalar(ScalarFunc::Abs)),

View File

@@ -1071,6 +1071,56 @@ pub fn translate_expr(
Ok(target_register)
}
ScalarFunc::Iif => {
let args = match args {
Some(args) if args.len() == 3 => args,
_ => crate::bail_parse_error!(
"{} requires exactly 3 arguments",
srf.to_string()
),
};
let temp_reg = program.alloc_register();
translate_expr(
program,
referenced_tables,
&args[0],
temp_reg,
precomputed_exprs_to_registers,
)?;
let jump_target_when_false = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::IfNot {
reg: temp_reg,
target_pc: jump_target_when_false,
null_reg: 1,
},
jump_target_when_false,
);
translate_expr(
program,
referenced_tables,
&args[1],
target_register,
precomputed_exprs_to_registers,
)?;
let jump_target_result = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: jump_target_result,
},
jump_target_result,
);
program.resolve_label(jump_target_when_false, program.offset());
translate_expr(
program,
referenced_tables,
&args[2],
target_register,
precomputed_exprs_to_registers,
)?;
program.resolve_label(jump_target_result, program.offset());
Ok(target_register)
}
ScalarFunc::Glob | ScalarFunc::Like => {
let args = if let Some(args) = args {
if args.len() < 2 {

View File

@@ -2106,6 +2106,13 @@ impl Program {
state.registers[*dest] = result;
}
ScalarFunc::IfNull => {}
ScalarFunc::Iif => {}
ScalarFunc::Instr => {
let reg_value = &state.registers[*start_reg];
let pattern_value = &state.registers[*start_reg + 1];
let result = exec_instr(reg_value, pattern_value);
state.registers[*dest] = result;
}
ScalarFunc::LastInsertRowid => {
if let Some(conn) = self.connection.upgrade() {
state.registers[*dest] =
@@ -2114,12 +2121,6 @@ impl Program {
state.registers[*dest] = OwnedValue::Null;
}
}
ScalarFunc::Instr => {
let reg_value = &state.registers[*start_reg];
let pattern_value = &state.registers[*start_reg + 1];
let result = exec_instr(reg_value, pattern_value);
state.registers[*dest] = result;
}
ScalarFunc::Like => {
let pattern = &state.registers[*start_reg];
let text = &state.registers[*start_reg + 1];

View File

@@ -75,6 +75,14 @@ do_execsql_test ifnull-2 {
select ifnull(null, 2);
} {2}
do_execsql_test iif-true {
select iif(1, 'pass', 'fail');
} {pass}
do_execsql_test iif-false {
select iif(0, 'fail', 'pass');
} {pass}
do_execsql_test instr-str {
select instr('limbo', 'im');
} {2}