mirror of https://github.com/AMT-Cheif/drift.git
Support ESCAPE clause for LIKE and similar expressions
This commit is contained in:
parent
44663d3648
commit
791afdb6bf
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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%'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue