Skip to content

Commit 1c64129

Browse files
iskakaushikalamb
andauthored
[postgres] Add support for custom binary operators (apache#548)
* [postgres] Add support for custom binary operators More details about operators in general are at: https://www.postgresql.org/docs/current/sql-createoperator.html. This patch attempts to parse `SELECT` queries that reference an operator using `OPERATOR(<optional_schema>.<operator_name>)` syntax. This is a PostgreSQL extension. There are no provisions for user-defined operators in the SQL standard. * fix code-review comments and ci failures * Allow custom operator in generic dialects too * Parse `OPERATOR` as Vec<String> * fix: std Co-authored-by: Andrew Lamb <[email protected]>
1 parent 6c98228 commit 1c64129

File tree

4 files changed

+114
-32
lines changed

4 files changed

+114
-32
lines changed

src/ast/operator.rs

+44-32
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@
1212

1313
use core::fmt;
1414

15+
#[cfg(not(feature = "std"))]
16+
use alloc::{string::String, vec::Vec};
1517
#[cfg(feature = "serde")]
1618
use serde::{Deserialize, Serialize};
1719

20+
use super::display_separated;
21+
1822
/// Unary operators
1923
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2024
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -86,41 +90,49 @@ pub enum BinaryOperator {
8690
PGRegexIMatch,
8791
PGRegexNotMatch,
8892
PGRegexNotIMatch,
93+
/// PostgreSQL-specific custom operator.
94+
///
95+
/// See [CREATE OPERATOR](https://www.postgresql.org/docs/current/sql-createoperator.html)
96+
/// for more information.
97+
PGCustomBinaryOperator(Vec<String>),
8998
}
9099

91100
impl fmt::Display for BinaryOperator {
92101
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93-
f.write_str(match self {
94-
BinaryOperator::Plus => "+",
95-
BinaryOperator::Minus => "-",
96-
BinaryOperator::Multiply => "*",
97-
BinaryOperator::Divide => "/",
98-
BinaryOperator::Modulo => "%",
99-
BinaryOperator::StringConcat => "||",
100-
BinaryOperator::Gt => ">",
101-
BinaryOperator::Lt => "<",
102-
BinaryOperator::GtEq => ">=",
103-
BinaryOperator::LtEq => "<=",
104-
BinaryOperator::Spaceship => "<=>",
105-
BinaryOperator::Eq => "=",
106-
BinaryOperator::NotEq => "<>",
107-
BinaryOperator::And => "AND",
108-
BinaryOperator::Or => "OR",
109-
BinaryOperator::Xor => "XOR",
110-
BinaryOperator::Like => "LIKE",
111-
BinaryOperator::NotLike => "NOT LIKE",
112-
BinaryOperator::ILike => "ILIKE",
113-
BinaryOperator::NotILike => "NOT ILIKE",
114-
BinaryOperator::BitwiseOr => "|",
115-
BinaryOperator::BitwiseAnd => "&",
116-
BinaryOperator::BitwiseXor => "^",
117-
BinaryOperator::PGBitwiseXor => "#",
118-
BinaryOperator::PGBitwiseShiftLeft => "<<",
119-
BinaryOperator::PGBitwiseShiftRight => ">>",
120-
BinaryOperator::PGRegexMatch => "~",
121-
BinaryOperator::PGRegexIMatch => "~*",
122-
BinaryOperator::PGRegexNotMatch => "!~",
123-
BinaryOperator::PGRegexNotIMatch => "!~*",
124-
})
102+
match self {
103+
BinaryOperator::Plus => f.write_str("+"),
104+
BinaryOperator::Minus => f.write_str("-"),
105+
BinaryOperator::Multiply => f.write_str("*"),
106+
BinaryOperator::Divide => f.write_str("/"),
107+
BinaryOperator::Modulo => f.write_str("%"),
108+
BinaryOperator::StringConcat => f.write_str("||"),
109+
BinaryOperator::Gt => f.write_str(">"),
110+
BinaryOperator::Lt => f.write_str("<"),
111+
BinaryOperator::GtEq => f.write_str(">="),
112+
BinaryOperator::LtEq => f.write_str("<="),
113+
BinaryOperator::Spaceship => f.write_str("<=>"),
114+
BinaryOperator::Eq => f.write_str("="),
115+
BinaryOperator::NotEq => f.write_str("<>"),
116+
BinaryOperator::And => f.write_str("AND"),
117+
BinaryOperator::Or => f.write_str("OR"),
118+
BinaryOperator::Xor => f.write_str("XOR"),
119+
BinaryOperator::Like => f.write_str("LIKE"),
120+
BinaryOperator::NotLike => f.write_str("NOT LIKE"),
121+
BinaryOperator::ILike => f.write_str("ILIKE"),
122+
BinaryOperator::NotILike => f.write_str("NOT ILIKE"),
123+
BinaryOperator::BitwiseOr => f.write_str("|"),
124+
BinaryOperator::BitwiseAnd => f.write_str("&"),
125+
BinaryOperator::BitwiseXor => f.write_str("^"),
126+
BinaryOperator::PGBitwiseXor => f.write_str("#"),
127+
BinaryOperator::PGBitwiseShiftLeft => f.write_str("<<"),
128+
BinaryOperator::PGBitwiseShiftRight => f.write_str(">>"),
129+
BinaryOperator::PGRegexMatch => f.write_str("~"),
130+
BinaryOperator::PGRegexIMatch => f.write_str("~*"),
131+
BinaryOperator::PGRegexNotMatch => f.write_str("!~"),
132+
BinaryOperator::PGRegexNotIMatch => f.write_str("!~*"),
133+
BinaryOperator::PGCustomBinaryOperator(idents) => {
134+
write!(f, "OPERATOR({})", display_separated(idents, "."))
135+
}
136+
}
125137
}
126138
}

src/keywords.rs

+1
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ define_keywords!(
358358
ON,
359359
ONLY,
360360
OPEN,
361+
OPERATOR,
361362
OPTION,
362363
OR,
363364
ORC,

src/parser.rs

+17
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,22 @@ impl<'a> Parser<'a> {
11731173
}
11741174
}
11751175
Keyword::XOR => Some(BinaryOperator::Xor),
1176+
Keyword::OPERATOR if dialect_of!(self is PostgreSqlDialect | GenericDialect) => {
1177+
self.expect_token(&Token::LParen)?;
1178+
// there are special rules for operator names in
1179+
// postgres so we can not use 'parse_object'
1180+
// or similar.
1181+
// See https://www.postgresql.org/docs/current/sql-createoperator.html
1182+
let mut idents = vec![];
1183+
loop {
1184+
idents.push(self.next_token().to_string());
1185+
if !self.consume_token(&Token::Period) {
1186+
break;
1187+
}
1188+
}
1189+
self.expect_token(&Token::RParen)?;
1190+
Some(BinaryOperator::PGCustomBinaryOperator(idents))
1191+
}
11761192
_ => None,
11771193
},
11781194
_ => None,
@@ -1437,6 +1453,7 @@ impl<'a> Parser<'a> {
14371453
Token::Word(w) if w.keyword == Keyword::BETWEEN => Ok(Self::BETWEEN_PREC),
14381454
Token::Word(w) if w.keyword == Keyword::LIKE => Ok(Self::BETWEEN_PREC),
14391455
Token::Word(w) if w.keyword == Keyword::ILIKE => Ok(Self::BETWEEN_PREC),
1456+
Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(Self::BETWEEN_PREC),
14401457
Token::Eq
14411458
| Token::Lt
14421459
| Token::LtEq

tests/sqlparser_postgres.rs

+52
Original file line numberDiff line numberDiff line change
@@ -1565,3 +1565,55 @@ fn parse_fetch() {
15651565
pg_and_generic()
15661566
.verified_stmt("FETCH BACKWARD ALL IN \"SQL_CUR0x7fa44801bc00\" INTO \"new_table\"");
15671567
}
1568+
1569+
#[test]
1570+
fn parse_custom_operator() {
1571+
// operator with a database and schema
1572+
let sql = r#"SELECT * FROM events WHERE relname OPERATOR(database.pg_catalog.~) '^(table)$'"#;
1573+
let select = pg().verified_only_select(sql);
1574+
assert_eq!(
1575+
select.selection,
1576+
Some(Expr::BinaryOp {
1577+
left: Box::new(Expr::Identifier(Ident {
1578+
value: "relname".into(),
1579+
quote_style: None,
1580+
})),
1581+
op: BinaryOperator::PGCustomBinaryOperator(vec![
1582+
"database".into(),
1583+
"pg_catalog".into(),
1584+
"~".into()
1585+
]),
1586+
right: Box::new(Expr::Value(Value::SingleQuotedString("^(table)$".into())))
1587+
})
1588+
);
1589+
1590+
// operator with a schema
1591+
let sql = r#"SELECT * FROM events WHERE relname OPERATOR(pg_catalog.~) '^(table)$'"#;
1592+
let select = pg().verified_only_select(sql);
1593+
assert_eq!(
1594+
select.selection,
1595+
Some(Expr::BinaryOp {
1596+
left: Box::new(Expr::Identifier(Ident {
1597+
value: "relname".into(),
1598+
quote_style: None,
1599+
})),
1600+
op: BinaryOperator::PGCustomBinaryOperator(vec!["pg_catalog".into(), "~".into()]),
1601+
right: Box::new(Expr::Value(Value::SingleQuotedString("^(table)$".into())))
1602+
})
1603+
);
1604+
1605+
// custom operator without a schema
1606+
let sql = r#"SELECT * FROM events WHERE relname OPERATOR(~) '^(table)$'"#;
1607+
let select = pg().verified_only_select(sql);
1608+
assert_eq!(
1609+
select.selection,
1610+
Some(Expr::BinaryOp {
1611+
left: Box::new(Expr::Identifier(Ident {
1612+
value: "relname".into(),
1613+
quote_style: None,
1614+
})),
1615+
op: BinaryOperator::PGCustomBinaryOperator(vec!["~".into()]),
1616+
right: Box::new(Expr::Value(Value::SingleQuotedString("^(table)$".into())))
1617+
})
1618+
);
1619+
}

0 commit comments

Comments
 (0)