Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement json_error_position #564

Merged
merged 2 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion COMPAT.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html).
| jsonb_array(value1,value2,...) | | |
| json_array_length(json) | Yes | |
| json_array_length(json,path) | Yes | |
| json_error_position(json) | | |
| json_error_position(json) | Yes | |
| json_extract(json,path,...) | Partial | Does not fully support unicode literal syntax and does not allow numbers > 2^127 - 1 (which SQLite truncates to i32), does not support BLOBs |
| jsonb_extract(json,path,...) | | |
| json -> path | Yes | |
Expand Down
4 changes: 4 additions & 0 deletions core/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub enum JsonFunc {
JsonArrowShiftExtract,
JsonExtract,
JsonType,
JsonErrorPosition,
}

#[cfg(feature = "json")]
Expand All @@ -46,6 +47,7 @@ impl Display for JsonFunc {
Self::JsonArrowExtract => "->".to_string(),
Self::JsonArrowShiftExtract => "->>".to_string(),
Self::JsonType => "json_type".to_string(),
Self::JsonErrorPosition => "json_error_position".to_string(),
}
)
}
Expand Down Expand Up @@ -379,6 +381,8 @@ impl Func {
"json_extract" => Ok(Func::Json(JsonFunc::JsonExtract)),
#[cfg(feature = "json")]
"json_type" => Ok(Func::Json(JsonFunc::JsonType)),
#[cfg(feature = "json")]
"json_error_position" => Ok(Self::Json(JsonFunc::JsonErrorPosition)),
"unixepoch" => Ok(Self::Scalar(ScalarFunc::UnixEpoch)),
"julianday" => Ok(Self::Scalar(ScalarFunc::JulianDay)),
"hex" => Ok(Self::Scalar(ScalarFunc::Hex)),
Expand Down
84 changes: 84 additions & 0 deletions core/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ mod ser;
use std::rc::Rc;

pub use crate::json::de::from_str;
use crate::json::error::Error as JsonError;
use crate::json::json_path::{json_path, JsonPath, PathElement};
pub use crate::json::ser::to_string;
use crate::types::{LimboText, OwnedValue, TextSubtype};
use indexmap::IndexMap;
use jsonb::Error as JsonbError;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
Expand Down Expand Up @@ -370,6 +372,32 @@ fn json_extract_single<'a>(
Ok(Some(&current_element))
}

pub fn json_error_position(json: &OwnedValue) -> crate::Result<OwnedValue> {
match json {
OwnedValue::Text(t) => match crate::json::from_str::<Val>(&t.value) {
Ok(_) => Ok(OwnedValue::Integer(0)),
Err(JsonError::Message { location, .. }) => {
if let Some(loc) = location {
Ok(OwnedValue::Integer(loc.column as i64))
} else {
Err(crate::error::LimboError::InternalError(
"failed to determine json error position".into(),
))
}
}
},
OwnedValue::Blob(b) => match jsonb::from_slice(b) {
Ok(_) => Ok(OwnedValue::Integer(0)),
Err(JsonbError::Syntax(_, pos)) => Ok(OwnedValue::Integer(pos as i64)),
_ => Err(crate::error::LimboError::InternalError(
"failed to determine json error position".into(),
)),
},
OwnedValue::Null => Ok(OwnedValue::Null),
_ => Ok(OwnedValue::Integer(0)),
petersooley marked this conversation as resolved.
Show resolved Hide resolved
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -698,4 +726,60 @@ mod tests {
Err(e) => assert!(e.to_string().contains("JSON path error")),
}
}

#[test]
fn test_json_error_position_no_error() {
let input = OwnedValue::build_text(Rc::new("[1,2,3]".to_string()));
let result = json_error_position(&input).unwrap();
assert_eq!(result, OwnedValue::Integer(0));
}

#[test]
fn test_json_error_position_no_error_more() {
let input = OwnedValue::build_text(Rc::new(r#"{"a":55,"b":72 , }"#.to_string()));
let result = json_error_position(&input).unwrap();
assert_eq!(result, OwnedValue::Integer(0));
}

#[test]
fn test_json_error_position_object() {
let input = OwnedValue::build_text(Rc::new(r#"{"a":55,"b":72,,}"#.to_string()));
let result = json_error_position(&input).unwrap();
assert_eq!(result, OwnedValue::Integer(16));
}

#[test]
fn test_json_error_position_array() {
let input = OwnedValue::build_text(Rc::new(r#"["a",55,"b",72,,]"#.to_string()));
let result = json_error_position(&input).unwrap();
assert_eq!(result, OwnedValue::Integer(16));
}

petersooley marked this conversation as resolved.
Show resolved Hide resolved
#[test]
fn test_json_error_position_null() {
let input = OwnedValue::Null;
let result = json_error_position(&input).unwrap();
assert_eq!(result, OwnedValue::Null);
}

#[test]
fn test_json_error_position_integer() {
let input = OwnedValue::Integer(5);
let result = json_error_position(&input).unwrap();
assert_eq!(result, OwnedValue::Integer(0));
}

#[test]
fn test_json_error_position_float() {
let input = OwnedValue::Float(-5.5);
let result = json_error_position(&input).unwrap();
assert_eq!(result, OwnedValue::Integer(0));
}

#[test]
fn test_json_error_position_blob() {
let input = OwnedValue::Blob(Rc::new(r#"["a",55,"b",72,,]"#.as_bytes().to_owned()));
let result = json_error_position(&input).unwrap();
assert_eq!(result, OwnedValue::Integer(16));
}
}
25 changes: 25 additions & 0 deletions core/translate/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,31 @@ pub fn translate_expr(
func_ctx,
)
}
JsonFunc::JsonErrorPosition => {
let args = if let Some(args) = args {
if args.len() != 1 {
crate::bail_parse_error!(
"{} function with not exactly 1 argument",
j.to_string()
);
}
args
} else {
crate::bail_parse_error!(
"{} function with no arguments",
j.to_string()
);
};
let json_reg = program.alloc_register();
translate_expr(program, referenced_tables, &args[0], json_reg, resolver)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: json_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
},
Func::Scalar(srf) => {
match srf {
Expand Down
9 changes: 9 additions & 0 deletions core/vdbe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use crate::vdbe::insn::Insn;
use crate::{
function::JsonFunc, json::get_json, json::json_array, json::json_array_length,
json::json_arrow_extract, json::json_arrow_shift_extract, json::json_extract, json::json_type,
json::json_error_position,
};
use crate::{Connection, Result, Rows, TransactionState, DATABASE_VERSION};
use datetime::{exec_date, exec_datetime_full, exec_julianday, exec_time, exec_unixepoch};
Expand Down Expand Up @@ -1421,6 +1422,14 @@ impl Program {
Err(e) => return Err(e),
}
}
#[cfg(feature = "json")]
crate::function::Func::Json(JsonFunc::JsonErrorPosition) => {
let json_value = &state.registers[*start_reg];
match json_error_position(json_value) {
Ok(pos) => state.registers[*dest] = pos,
Err(e) => return Err(e),
}
}
crate::function::Func::Scalar(scalar_func) => match scalar_func {
ScalarFunc::Cast => {
assert!(arg_count == 2);
Expand Down
32 changes: 32 additions & 0 deletions testing/json.test
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,35 @@ do_execsql_test json_type_cast {
do_execsql_test json_type_null_arg {
select json_type(null)
} {{}}

do_execsql_test json_error_position_valid {
SELECT json_error_position('{"a":55,"b":72,}');
} {{0}}

do_execsql_test json_error_position_valid_ws {
SELECT json_error_position('{"a":55,"b":72 , }');
} {{0}}

do_execsql_test json_error_position_object {
SELECT json_error_position('{"a":55,"b":72,,}');
} {{16}}

do_execsql_test json_error_position_array_valid {
SELECT json_error_position('["a",55,"b",72,]');
} {{0}}

do_execsql_test json_error_position_array_valid_ws {
SELECT json_error_position('["a",55,"b",72 , ]');
} {{0}}

do_execsql_test json_error_position_array {
SELECT json_error_position('["a",55,"b",72,,]');
} {{16}}

do_execsql_test json_error_position_null {
SELECT json_error_position(NULL);
} {{}}

do_execsql_test json_error_position_complex {
SELECT json_error_position('{a:null,{"h":[1,[1,2,3]],"j":"abc"}:true}');
} {{9}}
Loading