Skip to content

Commit

Permalink
feat(sqlparser): support jsonb operator -> and ->> (#8144)
Browse files Browse the repository at this point in the history
In #8023 we introduced the ability to access jsonb array element or object field via PostgreSQL internal function names. This PR adds the sqlparser support so that they can be written as `jsonb -> idx`, `jsonb -> key`, `jsonb ->> idx`, or `jsonb ->> key`.

Note that `#>`/`jsonb_extract_path` and `#>>`/`jsonb_extract_path_text` are not implemented yet.

Approved-By: lmatz
Approved-By: jon-chuang
  • Loading branch information
xiangjinwu committed Feb 23, 2023
1 parent 13b88a5 commit 58ff481
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 6 deletions.
12 changes: 7 additions & 5 deletions e2e_test/batch/types/jsonb.slt.part
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,21 @@ select null::jsonb::bool;
----
NULL

# Example of accessing the boolean nested inside object and array
query T
select jsonb_array_element(jsonb_object_field('{"k2":[2,true,4]}', 'k2'), -2)::bool;
select ('{"k2":[2,true,4]}'::jsonb -> 'k2' -> -2)::bool;
----
t

# But for text, avoid cast and use `->>` as the last access operator.
# Note the difference between access text directly vs access jsonb then cast to text.
query TTT
with t(v1) as (values (null::jsonb), ('null'), ('true'), ('1'), ('"a"'), ('[]'), ('{}')),
j(v1) as (select ('{"k":' || v1::varchar || '}')::jsonb from t)
select
jsonb_object_field_text(v1, 'k'),
jsonb_object_field(v1, 'k')::varchar,
jsonb_typeof(jsonb_object_field(v1, 'k'))
v1 ->> 'k',
(v1 -> 'k')::varchar,
jsonb_typeof(v1 -> 'k')
from j order by 2;
----
a "a" string
Expand All @@ -107,7 +109,7 @@ true true boolean
NULL NULL NULL

query T
select jsonb_array_element_text('true'::jsonb, 2);
select 'true'::jsonb ->> 2;
----
NULL

Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/binder/expr/binary_op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ impl Binder {
BinaryOperator::PGBitwiseXor => ExprType::BitwiseXor,
BinaryOperator::PGBitwiseShiftLeft => ExprType::BitwiseShiftLeft,
BinaryOperator::PGBitwiseShiftRight => ExprType::BitwiseShiftRight,
BinaryOperator::Arrow => ExprType::JsonbAccessInner,
BinaryOperator::LongArrow => ExprType::JsonbAccessStr,
BinaryOperator::Concat => {
match (bound_left.return_type(), bound_right.return_type()) {
// array concatenation
Expand Down
8 changes: 8 additions & 0 deletions src/sqlparser/src/ast/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ pub enum BinaryOperator {
PGRegexIMatch,
PGRegexNotMatch,
PGRegexNotIMatch,
Arrow,
LongArrow,
HashArrow,
HashLongArrow,
}

impl fmt::Display for BinaryOperator {
Expand Down Expand Up @@ -121,6 +125,10 @@ impl fmt::Display for BinaryOperator {
BinaryOperator::PGRegexIMatch => "~*",
BinaryOperator::PGRegexNotMatch => "!~",
BinaryOperator::PGRegexNotIMatch => "!~*",
BinaryOperator::Arrow => "->",
BinaryOperator::LongArrow => "->>",
BinaryOperator::HashArrow => "#>",
BinaryOperator::HashLongArrow => "#>>",
})
}
}
5 changes: 5 additions & 0 deletions src/sqlparser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,10 @@ impl Parser {
Token::TildeAsterisk => Some(BinaryOperator::PGRegexIMatch),
Token::ExclamationMarkTilde => Some(BinaryOperator::PGRegexNotMatch),
Token::ExclamationMarkTildeAsterisk => Some(BinaryOperator::PGRegexNotIMatch),
Token::Arrow => Some(BinaryOperator::Arrow),
Token::LongArrow => Some(BinaryOperator::LongArrow),
Token::HashArrow => Some(BinaryOperator::HashArrow),
Token::HashLongArrow => Some(BinaryOperator::HashLongArrow),
Token::Word(w) => match w.keyword {
Keyword::AND => Some(BinaryOperator::And),
Keyword::OR => Some(BinaryOperator::Or),
Expand Down Expand Up @@ -1361,6 +1365,7 @@ impl Parser {
Token::Pipe => Ok(21),
Token::Caret | Token::Sharp | Token::ShiftRight | Token::ShiftLeft => Ok(22),
Token::Ampersand => Ok(23),
Token::Arrow | Token::LongArrow | Token::HashArrow | Token::HashLongArrow => Ok(25),
Token::Plus | Token::Minus => Ok(Self::PLUS_MINUS_PREC),
Token::Mul | Token::Div | Token::Mod | Token::Concat => Ok(40),
Token::DoubleColon => Ok(50),
Expand Down
40 changes: 39 additions & 1 deletion src/sqlparser/src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ pub enum Token {
PGSquareRoot,
/// `||/` , a cube root math operator in PostgreSQL
PGCubeRoot,
/// `->`, access JSON object field or array element in PostgreSQL
Arrow,
/// `->>`, access JSON object field or array element as text in PostgreSQL
LongArrow,
/// `#>`, extract JSON sub-object at the specified path in PostgreSQL
HashArrow,
/// `#>>`, extract JSON sub-object at the specified path as text in PostgreSQL
HashLongArrow,
}

impl fmt::Display for Token {
Expand Down Expand Up @@ -196,6 +204,10 @@ impl fmt::Display for Token {
Token::ShiftRight => f.write_str(">>"),
Token::PGSquareRoot => f.write_str("|/"),
Token::PGCubeRoot => f.write_str("||/"),
Token::Arrow => f.write_str("->"),
Token::LongArrow => f.write_str("->>"),
Token::HashArrow => f.write_str("#>"),
Token::HashLongArrow => f.write_str("#>>"),
}
}
}
Expand Down Expand Up @@ -503,6 +515,16 @@ impl<'a> Tokenizer<'a> {
comment,
})))
}
Some('>') => {
chars.next(); // consume first '>'
match chars.peek() {
Some('>') => {
chars.next(); // consume second '>'
Ok(Some(Token::LongArrow))
}
_ => Ok(Some(Token::Arrow)),
}
}
// a regular '-' operator
_ => Ok(Some(Token::Minus)),
}
Expand Down Expand Up @@ -604,7 +626,23 @@ impl<'a> Tokenizer<'a> {
_ => Ok(Some(Token::Tilde)),
}
}
'#' => self.consume_and_return(chars, Token::Sharp),
'#' => {
chars.next(); // consume the '#'
match chars.peek() {
Some('>') => {
chars.next(); // consume first '>'
match chars.peek() {
Some('>') => {
chars.next(); // consume second '>'
Ok(Some(Token::HashLongArrow))
}
_ => Ok(Some(Token::HashArrow)),
}
}
// a regular '#' operator
_ => Ok(Some(Token::Sharp)),
}
}
'@' => self.consume_and_return(chars, Token::AtSign),
other => self.consume_and_return(chars, Token::Char(other)),
},
Expand Down

0 comments on commit 58ff481

Please sign in to comment.