Skip to content
Open
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
110 changes: 105 additions & 5 deletions core/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,15 @@ pub fn get_json(json_value: &Value, indent: Option<&str>) -> crate::Result<Value

Ok(Value::Text(Text::json(json)))
}
Value::Blob(b) => {
let jsonbin = Jsonb::new(b.len(), Some(b));
jsonbin.element_type()?;
Ok(Value::Text(Text::json(jsonbin.to_string())))
Value::Blob(_) => {
// Blobs can contain either text JSON or binary JSONB
// Use convert_dbtype_to_jsonb to properly detect and parse both formats
let json_val = convert_dbtype_to_jsonb(json_value, Conv::Strict)?;
let json = match indent {
Some(indent) => json_val.to_string_pretty(Some(indent))?,
None => json_val.to_string(),
};
Ok(Value::Text(Text::json(json)))
}
Value::Null => Ok(Value::Null),
_ => {
Expand Down Expand Up @@ -297,6 +302,10 @@ where
let second = args.next().ok_or_else(|| {
crate::LimboError::InternalError("args should have second element in loop".to_string())
})?;
let second_ref = second.as_value_ref();
if matches!(second_ref, ValueRef::Blob(_)) {
crate::bail_constraint_error!("JSON cannot hold BLOB values")
}
let value = convert_dbtype_to_jsonb(second, Conv::NotStrict)?;
let mut op = SetOperation::new(value);
if let Some(path) = path {
Expand Down Expand Up @@ -338,6 +347,10 @@ where
let second = args.next().ok_or_else(|| {
crate::LimboError::InternalError("args should have second element in loop".to_string())
})?;
let second_ref = second.as_value_ref();
if matches!(second_ref, ValueRef::Blob(_)) {
crate::bail_constraint_error!("JSON cannot hold BLOB values")
}
let value = convert_dbtype_to_jsonb(second, Conv::NotStrict)?;
let mut op = SetOperation::new(value);
if let Some(path) = path {
Expand Down Expand Up @@ -551,7 +564,8 @@ pub fn json_string_to_db_type(
if matches!(flag, OutputVariant::ElementType) {
json_string.remove(json_string.len() - 1);
json_string.remove(0);
Ok(Value::Text(Text::json(json_string)))
let unescaped = jsonb::unescape_string(&json_string);
Ok(Value::Text(Text::json(unescaped)))
} else {
Ok(Value::Text(Text::new(json_string)))
}
Expand Down Expand Up @@ -724,6 +738,10 @@ where
"values should have second element in loop".to_string(),
)
})?;
let second_ref = second.as_value_ref();
if matches!(second_ref, ValueRef::Blob(_)) {
crate::bail_constraint_error!("JSON cannot hold BLOB values")
}
let value = convert_dbtype_to_jsonb(second, Conv::NotStrict)?;
json.append_jsonb_to_end(value.data());
}
Expand Down Expand Up @@ -764,6 +782,10 @@ where
"values should have second element in loop".to_string(),
)
})?;
let second_ref = second.as_value_ref();
if matches!(second_ref, ValueRef::Blob(_)) {
crate::bail_constraint_error!("JSON cannot hold BLOB values")
}
let value = convert_dbtype_to_jsonb(second, Conv::NotStrict)?;
json.append_jsonb_to_end(value.data());
}
Expand Down Expand Up @@ -930,6 +952,54 @@ mod tests {
}
}

#[test]
fn test_get_json_blob_text_json() {
let true_blob = Value::Blob(b"true".to_vec());
let result = get_json(&true_blob, None).unwrap();
if let Value::Text(result_str) = result {
assert_eq!(result_str.as_str(), "true");
assert_eq!(result_str.subtype, TextSubtype::Json);
} else {
panic!("Expected Value::Text, got: {result:?}");
}

let false_blob = Value::Blob(b"false".to_vec());
let result = get_json(&false_blob, None).unwrap();
if let Value::Text(result_str) = result {
assert_eq!(result_str.as_str(), "false");
assert_eq!(result_str.subtype, TextSubtype::Json);
} else {
panic!("Expected Value::Text, got: {result:?}");
}

let null_blob = Value::Blob(b"null".to_vec());
let result = get_json(&null_blob, None).unwrap();
if let Value::Text(result_str) = result {
assert_eq!(result_str.as_str(), "null");
assert_eq!(result_str.subtype, TextSubtype::Json);
} else {
panic!("Expected Value::Text, got: {result:?}");
}

let number_blob = Value::Blob(b"123".to_vec());
let result = get_json(&number_blob, None).unwrap();
if let Value::Text(result_str) = result {
assert_eq!(result_str.as_str(), "123");
assert_eq!(result_str.subtype, TextSubtype::Json);
} else {
panic!("Expected Value::Text, got: {result:?}");
}

let string_blob = Value::Blob(b"\"hello\"".to_vec());
let result = get_json(&string_blob, None).unwrap();
if let Value::Text(result_str) = result {
assert_eq!(result_str.as_str(), "\"hello\"");
assert_eq!(result_str.subtype, TextSubtype::Json);
} else {
panic!("Expected Value::Text, got: {result:?}");
}
}

#[test]
fn test_get_json_non_text() {
let input = Value::Null;
Expand Down Expand Up @@ -1052,6 +1122,36 @@ mod tests {
}
}

#[test]
fn test_json_object_blob_invalid() {
let key = Value::build_text("a");
let blob = Value::Blob("true".as_bytes().to_vec());

let input = [key, blob];

let result = json_object(&input);

match result {
Ok(_) => panic!("Expected error for blob value in json_object"),
Err(e) => assert!(e.to_string().contains("JSON cannot hold BLOB values")),
}
}

#[test]
fn test_json_set_blob_invalid() {
let json_cache = JsonCacheCell::new();
let blob = Value::Blob("test".as_bytes().to_vec());
let result = json_set(
&[Value::build_text("{}"), Value::build_text("$.field"), blob],
&json_cache,
);

match result {
Ok(_) => panic!("Expected error for blob value in json_set"),
Err(e) => assert!(e.to_string().contains("JSON cannot hold BLOB values")),
}
}

#[test]
fn test_json_array_length() {
let input = Value::build_text("[1,2,3,4]");
Expand Down
48 changes: 48 additions & 0 deletions core/json/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ where
let second = args.next().ok_or_else(|| {
crate::LimboError::InternalError("args should have second element in loop".to_string())
})?;
let second_ref = second.as_value_ref();
if matches!(second_ref, ValueRef::Blob(_)) {
crate::bail_constraint_error!("JSON cannot hold BLOB values")
}
let value = convert_dbtype_to_jsonb(&second, Conv::NotStrict)?;
if let Some(path) = path {
let mut op = ReplaceOperation::new(value);
Expand Down Expand Up @@ -186,6 +190,10 @@ where
let second = args.next().ok_or_else(|| {
crate::LimboError::InternalError("args should have second element in loop".to_string())
})?;
let second_ref = second.as_value_ref();
if matches!(second_ref, ValueRef::Blob(_)) {
crate::bail_constraint_error!("JSON cannot hold BLOB values")
}
let value = convert_dbtype_to_jsonb(&second, Conv::NotStrict)?;
if let Some(path) = path {
let mut op = ReplaceOperation::new(value);
Expand Down Expand Up @@ -228,6 +236,10 @@ where
let second = args.next().ok_or_else(|| {
crate::LimboError::InternalError("args should have second element in loop".to_string())
})?;
let second_ref = second.as_value_ref();
if matches!(second_ref, ValueRef::Blob(_)) {
crate::bail_constraint_error!("JSON cannot hold BLOB values")
}
let value = convert_dbtype_to_jsonb(&second, Conv::NotStrict)?;
if let Some(path) = path {
let mut op = InsertOperation::new(value);
Expand Down Expand Up @@ -270,6 +282,10 @@ where
let second = args.next().ok_or_else(|| {
crate::LimboError::InternalError("args should have second element in loop".to_string())
})?;
let second_ref = second.as_value_ref();
if matches!(second_ref, ValueRef::Blob(_)) {
crate::bail_constraint_error!("JSON cannot hold BLOB values")
}
let value = convert_dbtype_to_jsonb(&second, Conv::NotStrict)?;
if let Some(path) = path {
let mut op = InsertOperation::new(value);
Expand Down Expand Up @@ -462,6 +478,38 @@ mod tests {
assert!(json_remove(&args, &json_cache).is_err());
}

#[test]
fn test_json_insert_blob_invalid() {
let json_cache = JsonCacheCell::new();
let blob = Value::Blob("test".as_bytes().to_vec());
let args = [create_json("{}"), create_text("$.field"), blob];

let result = json_insert(&args, &json_cache);

match result {
Ok(_) => panic!("Expected error for blob value in json_insert"),
Err(e) => assert!(e.to_string().contains("JSON cannot hold BLOB values")),
}
}

#[test]
fn test_json_replace_blob_invalid() {
let json_cache = JsonCacheCell::new();
let blob = Value::Blob("test".as_bytes().to_vec());
let args = [
create_json(r#"{"field":"value"}"#),
create_text("$.field"),
blob,
];

let result = json_replace(&args, &json_cache);

match result {
Ok(_) => panic!("Expected error for blob value in json_replace"),
Err(e) => assert!(e.to_string().contains("JSON cannot hold BLOB values")),
}
}

#[test]
fn test_json_remove_complex_case() {
let args = [
Expand Down
54 changes: 18 additions & 36 deletions core/vdbe/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4780,64 +4780,46 @@ pub fn op_function(
)?);
}
JsonFunc::JsonRemove => {
if let Ok(json) = json_remove(
let json = json_remove(
registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),
&state.json_cache,
) {
state.registers[*dest] = Register::Value(json);
} else {
state.registers[*dest] = Register::Value(Value::Null);
}
)?;
state.registers[*dest] = Register::Value(json);
}
JsonFunc::JsonbRemove => {
if let Ok(json) = jsonb_remove(
let json = jsonb_remove(
registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),
&state.json_cache,
) {
state.registers[*dest] = Register::Value(json);
} else {
state.registers[*dest] = Register::Value(Value::Null);
}
)?;
state.registers[*dest] = Register::Value(json);
}
JsonFunc::JsonReplace => {
if let Ok(json) = json_replace(
let json = json_replace(
registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),
&state.json_cache,
) {
state.registers[*dest] = Register::Value(json);
} else {
state.registers[*dest] = Register::Value(Value::Null);
}
)?;
state.registers[*dest] = Register::Value(json);
}
JsonFunc::JsonbReplace => {
if let Ok(json) = jsonb_replace(
let json = jsonb_replace(
registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),
&state.json_cache,
) {
state.registers[*dest] = Register::Value(json);
} else {
state.registers[*dest] = Register::Value(Value::Null);
}
)?;
state.registers[*dest] = Register::Value(json);
}
JsonFunc::JsonInsert => {
if let Ok(json) = json_insert(
let json = json_insert(
registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),
&state.json_cache,
) {
state.registers[*dest] = Register::Value(json);
} else {
state.registers[*dest] = Register::Value(Value::Null);
}
)?;
state.registers[*dest] = Register::Value(json);
}
JsonFunc::JsonbInsert => {
if let Ok(json) = jsonb_insert(
let json = jsonb_insert(
registers_to_ref_values(&state.registers[*start_reg..*start_reg + arg_count]),
&state.json_cache,
) {
state.registers[*dest] = Register::Value(json);
} else {
state.registers[*dest] = Register::Value(Value::Null);
}
)?;
state.registers[*dest] = Register::Value(json);
}
JsonFunc::JsonPretty => {
let json_value = &state.registers[*start_reg];
Expand Down
Loading
Loading