Add support for nullif scalar function

This commit is contained in:
Kim Seon Woo
2024-08-20 18:36:06 +02:00
parent 9404a561a9
commit 8bb2a48cb6
5 changed files with 84 additions and 8 deletions

View File

@@ -74,7 +74,7 @@ This document describes the SQLite compatibility status of Limbo:
| iif(X,Y,Z) | No | |
| instr(X,Y) | No | |
| last_insert_rowid() | No | |
| length(X) | No | |
| length(X) | Yes | |
| like(X,Y) | No | |
| like(X,Y,Z) | No | |
| likelihood(X,Y) | No | |
@@ -86,7 +86,7 @@ This document describes the SQLite compatibility status of Limbo:
| ltrim(X,Y) | Yes | |
| max(X,Y,...) | Yes | |
| min(X,Y,...) | Yes | |
| nullif(X,Y) | No | |
| nullif(X,Y) | Yes | |
| octet_length(X) | No | |
| printf(FORMAT,...) | No | |
| quote(X) | Yes | |

View File

@@ -56,6 +56,7 @@ pub enum ScalarFunc {
Length,
Min,
Max,
Nullif,
Substr,
Substring,
Date,
@@ -83,6 +84,7 @@ impl ToString for ScalarFunc {
ScalarFunc::Length => "length".to_string(),
ScalarFunc::Min => "min".to_string(),
ScalarFunc::Max => "max".to_string(),
ScalarFunc::Nullif => "nullif".to_string(),
ScalarFunc::Substr => "substr".to_string(),
ScalarFunc::Substring => "substring".to_string(),
ScalarFunc::Date => "date".to_string(),
@@ -110,6 +112,7 @@ impl Func {
"max" if arg_count > 1 => Ok(Func::Scalar(ScalarFunc::Max)),
"min" if arg_count == 0 || arg_count == 1 => Ok(Func::Agg(AggFunc::Min)),
"min" if arg_count > 1 => Ok(Func::Scalar(ScalarFunc::Min)),
"nullif" if arg_count == 2 => Ok(Func::Scalar(ScalarFunc::Nullif)),
"string_agg" => Ok(Func::Agg(AggFunc::StringAgg)),
"sum" => Ok(Func::Agg(AggFunc::Sum)),
"total" => Ok(Func::Agg(AggFunc::Total)),

View File

@@ -1067,6 +1067,43 @@ pub fn translate_expr(
});
Ok(target_register)
}
ScalarFunc::Nullif => {
let args = if let Some(args) = args {
if args.len() != 2 {
crate::bail_parse_error!(
"nullif function must have two argument"
);
}
args
} else {
crate::bail_parse_error!("nullif function with no arguments");
};
let func_reg = program.alloc_register();
let first_reg = program.alloc_register();
translate_expr(
program,
referenced_tables,
&args[0],
first_reg,
cursor_hint,
)?;
let second_reg = program.alloc_register();
translate_expr(
program,
referenced_tables,
&args[1],
second_reg,
cursor_hint,
)?;
program.emit_insn(Insn::Function {
start_reg: func_reg,
dest: target_register,
func: crate::vdbe::Func::Scalar(srf),
});
Ok(target_register)
}
}
}
None => {

View File

@@ -1321,6 +1321,13 @@ impl Program {
}
state.pc += 1;
}
Func::Scalar(ScalarFunc::Nullif) => {
let start_reg = *start_reg;
let first_value = &state.registers[start_reg + 1];
let second_value = &state.registers[start_reg + 2];
state.registers[*dest] = exec_nullif(first_value, second_value);
state.pc += 1;
}
Func::Scalar(ScalarFunc::Substr) | Func::Scalar(ScalarFunc::Substring) => {
let start_reg = *start_reg;
let str_value = &state.registers[start_reg];
@@ -1717,6 +1724,17 @@ fn exec_minmax<'a>(
regs.into_iter().reduce(|a, b| op(a, b)).cloned()
}
fn exec_nullif(
first_value: &OwnedValue,
second_value: &OwnedValue,
) -> OwnedValue {
if first_value != second_value {
first_value.clone()
} else {
OwnedValue::Null
}
}
fn exec_substring(
str_value: &OwnedValue,
start_value: &OwnedValue,
@@ -1865,12 +1883,7 @@ fn exec_if(reg: &OwnedValue, null_reg: &OwnedValue, not: bool) -> bool {
#[cfg(test)]
mod tests {
use super::{
exec_abs, exec_char, exec_if, exec_length, exec_like, exec_lower, exec_ltrim, exec_minmax,
exec_quote, exec_random, exec_round, exec_rtrim, exec_substring, exec_trim, exec_unicode,
exec_upper, get_new_rowid, Cursor, CursorResult, LimboError, OwnedRecord, OwnedValue,
Result,
};
use super::{exec_abs, exec_char, exec_if, exec_length, exec_like, exec_lower, exec_ltrim, exec_minmax, exec_nullif, exec_quote, exec_random, exec_round, exec_rtrim, exec_substring, exec_trim, exec_unicode, exec_upper, get_new_rowid, Cursor, CursorResult, LimboError, OwnedRecord, OwnedValue, Result};
use mockall::{mock, predicate};
use rand::{rngs::mock::StepRng, thread_rng};
use std::{cell::Ref, rc::Rc};
@@ -2298,6 +2311,17 @@ mod tests {
assert!(!exec_if(&reg, &null_reg, true));
}
#[test]
fn test_nullif() {
assert_eq!(exec_nullif(&OwnedValue::Integer(1), &OwnedValue::Integer(1)), OwnedValue::Null);
assert_eq!(exec_nullif(&OwnedValue::Float(1.1), &OwnedValue::Float(1.1)), OwnedValue::Null);
assert_eq!(exec_nullif(&OwnedValue::Text(Rc::new("limbo".to_string())), &OwnedValue::Text(Rc::new("limbo".to_string()))), OwnedValue::Null);
assert_eq!(exec_nullif(&OwnedValue::Integer(1), &OwnedValue::Integer(2)), OwnedValue::Integer(1));
assert_eq!(exec_nullif(&OwnedValue::Float(1.1), &OwnedValue::Float(1.2)), OwnedValue::Float(1.1));
assert_eq!(exec_nullif(&OwnedValue::Text(Rc::new("limbo".to_string())), &OwnedValue::Text(Rc::new("limb".to_string()))), OwnedValue::Text(Rc::new("limbo".to_string())));
}
#[test]
fn test_substring() {
let str_value = OwnedValue::Text(Rc::new("limbo".to_string()));

View File

@@ -279,6 +279,18 @@ do_execsql_test max-null {
select max(null,null)
} {}
do_execsql_test nullif {
select nullif(1, 2)
} {1}
do_execsql_test nullif {
select nullif(1, 1)
} {}
do_execsql_test nullif {
select nullif('limbo', 'limbo')
} {}
do_execsql_test substr-3-args {
SELECT substr('limbo', 1, 3);
} {lim}