Skip to content

Commit aac7085

Browse files
committed
Fix the precedence of NOT LIKE
NOT LIKE has the same precedence as the LIKE operator. The parser was previously assigning it the precedence of the unary NOT operator. Fixes apache#81.
1 parent 4f944dd commit aac7085

File tree

2 files changed

+140
-75
lines changed

2 files changed

+140
-75
lines changed

src/sqlparser.rs

+56-51
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,10 @@ impl Parser {
173173
}
174174
"CASE" => self.parse_case_expression(),
175175
"CAST" => self.parse_cast_expression(),
176-
"NOT" => {
177-
let p = self.get_precedence(&Token::make_keyword("NOT"))?;
178-
Ok(ASTNode::SQLUnary {
179-
operator: SQLOperator::Not,
180-
expr: Box::new(self.parse_subexpr(p)?),
181-
})
182-
}
176+
"NOT" => Ok(ASTNode::SQLUnary {
177+
operator: SQLOperator::Not,
178+
expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?),
179+
}),
183180
// Here `w` is a word, check if it's a part of a multi-part
184181
// identifier, a function call, or a simple identifier:
185182
_ => match self.peek_token() {
@@ -213,15 +210,14 @@ impl Parser {
213210
}, // End of Token::SQLWord
214211
Token::Mult => Ok(ASTNode::SQLWildcard),
215212
tok @ Token::Minus | tok @ Token::Plus => {
216-
let p = self.get_precedence(&tok)?;
217213
let operator = if tok == Token::Plus {
218214
SQLOperator::Plus
219215
} else {
220216
SQLOperator::Minus
221217
};
222218
Ok(ASTNode::SQLUnary {
223219
operator,
224-
expr: Box::new(self.parse_subexpr(p)?),
220+
expr: Box::new(self.parse_subexpr(Self::PLUS_MINUS_PREC)?),
225221
})
226222
}
227223
Token::Number(_) | Token::SingleQuotedString(_) | Token::NationalStringLiteral(_) => {
@@ -497,71 +493,80 @@ impl Parser {
497493
})
498494
}
499495

496+
const UNARY_NOT_PREC: u8 = 15;
497+
const PLUS_MINUS_PREC: u8 = 30;
498+
500499
/// Get the precedence of the next token
501500
pub fn get_next_precedence(&self) -> Result<u8, ParserError> {
502501
if let Some(token) = self.peek_token() {
503-
self.get_precedence(&token)
502+
debug!("get_precedence() {:?}", token);
503+
504+
match &token {
505+
Token::SQLWord(k) if k.keyword == "OR" => Ok(5),
506+
Token::SQLWord(k) if k.keyword == "AND" => Ok(10),
507+
Token::SQLWord(k) if k.keyword == "NOT" => match &self.peek_nth_token(1) {
508+
// The precedence of NOT varies depending on keyword that
509+
// follows it. If it is followed by IN, BETWEEN, or LIKE,
510+
// it takes on the precedence of those tokens. Otherwise it
511+
// takes on UNARY_NOT_PREC.
512+
Some(Token::SQLWord(k)) if k.keyword == "IN" => Ok(20),
513+
Some(Token::SQLWord(k)) if k.keyword == "BETWEEN" => Ok(20),
514+
Some(Token::SQLWord(k)) if k.keyword == "LIKE" => Ok(20),
515+
_ => Ok(Self::UNARY_NOT_PREC),
516+
},
517+
Token::SQLWord(k) if k.keyword == "IS" => Ok(17),
518+
Token::SQLWord(k) if k.keyword == "IN" => Ok(20),
519+
Token::SQLWord(k) if k.keyword == "BETWEEN" => Ok(20),
520+
Token::SQLWord(k) if k.keyword == "LIKE" => Ok(20),
521+
Token::Eq | Token::Lt | Token::LtEq | Token::Neq | Token::Gt | Token::GtEq => {
522+
Ok(20)
523+
}
524+
Token::Plus | Token::Minus => Ok(Self::PLUS_MINUS_PREC),
525+
Token::Mult | Token::Div | Token::Mod => Ok(40),
526+
Token::DoubleColon => Ok(50),
527+
_ => Ok(0),
528+
}
504529
} else {
505530
Ok(0)
506531
}
507532
}
508533

509-
/// Get the precedence of a token
510-
pub fn get_precedence(&self, tok: &Token) -> Result<u8, ParserError> {
511-
debug!("get_precedence() {:?}", tok);
512-
513-
match tok {
514-
Token::SQLWord(k) if k.keyword == "OR" => Ok(5),
515-
Token::SQLWord(k) if k.keyword == "AND" => Ok(10),
516-
Token::SQLWord(k) if k.keyword == "NOT" => Ok(15),
517-
Token::SQLWord(k) if k.keyword == "IS" => Ok(17),
518-
Token::SQLWord(k) if k.keyword == "IN" => Ok(20),
519-
Token::SQLWord(k) if k.keyword == "BETWEEN" => Ok(20),
520-
Token::SQLWord(k) if k.keyword == "LIKE" => Ok(20),
521-
Token::Eq | Token::Lt | Token::LtEq | Token::Neq | Token::Gt | Token::GtEq => Ok(20),
522-
Token::Plus | Token::Minus => Ok(30),
523-
Token::Mult | Token::Div | Token::Mod => Ok(40),
524-
Token::DoubleColon => Ok(50),
525-
_ => Ok(0),
526-
}
527-
}
528-
529534
/// Return first non-whitespace token that has not yet been processed
530535
pub fn peek_token(&self) -> Option<Token> {
531-
if let Some(n) = self.til_non_whitespace() {
532-
self.token_at(n)
533-
} else {
534-
None
535-
}
536+
self.peek_nth_token(0)
536537
}
537538

538-
/// Get the next token skipping whitespace and increment the token index
539-
pub fn next_token(&mut self) -> Option<Token> {
539+
/// Return nth non-whitespace token that has not yet been processed
540+
pub fn peek_nth_token(&self, mut n: usize) -> Option<Token> {
541+
let mut index = self.index;
540542
loop {
541-
match self.next_token_no_skip() {
543+
match self.token_at(index) {
542544
Some(Token::Whitespace(_)) => {
543-
continue;
545+
index += 1;
544546
}
545-
token => {
546-
return token;
547+
Some(token) => {
548+
if n == 0 {
549+
return Some(token);
550+
}
551+
index += 1;
552+
n -= 1;
553+
}
554+
None => {
555+
return None;
547556
}
548557
}
549558
}
550559
}
551560

552-
/// get the index for non whitepsace token
553-
fn til_non_whitespace(&self) -> Option<usize> {
554-
let mut index = self.index;
561+
/// Get the next token skipping whitespace and increment the token index
562+
pub fn next_token(&mut self) -> Option<Token> {
555563
loop {
556-
match self.token_at(index) {
564+
match self.next_token_no_skip() {
557565
Some(Token::Whitespace(_)) => {
558-
index += 1;
559-
}
560-
Some(_) => {
561-
return Some(index);
566+
continue;
562567
}
563-
None => {
564-
return None;
568+
token => {
569+
return token;
565570
}
566571
}
567572
}

tests/sqlparser_common.rs

+84-24
Original file line numberDiff line numberDiff line change
@@ -356,38 +356,98 @@ fn parse_not_precedence() {
356356
operator: SQLOperator::Not,
357357
..
358358
});
359-
}
360359

361-
#[test]
362-
fn parse_like() {
363-
let sql = "SELECT * FROM customers WHERE name LIKE '%a'";
364-
let select = verified_only_select(sql);
360+
// NOT has lower precedence than BETWEEN, so the following parses as NOT (1 NOT BETWEEN 1 AND 2)
361+
let sql = "NOT 1 NOT BETWEEN 1 AND 2";
365362
assert_eq!(
366-
ASTNode::SQLBinaryExpr {
367-
left: Box::new(ASTNode::SQLIdentifier("name".to_string())),
368-
op: SQLOperator::Like,
369-
right: Box::new(ASTNode::SQLValue(Value::SingleQuotedString(
370-
"%a".to_string()
371-
))),
363+
verified_expr(sql),
364+
SQLUnary {
365+
operator: SQLOperator::Not,
366+
expr: Box::new(SQLBetween {
367+
expr: Box::new(SQLValue(Value::Long(1))),
368+
low: Box::new(SQLValue(Value::Long(1))),
369+
high: Box::new(SQLValue(Value::Long(2))),
370+
negated: true,
371+
}),
372372
},
373-
select.selection.unwrap()
374373
);
375-
}
376374

377-
#[test]
378-
fn parse_not_like() {
379-
let sql = "SELECT * FROM customers WHERE name NOT LIKE '%a'";
380-
let select = verified_only_select(sql);
375+
// NOT has lower precedence than LIKE, so the following parses as NOT ('a' NOT LIKE 'b')
376+
let sql = "NOT 'a' NOT LIKE 'b'";
381377
assert_eq!(
382-
ASTNode::SQLBinaryExpr {
383-
left: Box::new(ASTNode::SQLIdentifier("name".to_string())),
384-
op: SQLOperator::NotLike,
385-
right: Box::new(ASTNode::SQLValue(Value::SingleQuotedString(
386-
"%a".to_string()
387-
))),
378+
verified_expr(sql),
379+
SQLUnary {
380+
operator: SQLOperator::Not,
381+
expr: Box::new(SQLBinaryExpr {
382+
left: Box::new(SQLValue(Value::SingleQuotedString("a".into()))),
383+
op: SQLOperator::NotLike,
384+
right: Box::new(SQLValue(Value::SingleQuotedString("b".into()))),
385+
}),
388386
},
389-
select.selection.unwrap()
390387
);
388+
389+
// NOT has lower precedence than IN, so the following parses as NOT (a NOT IN 'a')
390+
let sql = "NOT a NOT IN ('a')";
391+
assert_eq!(
392+
verified_expr(sql),
393+
SQLUnary {
394+
operator: SQLOperator::Not,
395+
expr: Box::new(SQLInList {
396+
expr: Box::new(SQLIdentifier("a".into())),
397+
list: vec![SQLValue(Value::SingleQuotedString("a".into()))],
398+
negated: true,
399+
}),
400+
},
401+
);
402+
}
403+
404+
#[test]
405+
fn parse_like() {
406+
fn chk(negated: bool) {
407+
let sql = &format!(
408+
"SELECT * FROM customers WHERE name {}LIKE '%a'",
409+
if negated { "NOT " } else { "" }
410+
);
411+
let select = verified_only_select(sql);
412+
assert_eq!(
413+
ASTNode::SQLBinaryExpr {
414+
left: Box::new(ASTNode::SQLIdentifier("name".to_string())),
415+
op: if negated {
416+
SQLOperator::NotLike
417+
} else {
418+
SQLOperator::Like
419+
},
420+
right: Box::new(ASTNode::SQLValue(Value::SingleQuotedString(
421+
"%a".to_string()
422+
))),
423+
},
424+
select.selection.unwrap()
425+
);
426+
427+
// This statement tests that LIKE and NOT LIKE have the same precedence.
428+
// This was previously mishandled (#81).
429+
let sql = &format!(
430+
"SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL",
431+
if negated { "NOT " } else { "" }
432+
);
433+
let select = verified_only_select(sql);
434+
assert_eq!(
435+
ASTNode::SQLIsNull(Box::new(ASTNode::SQLBinaryExpr {
436+
left: Box::new(ASTNode::SQLIdentifier("name".to_string())),
437+
op: if negated {
438+
SQLOperator::NotLike
439+
} else {
440+
SQLOperator::Like
441+
},
442+
right: Box::new(ASTNode::SQLValue(Value::SingleQuotedString(
443+
"%a".to_string()
444+
))),
445+
})),
446+
select.selection.unwrap()
447+
);
448+
}
449+
chk(false);
450+
chk(true);
391451
}
392452

393453
#[test]

0 commit comments

Comments
 (0)