mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 17:05:36 +01:00
Merge 'Add instr(X,Y) scalar function' from Lauri Virtanen
Relates to issue #144 > ## instr(X,Y) > > The instr(X,Y) function finds the first occurrence of string Y within string X and returns the number of prior characters plus 1, or 0 if Y is nowhere found within X. Or, if X and Y are both BLOBs, then instr(X,Y) returns one more than the number bytes prior to the first occurrence of Y, or 0 if Y does not occur anywhere within X. If both arguments X and Y to instr(X,Y) are non-NULL and are not BLOBs then both are interpreted as strings. If either X or Y are NULL in instr(X,Y) then the result is NULL. Closes #357
This commit is contained in:
@@ -72,7 +72,7 @@ This document describes the SQLite compatibility status of Limbo:
|
||||
| hex(X) | Yes | |
|
||||
| ifnull(X,Y) | Yes | |
|
||||
| iif(X,Y,Z) | No | |
|
||||
| instr(X,Y) | No | |
|
||||
| instr(X,Y) | Yes | |
|
||||
| last_insert_rowid() | No | |
|
||||
| length(X) | Yes | |
|
||||
| like(X,Y) | No | |
|
||||
|
||||
@@ -53,6 +53,7 @@ pub enum ScalarFunc {
|
||||
ConcatWs,
|
||||
Glob,
|
||||
IfNull,
|
||||
Instr,
|
||||
Like,
|
||||
Abs,
|
||||
Upper,
|
||||
@@ -91,6 +92,7 @@ impl Display for ScalarFunc {
|
||||
ScalarFunc::ConcatWs => "concat_ws".to_string(),
|
||||
ScalarFunc::Glob => "glob".to_string(),
|
||||
ScalarFunc::IfNull => "ifnull".to_string(),
|
||||
ScalarFunc::Instr => "instr".to_string(),
|
||||
ScalarFunc::Like => "like(2)".to_string(),
|
||||
ScalarFunc::Abs => "abs".to_string(),
|
||||
ScalarFunc::Upper => "upper".to_string(),
|
||||
@@ -166,6 +168,7 @@ impl Func {
|
||||
"concat_ws" => Ok(Func::Scalar(ScalarFunc::ConcatWs)),
|
||||
"glob" => Ok(Func::Scalar(ScalarFunc::Glob)),
|
||||
"ifnull" => Ok(Func::Scalar(ScalarFunc::IfNull)),
|
||||
"instr" => Ok(Func::Scalar(ScalarFunc::Instr)),
|
||||
"like" => Ok(Func::Scalar(ScalarFunc::Like)),
|
||||
"abs" => Ok(Func::Scalar(ScalarFunc::Abs)),
|
||||
"upper" => Ok(Func::Scalar(ScalarFunc::Upper)),
|
||||
|
||||
@@ -1324,16 +1324,20 @@ pub fn translate_expr(
|
||||
});
|
||||
Ok(target_register)
|
||||
}
|
||||
ScalarFunc::Nullif => {
|
||||
ScalarFunc::Nullif | ScalarFunc::Instr => {
|
||||
let args = if let Some(args) = args {
|
||||
if args.len() != 2 {
|
||||
crate::bail_parse_error!(
|
||||
"nullif function must have two argument"
|
||||
"{} function must have two argument",
|
||||
srf.to_string()
|
||||
);
|
||||
}
|
||||
args
|
||||
} else {
|
||||
crate::bail_parse_error!("nullif function with no arguments");
|
||||
crate::bail_parse_error!(
|
||||
"{} function with no arguments",
|
||||
srf.to_string()
|
||||
);
|
||||
};
|
||||
|
||||
let first_reg = program.alloc_register();
|
||||
|
||||
156
core/vdbe/mod.rs
156
core/vdbe/mod.rs
@@ -1506,6 +1506,12 @@ impl Program {
|
||||
state.registers[*dest] = result;
|
||||
}
|
||||
ScalarFunc::IfNull => {}
|
||||
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];
|
||||
@@ -2170,6 +2176,43 @@ fn exec_substring(
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_instr(reg: &OwnedValue, pattern: &OwnedValue) -> OwnedValue {
|
||||
if reg == &OwnedValue::Null || pattern == &OwnedValue::Null {
|
||||
return OwnedValue::Null;
|
||||
}
|
||||
|
||||
if let (OwnedValue::Blob(reg), OwnedValue::Blob(pattern)) = (reg, pattern) {
|
||||
let result = reg
|
||||
.windows(pattern.len())
|
||||
.position(|window| window == **pattern)
|
||||
.map_or(0, |i| i + 1);
|
||||
return OwnedValue::Integer(result as i64);
|
||||
}
|
||||
|
||||
let reg_str;
|
||||
let reg = match reg {
|
||||
OwnedValue::Text(s) => s.as_str(),
|
||||
_ => {
|
||||
reg_str = reg.to_string();
|
||||
reg_str.as_str()
|
||||
}
|
||||
};
|
||||
|
||||
let pattern_str;
|
||||
let pattern = match pattern {
|
||||
OwnedValue::Text(s) => s.as_str(),
|
||||
_ => {
|
||||
pattern_str = pattern.to_string();
|
||||
pattern_str.as_str()
|
||||
}
|
||||
};
|
||||
|
||||
match reg.find(pattern) {
|
||||
Some(position) => OwnedValue::Integer(position as i64 + 1),
|
||||
None => OwnedValue::Integer(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_typeof(reg: &OwnedValue) -> OwnedValue {
|
||||
match reg {
|
||||
OwnedValue::Null => OwnedValue::Text(Rc::new("null".to_string())),
|
||||
@@ -2338,11 +2381,11 @@ fn execute_sqlite_version(version_integer: i64) -> String {
|
||||
mod tests {
|
||||
|
||||
use super::{
|
||||
exec_abs, exec_char, exec_hex, exec_if, exec_length, exec_like, exec_lower, exec_ltrim,
|
||||
exec_max, exec_min, exec_nullif, exec_quote, exec_random, exec_randomblob, exec_round,
|
||||
exec_rtrim, exec_sign, exec_substring, exec_trim, exec_typeof, exec_unhex, exec_unicode,
|
||||
exec_upper, exec_zeroblob, execute_sqlite_version, get_new_rowid, AggContext, Cursor,
|
||||
CursorResult, LimboError, OwnedRecord, OwnedValue, Result,
|
||||
exec_abs, exec_char, exec_hex, exec_if, exec_instr, exec_length, exec_like, exec_lower,
|
||||
exec_ltrim, exec_max, exec_min, exec_nullif, exec_quote, exec_random, exec_randomblob,
|
||||
exec_round, exec_rtrim, exec_sign, exec_substring, exec_trim, exec_typeof, exec_unhex,
|
||||
exec_unicode, exec_upper, exec_zeroblob, execute_sqlite_version, get_new_rowid, AggContext,
|
||||
Cursor, CursorResult, LimboError, OwnedRecord, OwnedValue, Result,
|
||||
};
|
||||
use mockall::{mock, predicate};
|
||||
use rand::{rngs::mock::StepRng, thread_rng};
|
||||
@@ -2994,6 +3037,109 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec_instr() {
|
||||
let input = OwnedValue::Text(Rc::new(String::from("limbo")));
|
||||
let pattern = OwnedValue::Text(Rc::new(String::from("im")));
|
||||
let expected = OwnedValue::Integer(2);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Text(Rc::new(String::from("limbo")));
|
||||
let pattern = OwnedValue::Text(Rc::new(String::from("limbo")));
|
||||
let expected = OwnedValue::Integer(1);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Text(Rc::new(String::from("limbo")));
|
||||
let pattern = OwnedValue::Text(Rc::new(String::from("o")));
|
||||
let expected = OwnedValue::Integer(5);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Text(Rc::new(String::from("liiiiimbo")));
|
||||
let pattern = OwnedValue::Text(Rc::new(String::from("ii")));
|
||||
let expected = OwnedValue::Integer(2);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Text(Rc::new(String::from("limbo")));
|
||||
let pattern = OwnedValue::Text(Rc::new(String::from("limboX")));
|
||||
let expected = OwnedValue::Integer(0);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Text(Rc::new(String::from("limbo")));
|
||||
let pattern = OwnedValue::Text(Rc::new(String::from("")));
|
||||
let expected = OwnedValue::Integer(1);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Text(Rc::new(String::from("")));
|
||||
let pattern = OwnedValue::Text(Rc::new(String::from("limbo")));
|
||||
let expected = OwnedValue::Integer(0);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Text(Rc::new(String::from("")));
|
||||
let pattern = OwnedValue::Text(Rc::new(String::from("")));
|
||||
let expected = OwnedValue::Integer(1);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Null;
|
||||
let pattern = OwnedValue::Null;
|
||||
let expected = OwnedValue::Null;
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Text(Rc::new(String::from("limbo")));
|
||||
let pattern = OwnedValue::Null;
|
||||
let expected = OwnedValue::Null;
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Null;
|
||||
let pattern = OwnedValue::Text(Rc::new(String::from("limbo")));
|
||||
let expected = OwnedValue::Null;
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Integer(123);
|
||||
let pattern = OwnedValue::Integer(2);
|
||||
let expected = OwnedValue::Integer(2);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Integer(123);
|
||||
let pattern = OwnedValue::Integer(5);
|
||||
let expected = OwnedValue::Integer(0);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Float(12.34);
|
||||
let pattern = OwnedValue::Float(2.3);
|
||||
let expected = OwnedValue::Integer(2);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Float(12.34);
|
||||
let pattern = OwnedValue::Float(5.6);
|
||||
let expected = OwnedValue::Integer(0);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Float(12.34);
|
||||
let pattern = OwnedValue::Text(Rc::new(String::from(".")));
|
||||
let expected = OwnedValue::Integer(3);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Blob(Rc::new(vec![1, 2, 3, 4, 5]));
|
||||
let pattern = OwnedValue::Blob(Rc::new(vec![3, 4]));
|
||||
let expected = OwnedValue::Integer(3);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Blob(Rc::new(vec![1, 2, 3, 4, 5]));
|
||||
let pattern = OwnedValue::Blob(Rc::new(vec![3, 2]));
|
||||
let expected = OwnedValue::Integer(0);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Blob(Rc::new(vec![0x61, 0x62, 0x63, 0x64, 0x65]));
|
||||
let pattern = OwnedValue::Text(Rc::new(String::from("cd")));
|
||||
let expected = OwnedValue::Integer(3);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
|
||||
let input = OwnedValue::Text(Rc::new(String::from("abcde")));
|
||||
let pattern = OwnedValue::Blob(Rc::new(vec![0x63, 0x64]));
|
||||
let expected = OwnedValue::Integer(3);
|
||||
assert_eq!(exec_instr(&input, &pattern), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec_sign() {
|
||||
let input = OwnedValue::Integer(42);
|
||||
|
||||
@@ -75,6 +75,58 @@ do_execsql_test ifnull-2 {
|
||||
select ifnull(null, 2);
|
||||
} {2}
|
||||
|
||||
do_execsql_test instr-str {
|
||||
select instr('limbo', 'im');
|
||||
} {2}
|
||||
|
||||
do_execsql_test instr-str-not-found {
|
||||
select instr('limbo', 'xyz');
|
||||
} {0}
|
||||
|
||||
do_execsql_test instr-blob {
|
||||
select instr(x'000102', x'01');
|
||||
} {2}
|
||||
|
||||
do_execsql_test instr-blob-not-found {
|
||||
select instr(x'000102', x'10');
|
||||
} {0}
|
||||
|
||||
do_execsql_test instr-null {
|
||||
select instr(null, 'limbo');
|
||||
} {}
|
||||
|
||||
do_execsql_test instr-integer {
|
||||
select instr(123, 2);
|
||||
} {2}
|
||||
|
||||
do_execsql_test instr-integer-not-found {
|
||||
select instr(123, 5);
|
||||
} {0}
|
||||
|
||||
do_execsql_test instr-integer-leading-zeros {
|
||||
select instr(0001, 0);
|
||||
} {0}
|
||||
|
||||
do_execsql_test instr-real {
|
||||
select instr(12.34, 2.3);
|
||||
} {2}
|
||||
|
||||
do_execsql_test instr-real-not-found {
|
||||
select instr(12.34, 5);
|
||||
} {0}
|
||||
|
||||
do_execsql_test instr-real-trailing-zeros {
|
||||
select instr(1.10000, 0);
|
||||
} {0}
|
||||
|
||||
do_execsql_test instr-blob {
|
||||
select instr(x'01020304', x'02');
|
||||
} {2}
|
||||
|
||||
do_execsql_test instr-blob-not-found {
|
||||
select instr(x'01020304', x'05');
|
||||
} {0}
|
||||
|
||||
do_execsql_test upper {
|
||||
select upper('Limbo')
|
||||
} {LIMBO}
|
||||
|
||||
Reference in New Issue
Block a user