Skip to content

Commit 67603ca

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 67603ca

File tree

2 files changed

+100
-78
lines changed

2 files changed

+100
-78
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

+44-27
Original file line numberDiff line numberDiff line change
@@ -360,34 +360,51 @@ fn parse_not_precedence() {
360360

361361
#[test]
362362
fn parse_like() {
363-
let sql = "SELECT * FROM customers WHERE name LIKE '%a'";
364-
let select = verified_only_select(sql);
365-
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-
))),
372-
},
373-
select.selection.unwrap()
374-
);
375-
}
363+
fn chk(negated: bool) {
364+
let sql = &format!(
365+
"SELECT * FROM customers WHERE name {}LIKE '%a'",
366+
if negated { "NOT " } else { "" }
367+
);
368+
let select = verified_only_select(sql);
369+
assert_eq!(
370+
ASTNode::SQLBinaryExpr {
371+
left: Box::new(ASTNode::SQLIdentifier("name".to_string())),
372+
op: if negated {
373+
SQLOperator::NotLike
374+
} else {
375+
SQLOperator::Like
376+
},
377+
right: Box::new(ASTNode::SQLValue(Value::SingleQuotedString(
378+
"%a".to_string()
379+
))),
380+
},
381+
select.selection.unwrap()
382+
);
376383

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);
381-
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-
))),
388-
},
389-
select.selection.unwrap()
390-
);
384+
// This statement tests that LIKE and NOT LIKE have the same precedence.
385+
// This was previously mishandled (#81).
386+
let sql = &format!(
387+
"SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL",
388+
if negated { "NOT " } else { "" }
389+
);
390+
let select = verified_only_select(sql);
391+
assert_eq!(
392+
ASTNode::SQLIsNull(Box::new(ASTNode::SQLBinaryExpr {
393+
left: Box::new(ASTNode::SQLIdentifier("name".to_string())),
394+
op: if negated {
395+
SQLOperator::NotLike
396+
} else {
397+
SQLOperator::Like
398+
},
399+
right: Box::new(ASTNode::SQLValue(Value::SingleQuotedString(
400+
"%a".to_string()
401+
))),
402+
})),
403+
select.selection.unwrap()
404+
);
405+
}
406+
chk(false);
407+
chk(true);
391408
}
392409

393410
#[test]

0 commit comments

Comments
 (0)