Implement json_type

This commit is contained in:
Kacper Madej
2025-01-08 19:15:35 +07:00
parent bc1fd20892
commit eebf9bfaac
6 changed files with 129 additions and 34 deletions

View File

@@ -281,8 +281,8 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html).
| jsonb_replace(json,path,value,...) | | |
| json_set(json,path,value,...) | | |
| jsonb_set(json,path,value,...) | | |
| json_type(json) | | |
| json_type(json,path) | | |
| json_type(json) | Yes | |
| json_type(json,path) | Yes | |
| json_valid(json) | | |
| json_valid(json,flags) | | |
| json_quote(value) | | |

View File

@@ -27,6 +27,7 @@ pub enum JsonFunc {
JsonArray,
JsonExtract,
JsonArrayLength,
JsonType,
}
#[cfg(feature = "json")]
@@ -40,6 +41,7 @@ impl Display for JsonFunc {
Self::JsonArray => "json_array".to_string(),
Self::JsonExtract => "json_extract".to_string(),
Self::JsonArrayLength => "json_array_length".to_string(),
Self::JsonType => "json_type".to_string(),
}
)
}
@@ -371,6 +373,8 @@ impl Func {
"json_array" => Ok(Self::Json(JsonFunc::JsonArray)),
#[cfg(feature = "json")]
"json_extract" => Ok(Func::Json(JsonFunc::JsonExtract)),
#[cfg(feature = "json")]
"json_type" => Ok(Func::Json(JsonFunc::JsonType)),
"unixepoch" => Ok(Self::Scalar(ScalarFunc::UnixEpoch)),
"julianday" => Ok(Self::Scalar(ScalarFunc::JulianDay)),
"hex" => Ok(Self::Scalar(ScalarFunc::Hex)),

View File

@@ -121,17 +121,13 @@ pub fn json_array_length(
json_value: &OwnedValue,
json_path: Option<&OwnedValue>,
) -> crate::Result<OwnedValue> {
let path = match json_path {
Some(OwnedValue::Text(t)) => Some(t.value.to_string()),
Some(OwnedValue::Integer(i)) => Some(i.to_string()),
Some(OwnedValue::Float(f)) => Some(f.to_string()),
_ => None::<String>,
};
let json = get_json_value(json_value)?;
let arr_val = if let Some(path) = path {
&json_extract_single(&json, path.as_str())?
let arr_val = if let Some(path) = json_path {
match json_extract_single(&json, path)? {
Some(val) => val,
None => return Ok(OwnedValue::Null),
}
} else {
&json
};
@@ -161,10 +157,13 @@ pub fn json_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result<O
for path in paths {
match path {
OwnedValue::Text(p) => {
let extracted = json_extract_single(&json, p.value.as_str())?;
OwnedValue::Null => {
return Ok(OwnedValue::Null);
}
_ => {
let extracted = json_extract_single(&json, path)?.unwrap_or_else(|| &Val::Null);
if paths.len() == 1 && extracted == Val::Null {
if paths.len() == 1 && extracted == &Val::Null {
return Ok(OwnedValue::Null);
}
@@ -173,8 +172,6 @@ pub fn json_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result<O
result.push(',');
}
}
OwnedValue::Null => return Ok(OwnedValue::Null),
_ => crate::bail_constraint_error!("JSON path error near: {:?}", path.to_string()),
}
}
@@ -186,8 +183,49 @@ pub fn json_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result<O
Ok(OwnedValue::Text(LimboText::json(Rc::new(result))))
}
fn json_extract_single(json: &Val, path: &str) -> crate::Result<Val> {
let json_path = json_path(path)?;
pub fn json_type(value: &OwnedValue, path: Option<&OwnedValue>) -> crate::Result<OwnedValue> {
if let OwnedValue::Null = value {
return Ok(OwnedValue::Null);
}
let json = get_json_value(value)?;
let json = if let Some(path) = path {
match json_extract_single(&json, path)? {
Some(val) => val,
None => return Ok(OwnedValue::Null),
}
} else {
&json
};
let val = match json {
Val::Null => "null",
Val::Bool(v) => {
if *v {
"true"
} else {
"false"
}
}
Val::Integer(_) => "integer",
Val::Float(_) => "real",
Val::String(_) => "text",
Val::Array(_) => "array",
Val::Object(_) => "object",
};
Ok(OwnedValue::Text(LimboText::json(Rc::new(val.to_string()))))
}
/// Returns the value at the given JSON path. If the path does not exist, it returns None.
/// If the path is an invalid path, returns an error.
fn json_extract_single<'a>(json: &'a Val, path: &OwnedValue) -> crate::Result<Option<&'a Val>> {
let json_path = match path {
OwnedValue::Text(t) => json_path(t.value.as_str())?,
OwnedValue::Null => return Ok(None),
_ => crate::bail_constraint_error!("JSON path error near: {:?}", path.to_string()),
};
let mut current_element = &Val::Null;
@@ -204,12 +242,10 @@ fn json_extract_single(json: &Val, path: &str) -> crate::Result<Val> {
if let Some(value) = map.get(key) {
current_element = value;
} else {
return Ok(Val::Null);
return Ok(None);
}
}
_ => {
return Ok(Val::Null);
}
_ => return Ok(None),
}
}
PathElement::ArrayLocator(idx) => match current_element {
@@ -223,16 +259,15 @@ fn json_extract_single(json: &Val, path: &str) -> crate::Result<Val> {
if idx < array.len() as i32 {
current_element = &array[idx as usize];
} else {
return Ok(Val::Null);
return Ok(None);
}
}
_ => {
return Ok(Val::Null);
}
_ => return Ok(None),
},
}
}
Ok(current_element.clone())
Ok(Some(&current_element))
}
#[cfg(test)]

View File

@@ -789,7 +789,7 @@ pub fn translate_expr(
});
Ok(target_register)
}
JsonFunc::JsonArrayLength => {
JsonFunc::JsonArrayLength | JsonFunc::JsonType => {
let args = if let Some(args) = args {
if args.len() > 2 {
crate::bail_parse_error!(
@@ -837,7 +837,7 @@ pub fn translate_expr(
ScalarFunc::Changes => {
if let Some(_) = args {
crate::bail_parse_error!(
"{} fucntion with more than 0 arguments",
"{} function with more than 0 arguments",
srf
);
}

View File

@@ -41,7 +41,7 @@ use crate::vdbe::insn::Insn;
#[cfg(feature = "json")]
use crate::{
function::JsonFunc, json::get_json, json::json_array, json::json_array_length,
json::json_extract,
json::json_extract, json::json_type,
};
use crate::{Connection, Result, Rows, TransactionState, DATABASE_VERSION};
use datetime::{exec_date, exec_datetime_full, exec_julianday, exec_time, exec_unixepoch};
@@ -1381,17 +1381,25 @@ impl Program {
}
}
#[cfg(feature = "json")]
crate::function::Func::Json(JsonFunc::JsonArrayLength) => {
crate::function::Func::Json(
func @ (JsonFunc::JsonArrayLength | JsonFunc::JsonType),
) => {
let json_value = &state.registers[*start_reg];
let path_value = if arg_count > 1 {
Some(&state.registers[*start_reg + 1])
} else {
None
};
let json_array_length = json_array_length(json_value, path_value);
let func_result = match func {
JsonFunc::JsonArrayLength => {
json_array_length(json_value, path_value)
}
JsonFunc::JsonType => json_type(json_value, path_value),
_ => unreachable!(),
};
match json_array_length {
Ok(length) => state.registers[*dest] = length,
match func_result {
Ok(result) => state.registers[*dest] = result,
Err(e) => return Err(e),
}
}

View File

@@ -221,3 +221,51 @@ do_execsql_test json_array_length_via_bad_prop {
do_execsql_test json_array_length_nested {
SELECT json_array_length('{"one":[[1,2,3],2,3]}', '$.one[0]');
} {{3}}
do_execsql_test json_type_no_path {
select json_type('{"a":[2,3.5,true,false,null,"x"]}')
} {{object}}
do_execsql_test json_type_root_path {
select json_type('{"a":[2,3.5,true,false,null,"x"]}','$')
} {{object}}
do_execsql_test json_type_array {
select json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a')
} {{array}}
do_execsql_test json_type_integer {
select json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[0]')
} {{integer}}
do_execsql_test json_type_real {
select json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[1]')
} {{real}}
do_execsql_test json_type_true {
select json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[2]')
} {{true}}
do_execsql_test json_type_false {
select json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[3]')
} {{false}}
do_execsql_test json_type_null {
select json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[4]')
} {{null}}
do_execsql_test json_type_text {
select json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[5]')
} {{text}}
do_execsql_test json_type_NULL {
select json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[6]')
} {{}}
do_execsql_test json_type_cast {
select json_type(1)
} {{integer}}
do_execsql_test json_type_null_arg {
select json_type(null)
} {{}}