diff --git a/src/ast/operator.rs b/src/ast/operator.rs index 0b022527..244ea517 100644 --- a/src/ast/operator.rs +++ b/src/ast/operator.rs @@ -71,6 +71,7 @@ pub enum BinaryOperator { NotEq, And, Or, + Xor, Like, NotLike, ILike, @@ -105,6 +106,7 @@ impl fmt::Display for BinaryOperator { BinaryOperator::NotEq => "<>", BinaryOperator::And => "AND", BinaryOperator::Or => "OR", + BinaryOperator::Xor => "XOR", BinaryOperator::Like => "LIKE", BinaryOperator::NotLike => "NOT LIKE", BinaryOperator::ILike => "ILIKE", diff --git a/src/dialect/keywords.rs b/src/dialect/keywords.rs index f193ede4..b89e68f9 100644 --- a/src/dialect/keywords.rs +++ b/src/dialect/keywords.rs @@ -494,6 +494,7 @@ define_keywords!( WITHOUT, WORK, WRITE, + XOR, YEAR, ZONE ); diff --git a/src/parser.rs b/src/parser.rs index 2a5361d8..9b147b84 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -898,6 +898,7 @@ impl<'a> Parser<'a> { None } } + Keyword::XOR => Some(BinaryOperator::Xor), _ => None, }, _ => None, @@ -1018,6 +1019,7 @@ impl<'a> Parser<'a> { match token { Token::Word(w) if w.keyword == Keyword::OR => Ok(5), Token::Word(w) if w.keyword == Keyword::AND => Ok(10), + Token::Word(w) if w.keyword == Keyword::XOR => Ok(24), Token::Word(w) if w.keyword == Keyword::NOT => match self.peek_nth_token(1) { // The precedence of NOT varies depending on keyword that // follows it. If it is followed by IN, BETWEEN, or LIKE, diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 62a21e53..aab1e75d 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -824,7 +824,47 @@ mod tests { Token::Whitespace(Whitespace::Space), Token::make_word("three", None), ]; + compare(expected, tokens); + } + #[test] + fn tokenize_logical_xor() { + let sql = + String::from("SELECT true XOR true, false XOR false, true XOR false, false XOR true"); + let dialect = GenericDialect {}; + let mut tokenizer = Tokenizer::new(&dialect, &sql); + let tokens = tokenizer.tokenize().unwrap(); + + let expected = vec![ + Token::make_keyword("SELECT"), + Token::Whitespace(Whitespace::Space), + Token::make_keyword("true"), + Token::Whitespace(Whitespace::Space), + Token::make_keyword("XOR"), + Token::Whitespace(Whitespace::Space), + Token::make_keyword("true"), + Token::Comma, + Token::Whitespace(Whitespace::Space), + Token::make_keyword("false"), + Token::Whitespace(Whitespace::Space), + Token::make_keyword("XOR"), + Token::Whitespace(Whitespace::Space), + Token::make_keyword("false"), + Token::Comma, + Token::Whitespace(Whitespace::Space), + Token::make_keyword("true"), + Token::Whitespace(Whitespace::Space), + Token::make_keyword("XOR"), + Token::Whitespace(Whitespace::Space), + Token::make_keyword("false"), + Token::Comma, + Token::Whitespace(Whitespace::Space), + Token::make_keyword("false"), + Token::Whitespace(Whitespace::Space), + Token::make_keyword("XOR"), + Token::Whitespace(Whitespace::Space), + Token::make_keyword("true"), + ]; compare(expected, tokens); } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 5bc052a3..6ace8e81 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -815,6 +815,44 @@ fn parse_bitwise_ops() { } } +#[test] +fn parse_logical_xor() { + let sql = "SELECT true XOR true, false XOR false, true XOR false, false XOR true"; + let select = verified_only_select(sql); + assert_eq!( + SelectItem::UnnamedExpr(Expr::BinaryOp { + left: Box::new(Expr::Value(Value::Boolean(true))), + op: BinaryOperator::Xor, + right: Box::new(Expr::Value(Value::Boolean(true))), + }), + select.projection[0] + ); + assert_eq!( + SelectItem::UnnamedExpr(Expr::BinaryOp { + left: Box::new(Expr::Value(Value::Boolean(false))), + op: BinaryOperator::Xor, + right: Box::new(Expr::Value(Value::Boolean(false))), + }), + select.projection[1] + ); + assert_eq!( + SelectItem::UnnamedExpr(Expr::BinaryOp { + left: Box::new(Expr::Value(Value::Boolean(true))), + op: BinaryOperator::Xor, + right: Box::new(Expr::Value(Value::Boolean(false))), + }), + select.projection[2] + ); + assert_eq!( + SelectItem::UnnamedExpr(Expr::BinaryOp { + left: Box::new(Expr::Value(Value::Boolean(false))), + op: BinaryOperator::Xor, + right: Box::new(Expr::Value(Value::Boolean(true))), + }), + select.projection[3] + ); +} + #[test] fn parse_between() { fn chk(negated: bool) {