Skip to content

Commit 1cc9d2d

Browse files
authored
Merge pull request #82 from benesch/not-prec
Fix the precedence of NOT LIKE
2 parents 58420ca + 90bcf55 commit 1cc9d2d

File tree

2 files changed

+143
-78
lines changed

2 files changed

+143
-78
lines changed

src/sqlparser.rs

+59-54
Original file line numberDiff line numberDiff line change
@@ -190,13 +190,10 @@ impl Parser {
190190
}
191191
"CASE" => self.parse_case_expression(),
192192
"CAST" => self.parse_cast_expression(),
193-
"NOT" => {
194-
let p = self.get_precedence(&Token::make_keyword("NOT"))?;
195-
Ok(ASTNode::SQLUnary {
196-
operator: SQLOperator::Not,
197-
expr: Box::new(self.parse_subexpr(p)?),
198-
})
199-
}
193+
"NOT" => Ok(ASTNode::SQLUnary {
194+
operator: SQLOperator::Not,
195+
expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?),
196+
}),
200197
// Here `w` is a word, check if it's a part of a multi-part
201198
// identifier, a function call, or a simple identifier:
202199
_ => match self.peek_token() {
@@ -230,15 +227,14 @@ impl Parser {
230227
}, // End of Token::SQLWord
231228
Token::Mult => Ok(ASTNode::SQLWildcard),
232229
tok @ Token::Minus | tok @ Token::Plus => {
233-
let p = self.get_precedence(&tok)?;
234230
let operator = if tok == Token::Plus {
235231
SQLOperator::Plus
236232
} else {
237233
SQLOperator::Minus
238234
};
239235
Ok(ASTNode::SQLUnary {
240236
operator,
241-
expr: Box::new(self.parse_subexpr(p)?),
237+
expr: Box::new(self.parse_subexpr(Self::PLUS_MINUS_PREC)?),
242238
})
243239
}
244240
Token::Number(_) | Token::SingleQuotedString(_) | Token::NationalStringLiteral(_) => {
@@ -510,10 +506,9 @@ impl Parser {
510506
pub fn parse_between(&mut self, expr: ASTNode, negated: bool) -> Result<ASTNode, ParserError> {
511507
// Stop parsing subexpressions for <low> and <high> on tokens with
512508
// precedence lower than that of `BETWEEN`, such as `AND`, `IS`, etc.
513-
let prec = self.get_precedence(&Token::make_keyword("BETWEEN"))?;
514-
let low = self.parse_subexpr(prec)?;
509+
let low = self.parse_subexpr(Self::BETWEEN_PREC)?;
515510
self.expect_keyword("AND")?;
516-
let high = self.parse_subexpr(prec)?;
511+
let high = self.parse_subexpr(Self::BETWEEN_PREC)?;
517512
Ok(ASTNode::SQLBetween {
518513
expr: Box::new(expr),
519514
negated,
@@ -530,71 +525,81 @@ impl Parser {
530525
})
531526
}
532527

528+
const UNARY_NOT_PREC: u8 = 15;
529+
const BETWEEN_PREC: u8 = 20;
530+
const PLUS_MINUS_PREC: u8 = 30;
531+
533532
/// Get the precedence of the next token
534533
pub fn get_next_precedence(&self) -> Result<u8, ParserError> {
535534
if let Some(token) = self.peek_token() {
536-
self.get_precedence(&token)
535+
debug!("get_precedence() {:?}", token);
536+
537+
match &token {
538+
Token::SQLWord(k) if k.keyword == "OR" => Ok(5),
539+
Token::SQLWord(k) if k.keyword == "AND" => Ok(10),
540+
Token::SQLWord(k) if k.keyword == "NOT" => match &self.peek_nth_token(1) {
541+
// The precedence of NOT varies depending on keyword that
542+
// follows it. If it is followed by IN, BETWEEN, or LIKE,
543+
// it takes on the precedence of those tokens. Otherwise it
544+
// takes on UNARY_NOT_PREC.
545+
Some(Token::SQLWord(k)) if k.keyword == "IN" => Ok(Self::BETWEEN_PREC),
546+
Some(Token::SQLWord(k)) if k.keyword == "BETWEEN" => Ok(Self::BETWEEN_PREC),
547+
Some(Token::SQLWord(k)) if k.keyword == "LIKE" => Ok(Self::BETWEEN_PREC),
548+
_ => Ok(Self::UNARY_NOT_PREC),
549+
},
550+
Token::SQLWord(k) if k.keyword == "IS" => Ok(17),
551+
Token::SQLWord(k) if k.keyword == "IN" => Ok(Self::BETWEEN_PREC),
552+
Token::SQLWord(k) if k.keyword == "BETWEEN" => Ok(Self::BETWEEN_PREC),
553+
Token::SQLWord(k) if k.keyword == "LIKE" => Ok(Self::BETWEEN_PREC),
554+
Token::Eq | Token::Lt | Token::LtEq | Token::Neq | Token::Gt | Token::GtEq => {
555+
Ok(20)
556+
}
557+
Token::Plus | Token::Minus => Ok(Self::PLUS_MINUS_PREC),
558+
Token::Mult | Token::Div | Token::Mod => Ok(40),
559+
Token::DoubleColon => Ok(50),
560+
_ => Ok(0),
561+
}
537562
} else {
538563
Ok(0)
539564
}
540565
}
541566

542-
/// Get the precedence of a token
543-
pub fn get_precedence(&self, tok: &Token) -> Result<u8, ParserError> {
544-
debug!("get_precedence() {:?}", tok);
545-
546-
match tok {
547-
Token::SQLWord(k) if k.keyword == "OR" => Ok(5),
548-
Token::SQLWord(k) if k.keyword == "AND" => Ok(10),
549-
Token::SQLWord(k) if k.keyword == "NOT" => Ok(15),
550-
Token::SQLWord(k) if k.keyword == "IS" => Ok(17),
551-
Token::SQLWord(k) if k.keyword == "IN" => Ok(20),
552-
Token::SQLWord(k) if k.keyword == "BETWEEN" => Ok(20),
553-
Token::SQLWord(k) if k.keyword == "LIKE" => Ok(20),
554-
Token::Eq | Token::Lt | Token::LtEq | Token::Neq | Token::Gt | Token::GtEq => Ok(20),
555-
Token::Plus | Token::Minus => Ok(30),
556-
Token::Mult | Token::Div | Token::Mod => Ok(40),
557-
Token::DoubleColon => Ok(50),
558-
_ => Ok(0),
559-
}
560-
}
561-
562567
/// Return first non-whitespace token that has not yet been processed
563568
pub fn peek_token(&self) -> Option<Token> {
564-
if let Some(n) = self.til_non_whitespace() {
565-
self.token_at(n)
566-
} else {
567-
None
568-
}
569+
self.peek_nth_token(0)
569570
}
570571

571-
/// Get the next token skipping whitespace and increment the token index
572-
pub fn next_token(&mut self) -> Option<Token> {
572+
/// Return nth non-whitespace token that has not yet been processed
573+
pub fn peek_nth_token(&self, mut n: usize) -> Option<Token> {
574+
let mut index = self.index;
573575
loop {
574-
match self.next_token_no_skip() {
576+
match self.token_at(index) {
575577
Some(Token::Whitespace(_)) => {
576-
continue;
578+
index += 1;
577579
}
578-
token => {
579-
return token;
580+
Some(token) => {
581+
if n == 0 {
582+
return Some(token);
583+
}
584+
index += 1;
585+
n -= 1;
586+
}
587+
None => {
588+
return None;
580589
}
581590
}
582591
}
583592
}
584593

585-
/// get the index for non whitepsace token
586-
fn til_non_whitespace(&self) -> Option<usize> {
587-
let mut index = self.index;
594+
/// Get the next token skipping whitespace and increment the token index
595+
pub fn next_token(&mut self) -> Option<Token> {
588596
loop {
589-
match self.token_at(index) {
597+
match self.next_token_no_skip() {
590598
Some(Token::Whitespace(_)) => {
591-
index += 1;
592-
}
593-
Some(_) => {
594-
return Some(index);
599+
continue;
595600
}
596-
None => {
597-
return None;
601+
token => {
602+
return token;
598603
}
599604
}
600605
}

tests/sqlparser_common.rs

+84-24
Original file line numberDiff line numberDiff line change
@@ -428,40 +428,100 @@ fn parse_not_precedence() {
428428
operator: SQLOperator::Not,
429429
..
430430
});
431-
}
432431

433-
#[test]
434-
fn parse_like() {
435-
let sql = "SELECT * FROM customers WHERE name LIKE '%a'";
436-
let select = verified_only_select(sql);
432+
// NOT has lower precedence than BETWEEN, so the following parses as NOT (1 NOT BETWEEN 1 AND 2)
433+
let sql = "NOT 1 NOT BETWEEN 1 AND 2";
437434
assert_eq!(
438-
ASTNode::SQLBinaryExpr {
439-
left: Box::new(ASTNode::SQLIdentifier("name".to_string())),
440-
op: SQLOperator::Like,
441-
right: Box::new(ASTNode::SQLValue(Value::SingleQuotedString(
442-
"%a".to_string()
443-
))),
435+
verified_expr(sql),
436+
SQLUnary {
437+
operator: SQLOperator::Not,
438+
expr: Box::new(SQLBetween {
439+
expr: Box::new(SQLValue(Value::Long(1))),
440+
low: Box::new(SQLValue(Value::Long(1))),
441+
high: Box::new(SQLValue(Value::Long(2))),
442+
negated: true,
443+
}),
444444
},
445-
select.selection.unwrap()
446445
);
447-
}
448446

449-
#[test]
450-
fn parse_not_like() {
451-
let sql = "SELECT * FROM customers WHERE name NOT LIKE '%a'";
452-
let select = verified_only_select(sql);
447+
// NOT has lower precedence than LIKE, so the following parses as NOT ('a' NOT LIKE 'b')
448+
let sql = "NOT 'a' NOT LIKE 'b'";
453449
assert_eq!(
454-
ASTNode::SQLBinaryExpr {
455-
left: Box::new(ASTNode::SQLIdentifier("name".to_string())),
456-
op: SQLOperator::NotLike,
457-
right: Box::new(ASTNode::SQLValue(Value::SingleQuotedString(
458-
"%a".to_string()
459-
))),
450+
verified_expr(sql),
451+
SQLUnary {
452+
operator: SQLOperator::Not,
453+
expr: Box::new(SQLBinaryExpr {
454+
left: Box::new(SQLValue(Value::SingleQuotedString("a".into()))),
455+
op: SQLOperator::NotLike,
456+
right: Box::new(SQLValue(Value::SingleQuotedString("b".into()))),
457+
}),
458+
},
459+
);
460+
461+
// NOT has lower precedence than IN, so the following parses as NOT (a NOT IN 'a')
462+
let sql = "NOT a NOT IN ('a')";
463+
assert_eq!(
464+
verified_expr(sql),
465+
SQLUnary {
466+
operator: SQLOperator::Not,
467+
expr: Box::new(SQLInList {
468+
expr: Box::new(SQLIdentifier("a".into())),
469+
list: vec![SQLValue(Value::SingleQuotedString("a".into()))],
470+
negated: true,
471+
}),
460472
},
461-
select.selection.unwrap()
462473
);
463474
}
464475

476+
#[test]
477+
fn parse_like() {
478+
fn chk(negated: bool) {
479+
let sql = &format!(
480+
"SELECT * FROM customers WHERE name {}LIKE '%a'",
481+
if negated { "NOT " } else { "" }
482+
);
483+
let select = verified_only_select(sql);
484+
assert_eq!(
485+
ASTNode::SQLBinaryExpr {
486+
left: Box::new(ASTNode::SQLIdentifier("name".to_string())),
487+
op: if negated {
488+
SQLOperator::NotLike
489+
} else {
490+
SQLOperator::Like
491+
},
492+
right: Box::new(ASTNode::SQLValue(Value::SingleQuotedString(
493+
"%a".to_string()
494+
))),
495+
},
496+
select.selection.unwrap()
497+
);
498+
499+
// This statement tests that LIKE and NOT LIKE have the same precedence.
500+
// This was previously mishandled (#81).
501+
let sql = &format!(
502+
"SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL",
503+
if negated { "NOT " } else { "" }
504+
);
505+
let select = verified_only_select(sql);
506+
assert_eq!(
507+
ASTNode::SQLIsNull(Box::new(ASTNode::SQLBinaryExpr {
508+
left: Box::new(ASTNode::SQLIdentifier("name".to_string())),
509+
op: if negated {
510+
SQLOperator::NotLike
511+
} else {
512+
SQLOperator::Like
513+
},
514+
right: Box::new(ASTNode::SQLValue(Value::SingleQuotedString(
515+
"%a".to_string()
516+
))),
517+
})),
518+
select.selection.unwrap()
519+
);
520+
}
521+
chk(false);
522+
chk(true);
523+
}
524+
465525
#[test]
466526
fn parse_in_list() {
467527
fn chk(negated: bool) {

0 commit comments

Comments
 (0)