mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 17:05:36 +01:00
Merge 'Implement json_quote' from Pedro Muniz
Hi! This is my first PR on the project, so I apologize if I did not follow a convention from the project. #127 This PR implements json_quote as specified in their source: https://www.sqlite.org/json1.html#jquote. It follows the internal doc guidelines for implementing functions. Most tests were added from sqlite test suite for json_quote, while some others were added by me. Sqlite test suite for json_quote depends on json_valid to test for correct escape control characters, so that specific test at the moment cannot be done the same way. Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Reviewed-by: Sonny (@sonhmai) Closes #763
This commit is contained in:
@@ -380,7 +380,7 @@ Modifiers:
|
||||
| json_type(json,path) | Yes | |
|
||||
| json_valid(json) | Yes | |
|
||||
| json_valid(json,flags) | | |
|
||||
| json_quote(value) | | |
|
||||
| json_quote(value) | Yes | |
|
||||
| json_group_array(value) | | |
|
||||
| jsonb_group_array(value) | | |
|
||||
| json_group_object(label,value) | | |
|
||||
|
||||
4
Makefile
4
Makefile
@@ -89,3 +89,7 @@ test-time:
|
||||
test-sqlite3: limbo-c
|
||||
LIBS="$(SQLITE_LIB)" HEADERS="$(SQLITE_LIB_HEADERS)" make -C sqlite3/tests test
|
||||
.PHONY: test-sqlite3
|
||||
|
||||
test-json:
|
||||
SQLITE_EXEC=$(SQLITE_EXEC) ./testing/json.test
|
||||
.PHONY: test-json
|
||||
|
||||
@@ -84,6 +84,7 @@ pub enum JsonFunc {
|
||||
JsonRemove,
|
||||
JsonPretty,
|
||||
JsonSet,
|
||||
JsonQuote,
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
@@ -107,6 +108,7 @@ impl Display for JsonFunc {
|
||||
Self::JsonRemove => "json_remove".to_string(),
|
||||
Self::JsonPretty => "json_pretty".to_string(),
|
||||
Self::JsonSet => "json_set".to_string(),
|
||||
Self::JsonQuote => "json_quote".to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -568,6 +570,8 @@ impl Func {
|
||||
"json_pretty" => Ok(Self::Json(JsonFunc::JsonPretty)),
|
||||
#[cfg(feature = "json")]
|
||||
"json_set" => Ok(Self::Json(JsonFunc::JsonSet)),
|
||||
#[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)),
|
||||
|
||||
@@ -674,6 +674,43 @@ pub fn is_json_valid(json_value: &OwnedValue) -> crate::Result<OwnedValue> {
|
||||
}
|
||||
}
|
||||
|
||||
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 mut escaped_value = String::with_capacity(t.value.len() + 4);
|
||||
escaped_value.push('"');
|
||||
|
||||
for c in t.as_str().chars() {
|
||||
match c {
|
||||
'"' | '\\' | '\n' | '\r' | '\t' | '\u{0008}' | '\u{000c}' => {
|
||||
escaped_value.push('\\');
|
||||
escaped_value.push(c);
|
||||
}
|
||||
c => escaped_value.push(c),
|
||||
}
|
||||
}
|
||||
escaped_value.push('"');
|
||||
|
||||
Ok(OwnedValue::Text(Text::new(Rc::new(escaped_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 => Ok(OwnedValue::Text(Text::new(Rc::new("null".to_string())))),
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -975,7 +975,7 @@ pub fn translate_expr(
|
||||
|
||||
translate_function(
|
||||
program,
|
||||
&args,
|
||||
args,
|
||||
referenced_tables,
|
||||
resolver,
|
||||
target_register,
|
||||
@@ -1017,6 +1017,17 @@ pub fn translate_expr(
|
||||
});
|
||||
Ok(target_register)
|
||||
}
|
||||
JsonFunc::JsonQuote => {
|
||||
let args = expect_arguments_exact!(args, 1, j);
|
||||
translate_function(
|
||||
program,
|
||||
args,
|
||||
referenced_tables,
|
||||
resolver,
|
||||
target_register,
|
||||
func_ctx,
|
||||
)
|
||||
}
|
||||
JsonFunc::JsonPretty => {
|
||||
let args = expect_arguments_max!(args, 2, j);
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ use crate::{
|
||||
function::JsonFunc, json::get_json, json::is_json_valid, json::json_array,
|
||||
json::json_array_length, json::json_arrow_extract, json::json_arrow_shift_extract,
|
||||
json::json_error_position, json::json_extract, json::json_object, json::json_patch,
|
||||
json::json_remove, json::json_set, json::json_type,
|
||||
json::json_quote, json::json_remove, json::json_set, json::json_type,
|
||||
};
|
||||
use crate::{resolve_ext_path, Connection, Result, TransactionState, DATABASE_VERSION};
|
||||
use insn::{
|
||||
@@ -1973,6 +1973,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 => {
|
||||
|
||||
@@ -878,3 +878,38 @@ do_execsql_test json_set_add_array_in_array_in_nested_object {
|
||||
do_execsql_test json_set_add_array_in_array_in_nested_object_out_of_bounds {
|
||||
SELECT json_set('{}', '$.object[123].another', 'value', '$.field', 'value');
|
||||
} {{{"field":"value"}}}
|
||||
|
||||
# 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 and in some syntax that is not implemented
|
||||
# yet in limbo.
|
||||
# See https://github.com/sqlite/sqlite/blob/255548562b125e6c148bb27d49aaa01b2fe61dba/test/json102.test#L690
|
||||
# So for now not all control characters escaped are tested
|
||||
|
||||
# do_execsql_test json102-1501 {
|
||||
# WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<0x1f)
|
||||
# SELECT sum(json_valid(json_quote('a'||char(x)||'z'))) FROM c ORDER BY x;
|
||||
# } {31}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user