diff --git a/sqlparser/lib/src/analysis/types/resolver.dart b/sqlparser/lib/src/analysis/types/resolver.dart index b1309cb6..9e5088cb 100644 --- a/sqlparser/lib/src/analysis/types/resolver.dart +++ b/sqlparser/lib/src/analysis/types/resolver.dart @@ -78,7 +78,10 @@ class TypeResolver { return resolveColumn(expr.resolved as Column); } else if (expr is FunctionExpression) { return resolveFunctionCall(expr); - } else if (expr is IsExpression) { + } else if (expr is IsExpression || + expr is StringComparisonExpression || + expr is BetweenExpression || + expr is ExistsExpression) { return const ResolveResult(ResolvedType.bool()); } else if (expr is BinaryExpression) { final operator = expr.operator.type; @@ -89,10 +92,6 @@ class TypeResolver { [BasicType.int, BasicType.real, BasicType.text, BasicType.blob]); return ResolveResult(type); } - } else if (expr is StringComparisonExpression) { - return const ResolveResult(ResolvedType.bool()); - } else if (expr is BetweenExpression) { - return const ResolveResult(ResolvedType.bool()); } else if (expr is CaseExpression) { return resolveExpression(expr.whens.first.then); } else if (expr is SubQuery) { diff --git a/sqlparser/lib/src/ast/ast.dart b/sqlparser/lib/src/ast/ast.dart index b569d666..c066cf8a 100644 --- a/sqlparser/lib/src/ast/ast.dart +++ b/sqlparser/lib/src/ast/ast.dart @@ -145,6 +145,7 @@ abstract class AstVisitor { T visitReference(Reference e); T visitFunction(FunctionExpression e); T visitSubQuery(SubQuery e); + T visitExists(ExistsExpression e); T visitCaseExpression(CaseExpression e); T visitWhen(WhenComponent e); @@ -181,6 +182,9 @@ class RecursiveVisitor extends AstVisitor { @override T visitSubQuery(SubQuery e) => visitChildren(e); + @override + T visitExists(ExistsExpression e) => visitChildren(e); + @override T visitSetComponent(SetComponent e) => visitChildren(e); diff --git a/sqlparser/lib/src/ast/expressions/subquery.dart b/sqlparser/lib/src/ast/expressions/subquery.dart index a3c21263..ccb14fc6 100644 --- a/sqlparser/lib/src/ast/expressions/subquery.dart +++ b/sqlparser/lib/src/ast/expressions/subquery.dart @@ -16,3 +16,18 @@ class SubQuery extends Expression { @override bool contentEquals(SubQuery other) => true; } + +class ExistsExpression extends Expression { + final SelectStatement select; + + ExistsExpression({@required this.select}); + + @override + T accept(AstVisitor visitor) => visitor.visitExists(this); + + @override + Iterable get childNodes => [select]; + + @override + bool contentEquals(ExistsExpression other) => true; +} diff --git a/sqlparser/lib/src/ast/statements/select.dart b/sqlparser/lib/src/ast/statements/select.dart index d6e89bb4..bcb274d2 100644 --- a/sqlparser/lib/src/ast/statements/select.dart +++ b/sqlparser/lib/src/ast/statements/select.dart @@ -33,9 +33,9 @@ class SelectStatement extends Statement with ResultSet { @override Iterable get childNodes { return [ - if (where != null) where, ...columns, if (from != null) ...from, + if (where != null) where, if (groupBy != null) groupBy, if (limit != null) limit, if (orderBy != null) orderBy, diff --git a/sqlparser/lib/src/reader/parser/parser.dart b/sqlparser/lib/src/reader/parser/parser.dart index 0f862940..44d08745 100644 --- a/sqlparser/lib/src/reader/parser/parser.dart +++ b/sqlparser/lib/src/reader/parser/parser.dart @@ -612,6 +612,13 @@ class Parser { final operator = _previous; final expression = _unary(); return UnaryExpression(operator, expression); + } else if (_matchOne(TokenType.exists)) { + _consume( + TokenType.leftParen, 'Expected opening parenthesis after EXISTS'); + final selectStmt = select(); + _consume(TokenType.rightParen, + 'Expected closing paranthesis to finish EXISTS expression'); + return ExistsExpression(select: selectStmt); } return _postfix(); diff --git a/sqlparser/lib/src/reader/tokenizer/token.dart b/sqlparser/lib/src/reader/tokenizer/token.dart index db894666..13865550 100644 --- a/sqlparser/lib/src/reader/tokenizer/token.dart +++ b/sqlparser/lib/src/reader/tokenizer/token.dart @@ -35,6 +35,7 @@ enum TokenType { or, tilde, between, + exists, questionMark, colon, @@ -104,6 +105,7 @@ const Map keywords = { 'ALL': TokenType.all, 'AND': TokenType.and, 'OR': TokenType.or, + 'EXISTS': TokenType.exists, 'BETWEEN': TokenType.between, 'DELETE': TokenType.delete, 'FROM': TokenType.from, diff --git a/sqlparser/test/parser/expression_test.dart b/sqlparser/test/parser/expression_test.dart index 4adaa1c1..12f1952a 100644 --- a/sqlparser/test/parser/expression_test.dart +++ b/sqlparser/test/parser/expression_test.dart @@ -72,6 +72,17 @@ final Map _testCases = { right: StringLiteral.from(token(TokenType.stringLiteral), '%A%\$'), escape: StringLiteral.from(token(TokenType.stringLiteral), '\$'), ), + 'NOT EXISTS (SELECT * FROM demo)': UnaryExpression( + token(TokenType.not), + ExistsExpression( + select: SelectStatement( + columns: [StarResultColumn(null)], + from: [ + TableReference('demo', null), + ], + ), + ), + ), }; void main() {