Skip to content

Commit 42f68dc

Browse files
committed
Support ?-based jsonb operators in Postgres
We already support most postgres jsonb operators, but were missing the ?-based ones. This commit adds support for them in both the tokenizer and the AST enums. This is simplified in the tokenizer with a dialect-specific carve-out, since Postgres thankfully does not also use ? for anonymous prepared statement parameters.
1 parent 8626051 commit 42f68dc

File tree

5 files changed

+125
-13
lines changed

5 files changed

+125
-13
lines changed

src/ast/operator.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,27 @@ pub enum BinaryOperator {
214214
///
215215
/// See <https://www.postgresql.org/docs/current/functions-json.html>.
216216
AtQuestion,
217+
/// The `?` operator.
218+
///
219+
/// On PostgreSQL, this operator is used to check whether a string exists as a top-level key
220+
/// within the JSON value
221+
///
222+
/// See <https://www.postgresql.org/docs/current/functions-json.html>.
223+
Question,
224+
/// The `?&` operator.
225+
///
226+
/// On PostgreSQL, this operator is used to check whether all of the the indicated array
227+
/// members exist as top-level keys.
228+
///
229+
/// See <https://www.postgresql.org/docs/current/functions-json.html>.
230+
QuestionAnd,
231+
/// The `?|` operator.
232+
///
233+
/// On PostgreSQL, this operator is used to check whether any of the the indicated array
234+
/// members exist as top-level keys.
235+
///
236+
/// See <https://www.postgresql.org/docs/current/functions-json.html>.
237+
QuestionPipe,
217238
/// PostgreSQL-specific custom operator.
218239
///
219240
/// See [CREATE OPERATOR](https://www.postgresql.org/docs/current/sql-createoperator.html)
@@ -269,6 +290,9 @@ impl fmt::Display for BinaryOperator {
269290
BinaryOperator::ArrowAt => f.write_str("<@"),
270291
BinaryOperator::HashMinus => f.write_str("#-"),
271292
BinaryOperator::AtQuestion => f.write_str("@?"),
293+
BinaryOperator::Question => f.write_str("?"),
294+
BinaryOperator::QuestionAnd => f.write_str("?&"),
295+
BinaryOperator::QuestionPipe => f.write_str("?|"),
272296
BinaryOperator::PGCustomBinaryOperator(idents) => {
273297
write!(f, "OPERATOR({})", display_separated(idents, "."))
274298
}

src/parser/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2355,6 +2355,9 @@ impl<'a> Parser<'a> {
23552355
Token::HashMinus => Some(BinaryOperator::HashMinus),
23562356
Token::AtQuestion => Some(BinaryOperator::AtQuestion),
23572357
Token::AtAt => Some(BinaryOperator::AtAt),
2358+
Token::Question => Some(BinaryOperator::Question),
2359+
Token::QuestionAnd => Some(BinaryOperator::QuestionAnd),
2360+
Token::QuestionPipe => Some(BinaryOperator::QuestionPipe),
23582361

23592362
Token::Word(w) => match w.keyword {
23602363
Keyword::AND => Some(BinaryOperator::And),
@@ -2851,7 +2854,10 @@ impl<'a> Parser<'a> {
28512854
| Token::ArrowAt
28522855
| Token::HashMinus
28532856
| Token::AtQuestion
2854-
| Token::AtAt => Ok(Self::PG_OTHER_PREC),
2857+
| Token::AtAt
2858+
| Token::Question
2859+
| Token::QuestionAnd
2860+
| Token::QuestionPipe => Ok(Self::PG_OTHER_PREC),
28552861
_ => Ok(0),
28562862
}
28572863
}

src/tokenizer.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ use sqlparser_derive::{Visit, VisitMut};
3636

3737
use crate::ast::DollarQuotedString;
3838
use crate::dialect::{
39-
BigQueryDialect, DuckDbDialect, GenericDialect, HiveDialect, SnowflakeDialect,
39+
BigQueryDialect, DuckDbDialect, GenericDialect, HiveDialect, PostgreSqlDialect,
40+
SnowflakeDialect,
4041
};
4142
use crate::dialect::{Dialect, MySqlDialect};
4243
use crate::keywords::{Keyword, ALL_KEYWORDS, ALL_KEYWORDS_INDEX};
@@ -199,6 +200,15 @@ pub enum Token {
199200
/// for the specified JSON value. Only the first item of the result is taken into
200201
/// account. If the result is not Boolean, then NULL is returned.
201202
AtAt,
203+
/// jsonb ? text -> boolean: Checks whether the string exists as a top-level key within the
204+
/// jsonb object
205+
Question,
206+
/// jsonb ?& text[] -> boolean: Check whether all members of the text array exist as top-level
207+
/// keys within the jsonb object
208+
QuestionAnd,
209+
/// jsonb ?| text[] -> boolean: Check whether any member of the text array exists as top-level
210+
/// keys within the jsonb object
211+
QuestionPipe,
202212
}
203213

204214
impl fmt::Display for Token {
@@ -278,6 +288,9 @@ impl fmt::Display for Token {
278288
Token::HashMinus => write!(f, "#-"),
279289
Token::AtQuestion => write!(f, "@?"),
280290
Token::AtAt => write!(f, "@@"),
291+
Token::Question => write!(f, "?"),
292+
Token::QuestionAnd => write!(f, "?&"),
293+
Token::QuestionPipe => write!(f, "?|"),
281294
}
282295
}
283296
}
@@ -1059,6 +1072,15 @@ impl<'a> Tokenizer<'a> {
10591072
_ => Ok(Some(Token::AtSign)),
10601073
}
10611074
}
1075+
// Postgres uses ? for jsonb operators, not prepared statements
1076+
'?' if dialect_of!(self is PostgreSqlDialect) => {
1077+
chars.next();
1078+
match chars.peek() {
1079+
Some('|') => self.consume_and_return(chars, Token::QuestionPipe),
1080+
Some('&') => self.consume_and_return(chars, Token::QuestionAnd),
1081+
_ => self.consume_and_return(chars, Token::Question),
1082+
}
1083+
}
10621084
'?' => {
10631085
chars.next();
10641086
let s = peeking_take_while(chars, |ch| ch.is_numeric());

tests/sqlparser_common.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7750,17 +7750,6 @@ fn test_lock_nonblock() {
77507750

77517751
#[test]
77527752
fn test_placeholder() {
7753-
let sql = "SELECT * FROM student WHERE id = ?";
7754-
let ast = verified_only_select(sql);
7755-
assert_eq!(
7756-
ast.selection,
7757-
Some(Expr::BinaryOp {
7758-
left: Box::new(Expr::Identifier(Ident::new("id"))),
7759-
op: BinaryOperator::Eq,
7760-
right: Box::new(Expr::Value(Value::Placeholder("?".into()))),
7761-
})
7762-
);
7763-
77647753
let dialects = TestedDialects {
77657754
dialects: vec![
77667755
Box::new(GenericDialect {}),
@@ -7800,6 +7789,32 @@ fn test_placeholder() {
78007789
}),
78017790
);
78027791

7792+
let dialects = TestedDialects {
7793+
dialects: vec![
7794+
Box::new(GenericDialect {}),
7795+
Box::new(DuckDbDialect {}),
7796+
// Note: `?` is for jsonb operators in PostgreSqlDialect
7797+
// Box::new(PostgreSqlDialect {}),
7798+
Box::new(MsSqlDialect {}),
7799+
Box::new(AnsiDialect {}),
7800+
Box::new(BigQueryDialect {}),
7801+
Box::new(SnowflakeDialect {}),
7802+
// Note: `$` is the starting word for the HiveDialect identifier
7803+
// Box::new(sqlparser::dialect::HiveDialect {}),
7804+
],
7805+
options: None,
7806+
};
7807+
let sql = "SELECT * FROM student WHERE id = ?";
7808+
let ast = dialects.verified_only_select(sql);
7809+
assert_eq!(
7810+
ast.selection,
7811+
Some(Expr::BinaryOp {
7812+
left: Box::new(Expr::Identifier(Ident::new("id"))),
7813+
op: BinaryOperator::Eq,
7814+
right: Box::new(Expr::Value(Value::Placeholder("?".into()))),
7815+
})
7816+
);
7817+
78037818
let sql = "SELECT $fromage_français, :x, ?123";
78047819
let ast = dialects.verified_only_select(sql);
78057820
assert_eq!(

tests/sqlparser_postgres.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,6 +2401,51 @@ fn test_json() {
24012401
},
24022402
select.selection.unwrap(),
24032403
);
2404+
2405+
let sql = r#"SELECT info FROM orders WHERE info ? 'b'"#;
2406+
let select = pg().verified_only_select(sql);
2407+
assert_eq!(
2408+
Expr::BinaryOp {
2409+
left: Box::new(Expr::Identifier(Ident::new("info"))),
2410+
op: BinaryOperator::Question,
2411+
right: Box::new(Expr::Value(Value::SingleQuotedString("b".to_string()))),
2412+
},
2413+
select.selection.unwrap(),
2414+
);
2415+
2416+
let sql = r#"SELECT info FROM orders WHERE info ?& ARRAY['b', 'c']"#;
2417+
let select = pg().verified_only_select(sql);
2418+
assert_eq!(
2419+
Expr::BinaryOp {
2420+
left: Box::new(Expr::Identifier(Ident::new("info"))),
2421+
op: BinaryOperator::QuestionAnd,
2422+
right: Box::new(Expr::Array(Array {
2423+
elem: vec![
2424+
Expr::Value(Value::SingleQuotedString("b".to_string())),
2425+
Expr::Value(Value::SingleQuotedString("c".to_string()))
2426+
],
2427+
named: true
2428+
}))
2429+
},
2430+
select.selection.unwrap(),
2431+
);
2432+
2433+
let sql = r#"SELECT info FROM orders WHERE info ?| ARRAY['b', 'c']"#;
2434+
let select = pg().verified_only_select(sql);
2435+
assert_eq!(
2436+
Expr::BinaryOp {
2437+
left: Box::new(Expr::Identifier(Ident::new("info"))),
2438+
op: BinaryOperator::QuestionPipe,
2439+
right: Box::new(Expr::Array(Array {
2440+
elem: vec![
2441+
Expr::Value(Value::SingleQuotedString("b".to_string())),
2442+
Expr::Value(Value::SingleQuotedString("c".to_string()))
2443+
],
2444+
named: true
2445+
}))
2446+
},
2447+
select.selection.unwrap(),
2448+
);
24042449
}
24052450

24062451
#[test]

0 commit comments

Comments
 (0)