Support ESCAPE clause for LIKE and similar expressions

This commit is contained in:
Simon Binder 2019-07-01 13:52:46 +02:00
parent 44663d3648
commit 791afdb6bf
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
6 changed files with 71 additions and 4 deletions

View File

@ -137,6 +137,7 @@ abstract class AstVisitor<T> {
T visitSetComponent(SetComponent e);
T visitBinaryExpression(BinaryExpression e);
T visitStringComparison(StringComparisonExpression e);
T visitUnaryExpression(UnaryExpression e);
T visitIsExpression(IsExpression e);
T visitBetweenExpression(BetweenExpression e);
@ -156,6 +157,9 @@ class RecursiveVisitor<T> extends AstVisitor<T> {
@override
T visitBinaryExpression(BinaryExpression e) => visitChildren(e);
@override
T visitStringComparison(StringComparisonExpression e) => visitChildren(e);
@override
T visitFunction(FunctionExpression e) => visitChildren(e);

View File

@ -37,6 +37,30 @@ class BinaryExpression extends Expression {
}
}
class StringComparisonExpression extends Expression {
final bool not;
final Token operator;
final Expression left;
final Expression right;
final Expression escape;
StringComparisonExpression(
{this.not = false,
@required this.left,
@required this.operator,
@required this.right,
this.escape});
@override
T accept<T>(AstVisitor<T> visitor) => visitor.visitStringComparison(this);
@override
Iterable<AstNode> get childNodes => [left, right, if (escape != null) escape];
@override
bool contentEquals(StringComparisonExpression other) => other.not == not;
}
class IsExpression extends Expression {
final bool negated;
final Expression left;

View File

@ -77,6 +77,13 @@ class Parser {
return false;
}
/// Like [_checkWithNot], but with more than one token type.
bool _checkAnyWithNot(List<TokenType> types) {
if (types.any(_check)) return true;
if (_check(TokenType.not) && types.contains(_peekNext.type)) return true;
return false;
}
bool _check(TokenType type) {
if (_isAtEnd) return false;
return _peek.type == type;
@ -502,8 +509,12 @@ class Parser {
Expression _or() => _parseSimpleBinary(const [TokenType.or], _and);
Expression _and() => _parseSimpleBinary(const [TokenType.and], _equals);
/// Parses expressions with the "equals" precedence. This contains
/// comparisons, "IS (NOT) IN" expressions, between expressions and "like"
/// expressions.
Expression _equals() {
var expression = _comparison();
final ops = const [
TokenType.equal,
TokenType.doubleEqual,
@ -511,6 +522,8 @@ class Parser {
TokenType.lessMore,
TokenType.$is,
TokenType.$in,
];
final stringOps = const [
TokenType.like,
TokenType.glob,
TokenType.match,
@ -537,6 +550,23 @@ class Parser {
} else {
expression = BinaryExpression(expression, operator, _comparison());
}
} else if (_checkAnyWithNot(stringOps)) {
final not = _matchOne(TokenType.not);
_match(stringOps); // will consume, existence was verified with check
final operator = _previous;
final right = _comparison();
Expression escape;
if (_matchOne(TokenType.escape)) {
escape = _comparison();
}
expression = StringComparisonExpression(
not: not,
left: expression,
operator: operator,
right: right,
escape: escape);
} else {
break; // no matching operator with this precedence was found
}

View File

@ -30,6 +30,7 @@ enum TokenType {
glob,
match,
regexp,
escape,
and,
or,
tilde,
@ -131,6 +132,7 @@ const Map<String, TokenType> keywords = {
'GLOB': TokenType.glob,
'MATCH': TokenType.match,
'REGEXP': TokenType.regexp,
'ESCAPE': TokenType.escape,
'NOT': TokenType.not,
'TRUE': TokenType.$true,
'FALSE': TokenType.$false,

View File

@ -65,6 +65,13 @@ final Map<String, Expression> _testCases = {
],
elseExpr: Reference(columnName: 'e'),
),
"x NOT LIKE '%A%\$' ESCAPE '\$'": StringComparisonExpression(
not: true,
left: Reference(columnName: 'x'),
operator: token(TokenType.like),
right: StringLiteral.from(token(TokenType.stringLiteral), '%A%\$'),
escape: StringLiteral.from(token(TokenType.stringLiteral), '\$'),
),
};
void main() {

View File

@ -15,10 +15,10 @@ void main() {
stmt.groupBy,
GroupBy(
by: [Reference(columnName: 'country')],
having: BinaryExpression(
Reference(columnName: 'country'),
token(TokenType.like),
StringLiteral.from(token(TokenType.stringLiteral), '%G%'),
having: StringComparisonExpression(
left: Reference(columnName: 'country'),
operator: token(TokenType.like),
right: StringLiteral.from(token(TokenType.stringLiteral), '%G%'),
),
),
);