mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-20 15:35:29 +01:00
Implement json_type
This commit is contained in:
@@ -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) | | |
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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(¤t_element))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
} {{}}
|
||||
|
||||
Reference in New Issue
Block a user