Skip to content

Commit 2f8dfb4

Browse files
committed
pg: add json ops
1 parent 746b39a commit 2f8dfb4

3 files changed

Lines changed: 135 additions & 5 deletions

File tree

src/ast/operator.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pub enum BinaryOperator {
7272
Rlike,
7373
NotRlike,
7474
JsonIndex,
75+
PgJsonBinOp(String),
7576
BitwiseOr,
7677
BitwiseAnd,
7778
BitwiseXor,
@@ -100,6 +101,7 @@ impl fmt::Display for BinaryOperator {
100101
BinaryOperator::Rlike => "RLIKE",
101102
BinaryOperator::NotRlike => "NOT RLIKE",
102103
BinaryOperator::JsonIndex => ":",
104+
BinaryOperator::PgJsonBinOp(s) => s.as_str(),
103105
BinaryOperator::BitwiseOr => "|",
104106
BinaryOperator::BitwiseAnd => "&",
105107
BinaryOperator::BitwiseXor => "^",

src/parser.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,16 @@ impl<'a> Parser<'a> {
809809
}
810810
_ => None,
811811
},
812+
tok @ Token::PgJsonGetIndex
813+
| tok @ Token::PgJsonGetIndexText
814+
| tok @ Token::PgJsonGetPath
815+
| tok @ Token::PgJsonGetPathText
816+
| tok @ Token::PgJsonGt
817+
| tok @ Token::PgJsonLt
818+
| tok @ Token::PgJsonKeyExists
819+
| tok @ Token::PgJsonAnyKeyExists
820+
| tok @ Token::PgJsonAllKeysExist
821+
| tok @ Token::PgJsonMinus => Some(BinaryOperator::PgJsonBinOp(tok.to_string())),
812822
_ => None,
813823
};
814824

@@ -1038,6 +1048,9 @@ impl<'a> Parser<'a> {
10381048
Token::Word(w) if w.keyword == Keyword::IS => Ok(17),
10391049
Token::Word(w) if w.keyword == Keyword::AT => Ok(Self::BETWEEN_PREC),
10401050
Token::Word(w) if w.keyword == Keyword::IN => Ok(Self::BETWEEN_PREC),
1051+
Token::PgJsonKeyExists | Token::PgJsonAnyKeyExists | Token::PgJsonAllKeysExist => {
1052+
Ok(Self::BETWEEN_PREC)
1053+
}
10411054
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
10421055
Token::Word(w)
10431056
if w.keyword == Keyword::LIKE
@@ -1047,13 +1060,26 @@ impl<'a> Parser<'a> {
10471060
{
10481061
Ok(Self::BETWEEN_PREC)
10491062
}
1050-
Token::Eq | Token::Lt | Token::LtEq | Token::Neq | Token::Gt | Token::GtEq => Ok(20),
1063+
Token::Eq
1064+
| Token::Lt
1065+
| Token::LtEq
1066+
| Token::Neq
1067+
| Token::Gt
1068+
| Token::GtEq
1069+
| Token::PgJsonGt
1070+
| Token::PgJsonLt => Ok(20),
10511071
Token::Pipe => Ok(21),
10521072
Token::Caret | Token::Sharp | Token::ShiftRight | Token::ShiftLeft => Ok(22),
10531073
Token::Ampersand => Ok(23),
1054-
Token::Plus | Token::Minus => Ok(Self::PLUS_MINUS_PREC),
1074+
Token::Plus | Token::Minus | Token::PgJsonMinus => Ok(Self::PLUS_MINUS_PREC),
10551075
Token::Mult | Token::Div | Token::Mod | Token::StringConcat => Ok(40),
1056-
Token::Colon | Token::LBracket | Token::Period => Ok(45),
1076+
Token::Colon
1077+
| Token::LBracket
1078+
| Token::Period
1079+
| Token::PgJsonGetIndex
1080+
| Token::PgJsonGetIndexText
1081+
| Token::PgJsonGetPath
1082+
| Token::PgJsonGetPathText => Ok(45),
10571083
Token::DoubleColon => Ok(50),
10581084
Token::ExclamationMark => Ok(50),
10591085
_ => Ok(0),

src/tokenizer.rs

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use std::str::Chars;
2121

2222
use super::dialect::keywords::{Keyword, ALL_KEYWORDS, ALL_KEYWORDS_INDEX};
2323
use super::dialect::Dialect;
24+
use super::dialect::PostgreSqlDialect;
2425
use super::dialect::SnowflakeDialect;
2526
#[cfg(feature = "serde")]
2627
use serde::{Deserialize, Serialize};
@@ -104,6 +105,31 @@ pub enum Token {
104105
RBrace,
105106
/// Right Arrow `=>`
106107
RArrow,
108+
/// https://www.postgresql.org/docs/9.5/functions-json.html
109+
/// various PostgreSQL JSON index operators:
110+
/// `->`
111+
PgJsonGetIndex,
112+
/// `->>`
113+
PgJsonGetIndexText,
114+
/// `#>` Get JSON object at specified path
115+
PgJsonGetPath,
116+
/// `#>>` Get JSON object at specified path as text
117+
PgJsonGetPathText,
118+
/// `@>` Does the left JSON value contain the right JSON path/value entries
119+
/// at the top level?
120+
PgJsonGt,
121+
/// <@` Are the left JSON path/value entries contained at the top level
122+
/// within the right JSON value?
123+
PgJsonLt,
124+
/// `?` Does the string exist as a top-level key within the JSON value?
125+
PgJsonKeyExists,
126+
/// `?|` Do any of these array strings exist as top-level keys?
127+
PgJsonAnyKeyExists,
128+
/// `?&` Do all of these array strings exist as top-level keys?
129+
PgJsonAllKeysExist,
130+
/// `#-` Delete the field or element with specified path (for JSON arrays,
131+
/// negative integers count from the end)
132+
PgJsonMinus,
107133
/// Sharp `#` used for PostgreSQL Bitwise XOR operator
108134
Sharp,
109135
/// Tilde `~` used for PostgreSQL Bitwise NOT operator
@@ -163,6 +189,16 @@ impl fmt::Display for Token {
163189
Token::LBrace => f.write_str("{"),
164190
Token::RBrace => f.write_str("}"),
165191
Token::RArrow => f.write_str("=>"),
192+
Token::PgJsonGetIndex => f.write_str("->"),
193+
Token::PgJsonGetIndexText => f.write_str("->>"),
194+
Token::PgJsonGetPath => f.write_str("#>"),
195+
Token::PgJsonGetPathText => f.write_str("#>>"),
196+
Token::PgJsonGt => f.write_str("@>"),
197+
Token::PgJsonLt => f.write_str("@<"),
198+
Token::PgJsonKeyExists => f.write_str("?"),
199+
Token::PgJsonAnyKeyExists => f.write_str("?|"),
200+
Token::PgJsonAllKeysExist => f.write_str("?&"),
201+
Token::PgJsonMinus => f.write_str("#-"),
166202
Token::Sharp => f.write_str("#"),
167203
Token::ExclamationMark => f.write_str("!"),
168204
Token::DoubleExclamationMark => f.write_str("!!"),
@@ -409,6 +445,15 @@ impl<'a> Tokenizer<'a> {
409445
comment,
410446
})))
411447
}
448+
Some('>') if dialect_of!(self is PostgreSqlDialect) => {
449+
chars.next(); // consume >
450+
if let Some('>') = chars.peek() {
451+
chars.next(); // consume >
452+
Ok(Some(Token::PgJsonGetIndexText))
453+
} else {
454+
Ok(Some(Token::PgJsonGetIndex))
455+
}
456+
}
412457
// a regular '-' operator
413458
_ => Ok(Some(Token::Minus)),
414459
}
@@ -505,9 +550,66 @@ impl<'a> Tokenizer<'a> {
505550
comment,
506551
})))
507552
}
553+
'#' => {
554+
chars.next(); // consume #
555+
if dialect_of!(self is PostgreSqlDialect) {
556+
match chars.peek() {
557+
Some('>') => {
558+
chars.next(); // consume >
559+
if let Some('>') = chars.peek() {
560+
chars.next(); // consume >
561+
Ok(Some(Token::PgJsonGetPathText))
562+
} else {
563+
Ok(Some(Token::PgJsonGetPath))
564+
}
565+
}
566+
Some('-') => {
567+
chars.next(); // consume -
568+
Ok(Some(Token::PgJsonMinus))
569+
}
570+
_ => Ok(Some(Token::Sharp)),
571+
}
572+
} else {
573+
Ok(Some(Token::Sharp))
574+
}
575+
}
508576
'~' => self.consume_and_return(chars, Token::Tilde),
509-
'#' => self.consume_and_return(chars, Token::Sharp),
510-
'@' => self.consume_and_return(chars, Token::AtSign),
577+
'@' => {
578+
chars.next(); // consume @
579+
if dialect_of!(self is PostgreSqlDialect) {
580+
match chars.peek() {
581+
Some('>') => {
582+
chars.next(); // consume >
583+
Ok(Some(Token::PgJsonGt))
584+
}
585+
Some('<') => {
586+
chars.next(); // consume <
587+
Ok(Some(Token::PgJsonLt))
588+
}
589+
_ => Ok(Some(Token::AtSign)),
590+
}
591+
} else {
592+
Ok(Some(Token::AtSign))
593+
}
594+
}
595+
'?' => {
596+
chars.next(); // consume ?
597+
if dialect_of!(self is PostgreSqlDialect) {
598+
match chars.peek() {
599+
Some('|') => {
600+
chars.next(); // consume |
601+
Ok(Some(Token::PgJsonAnyKeyExists))
602+
}
603+
Some('&') => {
604+
chars.next(); // consume &
605+
Ok(Some(Token::PgJsonAllKeysExist))
606+
}
607+
_ => Ok(Some(Token::PgJsonKeyExists)),
608+
}
609+
} else {
610+
Ok(Some(Token::Char('?')))
611+
}
612+
}
511613
other => self.consume_and_return(chars, Token::Char(other)),
512614
},
513615
None => Ok(None),

0 commit comments

Comments
 (0)