diff --git a/COMPAT.md b/COMPAT.md index 9290103eb..5cf7b69bf 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -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 | | diff --git a/core/function.rs b/core/function.rs index 3eb110007..304d155cf 100644 --- a/core/function.rs +++ b/core/function.rs @@ -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)), diff --git a/core/translate/expr.rs b/core/translate/expr.rs index e1a5adfd3..d8c0c2604 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -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(); diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index fcd1d2690..20b923dfe 100644 --- a/core/vdbe/mod.rs +++ b/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); diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index e2cbf489a..5b6a64123 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -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}