diff --git a/Cargo.lock b/Cargo.lock index 498002620379d..92bbc847031cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4137,9 +4137,9 @@ dependencies = [ [[package]] name = "jsonbb" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44376417b2ff0cd879b5c84976fa9e0855c316321b4e0502e33e52963bf84f74" +checksum = "efd95430271266a57cbb8fd31115559c853fcaa5f367d32c4720034f7bd37b7f" dependencies = [ "bytes", "serde", diff --git a/proto/expr.proto b/proto/expr.proto index 4437a548beec3..0d29cabd494fb 100644 --- a/proto/expr.proto +++ b/proto/expr.proto @@ -237,6 +237,13 @@ message ExprNode { JSONB_EXISTS_ANY = 611; // jsonb ?& text[] JSONB_EXISTS_ALL = 612; + // see SUBTRACT for: + // jsonb - text -> jsonb + // jsonb - text[] -> jsonb + // jsonb - integer -> jsonb + // + // jsonb #- text[] -> jsonb + JSONB_DELETE_PATH = 615; // Non-pure functions below (> 1000) // ------------------------ diff --git a/src/common/src/types/jsonb.rs b/src/common/src/types/jsonb.rs index 664af6c0b1921..15345ac7cfe20 100644 --- a/src/common/src/types/jsonb.rs +++ b/src/common/src/types/jsonb.rs @@ -293,6 +293,14 @@ impl<'a> JsonbRef<'a> { self.0.as_null().is_some() } + /// Returns true if this is a jsonb null, boolean, number or string. + pub fn is_scalar(&self) -> bool { + matches!( + self.0, + ValueRef::Null | ValueRef::Bool(_) | ValueRef::Number(_) | ValueRef::String(_) + ) + } + /// Returns true if this is a jsonb array. pub fn is_array(&self) -> bool { matches!(self.0, ValueRef::Array(_)) diff --git a/src/expr/impl/src/scalar/jsonb_delete.rs b/src/expr/impl/src/scalar/jsonb_delete.rs new file mode 100644 index 0000000000000..dff60f34f722b --- /dev/null +++ b/src/expr/impl/src/scalar/jsonb_delete.rs @@ -0,0 +1,369 @@ +// Copyright 2023 RisingWave Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::HashSet; + +use jsonbb::{Value, ValueRef}; +use risingwave_common::types::{JsonbRef, JsonbVal, ListRef}; +use risingwave_expr::{function, ExprError, Result}; + +/// Removes a key (and its value) from a JSON object, or matching string value(s) from a JSON array. +/// +/// Examples: +/// +/// ```slt +/// # remove key from object +/// query T +/// SELECT '{"a": "b", "c": "d"}'::jsonb - 'a'; +/// ---- +/// {"c": "d"} +/// +/// # remove matching value from array +/// query T +/// SELECT '["a", "b", "c", "b"]'::jsonb - 'b'; +/// ---- +/// ["a", "c"] +/// +/// query error cannot delete from scalar +/// SELECT '1'::jsonb - 'b'; +/// ``` +#[function("subtract(jsonb, varchar) -> jsonb")] +fn jsonb_remove(v: JsonbRef<'_>, key: &str) -> Result { + match v.into() { + ValueRef::Object(obj) => Ok(JsonbVal::from(Value::object( + obj.iter().filter(|(k, _)| *k != key), + ))), + ValueRef::Array(arr) => Ok(JsonbVal::from(Value::array( + arr.iter().filter(|value| value.as_str() != Some(key)), + ))), + _ => Err(ExprError::InvalidParam { + name: "jsonb", + reason: "cannot delete from scalar".into(), + }), + } +} + +/// Deletes all matching keys or array elements from the left operand. +/// +/// Examples: +/// +/// ```slt +/// query T +/// SELECT '{"a": "b", "c": "d"}'::jsonb - '{a,c}'::text[]; +/// ---- +/// {} +/// +/// query error cannot delete from scalar +/// SELECT '1'::jsonb - '{a,c}'::text[]; +/// ``` +#[function("subtract(jsonb, varchar[]) -> jsonb")] +fn jsonb_remove_keys(v: JsonbRef<'_>, keys: ListRef<'_>) -> Result { + let keys_set: HashSet<&str> = keys.iter().flatten().map(|s| s.into_utf8()).collect(); + + match v.into() { + ValueRef::Object(obj) => Ok(JsonbVal::from(Value::object( + obj.iter().filter(|(k, _)| !keys_set.contains(*k)), + ))), + ValueRef::Array(arr) => { + Ok(JsonbVal::from(Value::array(arr.iter().filter( + |value| match value.as_str() { + Some(s) => !keys_set.contains(s), + None => true, + }, + )))) + } + _ => Err(ExprError::InvalidParam { + name: "jsonb", + reason: "cannot delete from scalar".into(), + }), + } +} + +/// Deletes the array element with the specified index (negative integers count from the end). +/// Throws an error if JSON value is not an array. +/// +/// Examples: +/// +/// ```slt +/// query T +/// SELECT '["a", "b"]'::jsonb - 1; +/// ---- +/// ["a"] +/// +/// query T +/// SELECT '["a", "b"]'::jsonb - -1; +/// ---- +/// ["a"] +/// +/// query T +/// SELECT '["a", "b"]'::jsonb - 2; +/// ---- +/// ["a", "b"] +/// +/// query T +/// SELECT '["a", "b"]'::jsonb - -3; +/// ---- +/// ["a", "b"] +/// +/// query error cannot delete from scalar +/// SELECT '1'::jsonb - 1; +/// +/// query error cannot delete from object using integer index +/// SELECT '{"a": 1}'::jsonb - 1; +/// ``` +#[function("subtract(jsonb, int4) -> jsonb")] +fn jsonb_remove_index(v: JsonbRef<'_>, index: i32) -> Result { + let array = match v.into() { + ValueRef::Array(array) => array, + ValueRef::Object(_) => { + return Err(ExprError::InvalidParam { + name: "jsonb", + reason: "cannot delete from object using integer index".into(), + }) + } + _ => { + return Err(ExprError::InvalidParam { + name: "jsonb", + reason: "cannot delete from scalar".into(), + }) + } + }; + let Some(idx) = normalize_array_index(array.len(), index) else { + // out of bounds index returns original value + return Ok(JsonbVal::from(v)); + }; + Ok(JsonbVal::from(Value::array( + array + .iter() + .enumerate() + .filter(|&(i, _)| i != idx) + .map(|(_, v)| v), + ))) +} + +/// Deletes the field or array element at the specified path, where path elements can be +/// either field keys or array indexes. +/// +/// Examples: +/// +/// ```slt +/// # Basic test case +/// query T +/// SELECT '["a", {"b":1}]'::jsonb #- '{1,b}'; +/// ---- +/// ["a", {}] +/// +/// # Invalid path +/// query error path element at position 1 is null +/// SELECT '["a", {"b":1}]'::jsonb #- array[null]; +/// +/// # Removing non-existent key from an object +/// query T +/// SELECT '{"a": 1, "b": 2}'::jsonb #- '{c}'; +/// ---- +/// {"a": 1, "b": 2} +/// +/// # Removing an existing key from an object +/// query T +/// SELECT '{"a": 1, "b": 2}'::jsonb #- '{a}'; +/// ---- +/// {"b": 2} +/// +/// # Removing an item from an array by positive index +/// query T +/// SELECT '["a", "b", "c"]'::jsonb #- '{1}'; +/// ---- +/// ["a", "c"] +/// +/// # Removing an item from an array by negative index +/// query T +/// SELECT '["a", "b", "c"]'::jsonb #- '{-1}'; +/// ---- +/// ["a", "b"] +/// +/// # Removing a non-existent index from an array +/// query T +/// SELECT '["a", "b", "c"]'::jsonb #- '{3}'; +/// ---- +/// ["a", "b", "c"] +/// +/// # Path element is not an integer for array +/// query error path element at position 1 is not an integer: "a" +/// SELECT '["a", "b", "c"]'::jsonb #- '{a}'; +/// +/// # Path to deeply nested value +/// query T +/// SELECT '{"a": {"b": {"c": [1, 2, 3]}}}'::jsonb #- '{a,b,c,1}'; +/// ---- +/// {"a": {"b": {"c": [1, 3]}}} +/// +/// # Path terminates early (before reaching the final depth of the JSON) +/// query T +/// SELECT '{"a": {"b": {"c": [1, 2, 3]}}}'::jsonb #- '{a}'; +/// ---- +/// {} +/// +/// # Removing non-existent path in nested structure +/// query T +/// SELECT '{"a": {"b": {"c": [1, 2, 3]}}}'::jsonb #- '{a,x}'; +/// ---- +/// {"a": {"b": {"c": [1, 2, 3]}}} +/// +/// # Path is longer than the depth of the JSON structure +/// query T +/// SELECT '{"a": 1}'::jsonb #- '{a,b}'; +/// ---- +/// {"a": 1} +/// +/// # Edge case: Removing root +/// query T +/// SELECT '{"a": 1}'::jsonb #- '{}'; +/// ---- +/// {"a": 1} +/// +/// # Edge case: Empty array +/// query T +/// SELECT '[]'::jsonb #- '{a}'; +/// ---- +/// [] +/// +/// # Edge case: Empty object +/// query T +/// SELECT '{}'::jsonb #- '{null}'; +/// ---- +/// {} +/// +/// query error cannot delete path in scalar +/// SELECT '1'::jsonb #- '{}'; +/// ``` +#[function("jsonb_delete_path(jsonb, varchar[]) -> jsonb")] +fn jsonb_delete_path(v: JsonbRef<'_>, path: ListRef<'_>) -> Result { + if v.is_scalar() { + return Err(ExprError::InvalidParam { + name: "jsonb", + reason: "cannot delete path in scalar".into(), + }); + } + if path.is_empty() { + return Ok(JsonbVal::from(v)); + } + let jsonb: ValueRef<'_> = v.into(); + let mut builder = jsonbb::Builder::>::with_capacity(jsonb.capacity()); + jsonbb_remove_path(jsonb, path, 0, &mut builder)?; + Ok(JsonbVal::from(builder.finish())) +} + +// Recursively remove `path[i..]` from `jsonb` and write the result to `builder`. +// Panics if `i` is out of bounds. +fn jsonbb_remove_path( + jsonb: ValueRef<'_>, + path: ListRef<'_>, + i: usize, + builder: &mut jsonbb::Builder, +) -> Result<()> { + match jsonb { + ValueRef::Object(obj) => { + if obj.is_empty() { + builder.add_value(jsonb); + return Ok(()); + } + let key = path + .get(i) + .unwrap() + .ok_or_else(|| ExprError::InvalidParam { + name: "path", + reason: format!("path element at position {} is null", i + 1).into(), + })? + .into_utf8(); + if !obj.contains_key(key) { + builder.add_value(jsonb); + return Ok(()); + } + builder.begin_object(); + for (k, v) in obj.iter() { + if k != key { + builder.add_string(k); + builder.add_value(v); + continue; + } + if i != path.len() - 1 { + builder.add_string(k); + // recursively remove path[i+1..] from v + jsonbb_remove_path(v, path, i + 1, builder)?; + } + } + builder.end_object(); + Ok(()) + } + ValueRef::Array(array) => { + if array.is_empty() { + builder.add_value(jsonb); + return Ok(()); + } + let key = path + .get(i) + .unwrap() + .ok_or_else(|| ExprError::InvalidParam { + name: "path", + reason: format!("path element at position {} is null", i + 1).into(), + })? + .into_utf8(); + let idx = key.parse::().map_err(|_| ExprError::InvalidParam { + name: "path", + reason: format!( + "path element at position {} is not an integer: \"{}\"", + i + 1, + key + ) + .into(), + })?; + let Some(idx) = normalize_array_index(array.len(), idx) else { + // out of bounds index returns original value + builder.add_value(jsonb); + return Ok(()); + }; + builder.begin_array(); + for (j, v) in array.iter().enumerate() { + if j != idx { + builder.add_value(v); + continue; + } + if i != path.len() - 1 { + // recursively remove path[i+1..] from v + jsonbb_remove_path(v, path, i + 1, builder)?; + } + } + builder.end_array(); + Ok(()) + } + _ => { + builder.add_value(jsonb); + Ok(()) + } + } +} + +/// Normalizes an array index to `0..len`. +/// Negative indices count from the end. i.e. `-len..0 => 0..len`. +/// Returns `None` if index is out of bounds. +fn normalize_array_index(len: usize, index: i32) -> Option { + if index < -(len as i32) || index >= (len as i32) { + return None; + } + if index >= 0 { + Some(index as usize) + } else { + Some((len as i32 + index) as usize) + } +} diff --git a/src/expr/impl/src/scalar/mod.rs b/src/expr/impl/src/scalar/mod.rs index d1b89d3c75d6c..564091897ae1a 100644 --- a/src/expr/impl/src/scalar/mod.rs +++ b/src/expr/impl/src/scalar/mod.rs @@ -45,6 +45,7 @@ mod int256; mod jsonb_access; mod jsonb_concat; mod jsonb_contains; +mod jsonb_delete; mod jsonb_info; mod jsonb_object; mod length; diff --git a/src/frontend/src/binder/expr/binary_op.rs b/src/frontend/src/binder/expr/binary_op.rs index 33f6f366e6496..8718ea74e5b5d 100644 --- a/src/frontend/src/binder/expr/binary_op.rs +++ b/src/frontend/src/binder/expr/binary_op.rs @@ -91,6 +91,7 @@ impl Binder { BinaryOperator::PGBitwiseShiftRight => ExprType::BitwiseShiftRight, BinaryOperator::Arrow => ExprType::JsonbAccess, BinaryOperator::LongArrow => ExprType::JsonbAccessStr, + BinaryOperator::HashMinus => ExprType::JsonbDeletePath, BinaryOperator::HashArrow => ExprType::JsonbAccessMulti, BinaryOperator::HashLongArrow => ExprType::JsonbAccessMultiStr, BinaryOperator::Prefix => ExprType::StartsWith, diff --git a/src/frontend/src/binder/expr/function.rs b/src/frontend/src/binder/expr/function.rs index 6dfab2c3bc283..a32e07f72ce51 100644 --- a/src/frontend/src/binder/expr/function.rs +++ b/src/frontend/src/binder/expr/function.rs @@ -884,6 +884,8 @@ impl Binder { ("jsonb_exists", raw_call(ExprType::JsonbExists)), ("jsonb_exists_any", raw_call(ExprType::JsonbExistsAny)), ("jsonb_exists_all", raw_call(ExprType::JsonbExistsAll)), + ("jsonb_delete", raw_call(ExprType::Subtract)), + ("jsonb_delete_path", raw_call(ExprType::JsonbDeletePath)), // Functions that return a constant value ("pi", pi()), // greatest and least diff --git a/src/frontend/src/expr/pure.rs b/src/frontend/src/expr/pure.rs index cebd59322c2b9..94d38dcff56f1 100644 --- a/src/frontend/src/expr/pure.rs +++ b/src/frontend/src/expr/pure.rs @@ -182,6 +182,7 @@ impl ExprVisitor for ImpureAnalyzer { | expr_node::Type::JsonbArrayLength | expr_node::Type::JsonbObject | expr_node::Type::JsonbPretty + | expr_node::Type::JsonbDeletePath | expr_node::Type::JsonbContains | expr_node::Type::JsonbContained | expr_node::Type::JsonbExists diff --git a/src/sqlparser/src/ast/operator.rs b/src/sqlparser/src/ast/operator.rs index 147a78d0b7174..9929d56b28679 100644 --- a/src/sqlparser/src/ast/operator.rs +++ b/src/sqlparser/src/ast/operator.rs @@ -99,6 +99,7 @@ pub enum BinaryOperator { LongArrow, HashArrow, HashLongArrow, + HashMinus, Contains, Contained, Exists, @@ -148,6 +149,7 @@ impl fmt::Display for BinaryOperator { BinaryOperator::LongArrow => "->>", BinaryOperator::HashArrow => "#>", BinaryOperator::HashLongArrow => "#>>", + BinaryOperator::HashMinus => "#-", BinaryOperator::Contains => "@>", BinaryOperator::Contained => "<@", BinaryOperator::Exists => "?", diff --git a/src/sqlparser/src/parser.rs b/src/sqlparser/src/parser.rs index d73ec295b0f90..87af26bfb1750 100644 --- a/src/sqlparser/src/parser.rs +++ b/src/sqlparser/src/parser.rs @@ -1397,6 +1397,7 @@ impl Parser { Token::LongArrow => Some(BinaryOperator::LongArrow), Token::HashArrow => Some(BinaryOperator::HashArrow), Token::HashLongArrow => Some(BinaryOperator::HashLongArrow), + Token::HashMinus => Some(BinaryOperator::HashMinus), Token::AtArrow => Some(BinaryOperator::Contains), Token::ArrowAt => Some(BinaryOperator::Contained), Token::QuestionMark => Some(BinaryOperator::Exists), @@ -1741,6 +1742,7 @@ impl Parser { | Token::LongArrow | Token::HashArrow | Token::HashLongArrow + | Token::HashMinus | Token::AtArrow | Token::ArrowAt | Token::QuestionMark diff --git a/src/sqlparser/src/tokenizer.rs b/src/sqlparser/src/tokenizer.rs index 914f0e5f0f8d4..4fafde820f414 100644 --- a/src/sqlparser/src/tokenizer.rs +++ b/src/sqlparser/src/tokenizer.rs @@ -164,6 +164,8 @@ pub enum Token { HashArrow, /// `#>>`, extract JSON sub-object at the specified path as text in PostgreSQL HashLongArrow, + /// `#-`, delete a key from a JSON object in PostgreSQL + HashMinus, /// `@>`, does the left JSON value contain the right JSON path/value entries at the top level AtArrow, /// `<@`, does the right JSON value contain the left JSON path/value entries at the top level @@ -241,6 +243,7 @@ impl fmt::Display for Token { Token::LongArrow => f.write_str("->>"), Token::HashArrow => f.write_str("#>"), Token::HashLongArrow => f.write_str("#>>"), + Token::HashMinus => f.write_str("#-"), Token::AtArrow => f.write_str("@>"), Token::ArrowAt => f.write_str("<@"), Token::QuestionMark => f.write_str("?"), @@ -761,6 +764,7 @@ impl<'a> Tokenizer<'a> { '#' => { chars.next(); // consume the '#' match chars.peek() { + Some('-') => self.consume_and_return(chars, Token::HashMinus), Some('>') => { chars.next(); // consume first '>' match chars.peek() { diff --git a/src/tests/regress/data/sql/jsonb.sql b/src/tests/regress/data/sql/jsonb.sql index 69bd9a928a0f5..8ae2b473b0dd4 100644 --- a/src/tests/regress/data/sql/jsonb.sql +++ b/src/tests/regress/data/sql/jsonb.sql @@ -1090,31 +1090,30 @@ select jsonb_pretty('{"a":["b", "c"], "d": {"e":"f"}}'); --@ select pg_column_size('{"aa":1, "b":2}'::jsonb || '{}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); --@ select pg_column_size('{}'::jsonb || '{"aa":1, "b":2}'::jsonb) = pg_column_size('{"aa":1, "b":2}'::jsonb); ---@ select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'a'); ---@ select jsonb_delete('{"a":null , "b":2, "c":3}'::jsonb, 'a'); ---@ select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'b'); ---@ select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'c'); ---@ select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'd'); ---@ select '{"a":1 , "b":2, "c":3}'::jsonb - 'a'; ---@ select '{"a":null , "b":2, "c":3}'::jsonb - 'a'; ---@ select '{"a":1 , "b":2, "c":3}'::jsonb - 'b'; ---@ select '{"a":1 , "b":2, "c":3}'::jsonb - 'c'; ---@ select '{"a":1 , "b":2, "c":3}'::jsonb - 'd'; +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'a'); +select jsonb_delete('{"a":null , "b":2, "c":3}'::jsonb, 'a'); +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'b'); +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'c'); +select jsonb_delete('{"a":1 , "b":2, "c":3}'::jsonb, 'd'); +select '{"a":1 , "b":2, "c":3}'::jsonb - 'a'; +select '{"a":null , "b":2, "c":3}'::jsonb - 'a'; +select '{"a":1 , "b":2, "c":3}'::jsonb - 'b'; +select '{"a":1 , "b":2, "c":3}'::jsonb - 'c'; +select '{"a":1 , "b":2, "c":3}'::jsonb - 'd'; --@ select pg_column_size('{"a":1 , "b":2, "c":3}'::jsonb - 'b') = pg_column_size('{"a":1, "b":2}'::jsonb); ---@ ---@ select '["a","b","c"]'::jsonb - 3; ---@ select '["a","b","c"]'::jsonb - 2; ---@ select '["a","b","c"]'::jsonb - 1; ---@ select '["a","b","c"]'::jsonb - 0; ---@ select '["a","b","c"]'::jsonb - -1; ---@ select '["a","b","c"]'::jsonb - -2; ---@ select '["a","b","c"]'::jsonb - -3; ---@ select '["a","b","c"]'::jsonb - -4; ---@ ---@ select '{"a":1 , "b":2, "c":3}'::jsonb - '{b}'::text[]; ---@ select '{"a":1 , "b":2, "c":3}'::jsonb - '{c,b}'::text[]; ---@ select '{"a":1 , "b":2, "c":3}'::jsonb - '{}'::text[]; ---@ + +select '["a","b","c"]'::jsonb - 3; +select '["a","b","c"]'::jsonb - 2; +select '["a","b","c"]'::jsonb - 1; +select '["a","b","c"]'::jsonb - 0; +select '["a","b","c"]'::jsonb - -1; +select '["a","b","c"]'::jsonb - -2; +select '["a","b","c"]'::jsonb - -3; +select '["a","b","c"]'::jsonb - -4; +select '{"a":1 , "b":2, "c":3}'::jsonb - '{b}'::text[]; +select '{"a":1 , "b":2, "c":3}'::jsonb - '{c,b}'::text[]; +select '{"a":1 , "b":2, "c":3}'::jsonb - '{}'::text[]; + --@ select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{n}', '[1,2,3]'); --@ select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '[1,2,3]'); --@ select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{d,1,0}', '[1,2,3]'); @@ -1127,28 +1126,28 @@ select jsonb_pretty('{"a":["b", "c"], "d": {"e":"f"}}'); --@ --@ select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '"test"'); --@ select jsonb_set('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb, '{b,-1}', '{"f": "test"}'); ---@ ---@ select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{n}'); ---@ select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{b,-1}'); ---@ select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{d,1,0}'); ---@ ---@ select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{n}'; ---@ select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{b,-1}'; ---@ select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{b,-1e}'; -- invalid array subscript ---@ select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{d,1,0}'; ---@ ---@ ---@ -- empty structure and error conditions for delete and replace ---@ ---@ select '"a"'::jsonb - 'a'; -- error ---@ select '{}'::jsonb - 'a'; ---@ select '[]'::jsonb - 'a'; ---@ select '"a"'::jsonb - 1; -- error ---@ select '{}'::jsonb - 1; -- error ---@ select '[]'::jsonb - 1; ---@ select '"a"'::jsonb #- '{a}'; -- error ---@ select '{}'::jsonb #- '{a}'; ---@ select '[]'::jsonb #- '{a}'; + +select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{n}'); +select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{b,-1}'); +select jsonb_delete_path('{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}', '{d,1,0}'); + +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{n}'; +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{b,-1}'; +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{b,-1e}'; -- invalid array subscript +select '{"n":null, "a":1, "b":[1,2], "c":{"1":2}, "d":{"1":[2,3]}}'::jsonb #- '{d,1,0}'; + + +-- empty structure and error conditions for delete and replace + +select '"a"'::jsonb - 'a'; -- error +select '{}'::jsonb - 'a'; +select '[]'::jsonb - 'a'; +select '"a"'::jsonb - 1; -- error +select '{}'::jsonb - 1; -- error +select '[]'::jsonb - 1; +select '"a"'::jsonb #- '{a}'; -- error +select '{}'::jsonb #- '{a}'; +select '[]'::jsonb #- '{a}'; --@ select jsonb_set('"a"','{a}','"b"'); --error --@ select jsonb_set('{}','{a}','"b"', false); --@ select jsonb_set('[]','{1}','"b"', false);