diff --git a/COMPAT.md b/COMPAT.md index 93f943238..086ce1667 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -89,7 +89,7 @@ This document describes the SQLite compatibility status of Limbo: | nullif(X,Y) | No | | | octet_length(X) | No | | | printf(FORMAT,...) | No | | -| quote(X) | No | | +| quote(X) | Yes | | | random() | Yes | | | randomblob(N) | No | | | replace(X,Y,Z) | No | | diff --git a/core/function.rs b/core/function.rs index 532f89234..9dbfa8693 100644 --- a/core/function.rs +++ b/core/function.rs @@ -61,6 +61,7 @@ pub enum ScalarFunc { Date, Time, Unicode, + Quote, } impl ToString for ScalarFunc { @@ -87,6 +88,7 @@ impl ToString for ScalarFunc { ScalarFunc::Date => "date".to_string(), ScalarFunc::Time => "time".to_string(), ScalarFunc::Unicode => "unicode".to_string(), + ScalarFunc::Quote => "quote".to_string(), } } } @@ -130,6 +132,7 @@ impl Func { "date" => Ok(Func::Scalar(ScalarFunc::Date)), "time" => Ok(Func::Scalar(ScalarFunc::Time)), "unicode" => Ok(Func::Scalar(ScalarFunc::Unicode)), + "quote" => Ok(Func::Scalar(ScalarFunc::Quote)), "json" => Ok(Func::Json(JsonFunc::JSON)), _ => Err(()), } diff --git a/core/translate/expr.rs b/core/translate/expr.rs index c18a4c7ab..71a1c29eb 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -302,7 +302,8 @@ pub fn translate_expr( | ScalarFunc::Lower | ScalarFunc::Upper | ScalarFunc::Length - | ScalarFunc::Unicode => { + | ScalarFunc::Unicode + | ScalarFunc::Quote => { let args = if let Some(args) = args { if args.len() != 1 { crate::bail_parse_error!( diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 77fc82454..0927ce0ef 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -1379,6 +1379,11 @@ impl Program { state.registers[*dest] = exec_unicode(reg_value); state.pc += 1; } + Func::Scalar(ScalarFunc::Quote) => { + let reg_value = state.registers[*start_reg].borrow_mut(); + state.registers[*dest] = exec_quote(reg_value); + state.pc += 1; + } }, Insn::InitCoroutine { yield_reg, @@ -1662,6 +1667,28 @@ fn exec_random() -> OwnedValue { OwnedValue::Integer(random_number) } +fn exec_quote(value: &OwnedValue) -> OwnedValue { + match value { + OwnedValue::Null => OwnedValue::Text(OwnedValue::Null.to_string().into()), + OwnedValue::Integer(_) | OwnedValue::Float(_) => value.to_owned(), + OwnedValue::Blob(_) => todo!(), + OwnedValue::Text(s) => { + let mut quoted = String::with_capacity(s.len() + 2); + quoted.push('\''); + for c in s.chars() { + if c == '\0' { + break; + } else { + quoted.push(c); + } + } + quoted.push('\''); + OwnedValue::Text(Rc::new(quoted)) + } + _ => OwnedValue::Null, // For unsupported types, return NULL + } +} + fn exec_char(values: Vec) -> OwnedValue { let result: String = values .iter() @@ -1839,8 +1866,9 @@ fn exec_if(reg: &OwnedValue, null_reg: &OwnedValue, not: bool) -> bool { mod tests { use super::{ exec_abs, exec_char, exec_if, exec_length, exec_like, exec_lower, exec_ltrim, exec_minmax, - exec_random, exec_round, exec_rtrim, exec_substring, exec_trim, exec_unicode, exec_upper, - get_new_rowid, Cursor, CursorResult, LimboError, OwnedRecord, OwnedValue, Result, + 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}; @@ -2003,6 +2031,21 @@ mod tests { assert_eq!(exec_length(&expected_blob), expected_len); } + #[test] + fn test_quote() { + let input = OwnedValue::Text(Rc::new(String::from("abc\0edf"))); + let expected = OwnedValue::Text(Rc::new(String::from("'abc'"))); + assert_eq!(exec_quote(&input), expected); + + let input = OwnedValue::Integer(123); + let expected = OwnedValue::Integer(123); + assert_eq!(exec_quote(&input), expected); + + let input = OwnedValue::Text(Rc::new(String::from("hello''world"))); + let expected = OwnedValue::Text(Rc::new(String::from("'hello''world'"))); + assert_eq!(exec_quote(&input), expected); + } + #[test] fn test_unicode() { assert_eq!( diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index c1de40ebe..7933df79f 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -495,3 +495,18 @@ do_execsql_test unicode-null { SELECT unicode(NULL); } {} +do_execsql_test quote-string-embedded-nul { + SELECT quote(concat('abc', char(0), 'def')) +} {'abc'} + +do_execsql_test quote-string { + SELECT quote('limbo') +} {'limbo'} + +do_execsql_test quote-null { + SELECT quote(null) +} {NULL} + +do_execsql_test quote-integer { + SELECT quote(123) +} {123}