Skip to content

Commit

Permalink
Implement json_quote
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrocarlo committed Jan 21, 2025
1 parent c27427d commit 040004d
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 1 deletion.
4 changes: 4 additions & 0 deletions core/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub enum JsonFunc {
JsonObject,
JsonType,
JsonErrorPosition,
JsonQuote,
}

#[cfg(feature = "json")]
Expand All @@ -97,6 +98,7 @@ impl Display for JsonFunc {
Self::JsonObject => "json_object".to_string(),
Self::JsonType => "json_type".to_string(),
Self::JsonErrorPosition => "json_error_position".to_string(),
Self::JsonQuote => "json_quote".to_string(),
}
)
}
Expand Down Expand Up @@ -517,6 +519,8 @@ impl Func {
"json_type" => Ok(Func::Json(JsonFunc::JsonType)),
#[cfg(feature = "json")]
"json_error_position" => Ok(Self::Json(JsonFunc::JsonErrorPosition)),
#[cfg(feature = "json")]
"json_quote" => Ok(Self::Json(JsonFunc::JsonQuote)),
"unixepoch" => Ok(Self::Scalar(ScalarFunc::UnixEpoch)),
"julianday" => Ok(Self::Scalar(ScalarFunc::JulianDay)),
"hex" => Ok(Self::Scalar(ScalarFunc::Hex)),
Expand Down
31 changes: 31 additions & 0 deletions core/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub use crate::json::ser::to_string;
use crate::types::{LimboText, OwnedValue, TextSubtype};
use indexmap::IndexMap;
use jsonb::Error as JsonbError;
use ser::escape;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
Expand Down Expand Up @@ -448,6 +449,36 @@ pub fn json_object(values: &[OwnedValue]) -> crate::Result<OwnedValue> {
Ok(OwnedValue::Text(LimboText::json(Rc::new(result))))
}

pub fn json_quote(value: &OwnedValue) -> crate::Result<OwnedValue> {
match value {
OwnedValue::Text(ref t) => {
// If X is a JSON value returned by another JSON function,
// then this function is a no-op
if t.subtype == TextSubtype::Json {
// Should just return the json value with no quotes
return Ok(value.to_owned());
}

let quoted_value = format!("\"{}\"", escape(&t.value));

Ok(OwnedValue::Text(LimboText::new(Rc::new(quoted_value))))
}
// Numbers are unquoted in json
OwnedValue::Integer(ref int) => Ok(OwnedValue::Integer(int.to_owned())),
OwnedValue::Float(ref float) => Ok(OwnedValue::Float(float.to_owned())),
OwnedValue::Blob(_) => crate::bail_constraint_error!("JSON cannot hold BLOB values"),
OwnedValue::Null => {
let null_value = "null".to_string();

Ok(OwnedValue::Text(LimboText::new(Rc::new(null_value))))
}
_ => {
// TODO not too sure what message should be here
crate::bail_parse_error!("Syntax error");
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
6 changes: 5 additions & 1 deletion core/json/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,11 @@ impl ser::SerializeStructVariant for &mut Serializer {
}
}

fn escape(v: &str) -> String {
// TODO not sure if this function can be public
// But wanted to avoid having the same copy of the function in two different places
// Maybe this function could be placed in mod file instead
// As it is used by the json quote funcion
pub fn escape(v: &str) -> String {
v.chars()
.flat_map(|c| match c {
'"' => vec!['\\', c],
Expand Down
12 changes: 12 additions & 0 deletions core/translate/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,18 @@ pub fn translate_expr(
JsonFunc::JsonObject => {
let args = expect_arguments_even!(args, j);

translate_function(
program,
&args,
referenced_tables,
resolver,
target_register,
func_ctx,
)
}
JsonFunc::JsonQuote => {
let args = expect_arguments_exact!(args, 1, j);

translate_function(
program,
&args,
Expand Down
9 changes: 9 additions & 0 deletions core/vdbe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub mod sorter;
use crate::error::{LimboError, SQLITE_CONSTRAINT_PRIMARYKEY};
use crate::ext::ExtValue;
use crate::function::{AggFunc, ExtFunc, FuncCtx, MathFunc, MathFuncArity, ScalarFunc};
use crate::json::json_quote;
use crate::pseudo::PseudoCursor;
use crate::result::LimboResult;
use crate::storage::sqlite3_ondisk::DatabaseHeader;
Expand Down Expand Up @@ -1698,6 +1699,14 @@ impl Program {
Err(e) => return Err(e),
}
}
JsonFunc::JsonQuote => {
let json_value = &state.registers[*start_reg];

match json_quote(json_value) {
Ok(result) => state.registers[*dest] = result,
Err(e) => return Err(e),
}
}
},
crate::function::Func::Scalar(scalar_func) => match scalar_func {
ScalarFunc::Cast => {
Expand Down
41 changes: 41 additions & 0 deletions testing/json.test
Original file line number Diff line number Diff line change
Expand Up @@ -544,3 +544,44 @@ do_execsql_test json_from_json_object {
#do_execsql_test json_object_duplicated_keys {
# SELECT json_object('key', 'value', 'key', 'value2');
#} {{{"key":"value2"}}}

# The json_quote() function transforms an SQL value into a JSON value.
# String values are quoted and interior quotes are escaped. NULL values
# are rendered as the unquoted string "null".
#
do_execsql_test json_quote_string_literal {
SELECT json_quote('abc"xyz');
} {{"abc\"xyz"}}
do_execsql_test json_quote_float {
SELECT json_quote(3.14159);
} {3.14159}
do_execsql_test json_quote_integer {
SELECT json_quote(12345);
} {12345}
do_execsql_test json_quote_null {
SELECT json_quote(null);
} {"null"}
do_execsql_test json_quote_null_caps {
SELECT json_quote(NULL);
} null
do_execsql_test json_quote_json_value {
SELECT json_quote(json('{a:1, b: "test"}'));
} {{{"a":1,"b":"test"}}}
# Escape character tests in sqlite source depend on json_valid
# See https://github.com/sqlite/sqlite/blob/255548562b125e6c148bb27d49aaa01b2fe61dba/test/json102.test#L690
# So for now not all control characters escaped are tested
# TODO No catchsql tests function to test these on
#do_catchsql_test json_quote_blob {
# SELECT json_quote(x'3031323334');
#} {1 {JSON cannot hold BLOB values}}
#do_catchsql_test json_quote_more_than_one_arg {
# SELECT json_quote(123,456)
#} {1 {json_quote function called with not exactly 1 arguments}}
#do_catchsql_test json_quote_no_args {
# SELECT json_quote()
#} {1 {json_quote function with no arguments}}

0 comments on commit 040004d

Please sign in to comment.