From 44848906098cf931e6dfa6454daee28eb183070f Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 30 Dec 2019 20:14:29 +0100 Subject: [PATCH] Types2: Initial support for binary expressions --- .../analysis/types2/graph/relationships.dart | 27 ++++++++ .../analysis/types2/resolving_visitor.dart | 63 +++++++++++++++++++ .../test/analysis/types2/resolver_test.dart | 5 ++ 3 files changed, 95 insertions(+) diff --git a/sqlparser/lib/src/analysis/types2/graph/relationships.dart b/sqlparser/lib/src/analysis/types2/graph/relationships.dart index e1098d3b..fd34fa28 100644 --- a/sqlparser/lib/src/analysis/types2/graph/relationships.dart +++ b/sqlparser/lib/src/analysis/types2/graph/relationships.dart @@ -17,6 +17,33 @@ class CopyTypeFrom extends TypeRelationship { CopyTypeFrom(this.target, this.other); } +/// Dependency declaring that [target] has a type that matches all of [from]. +class CopyEncapsulating extends TypeRelationship { + final Typeable target; + final List from; + + CopyEncapsulating(this.target, this.from); +} + +/// Dependency declaring that [first] and [second] have the same type. This is +/// an optional dependency that will only be applied when one type is known and +/// the other is not. +class HaveSameType extends TypeRelationship { + final Typeable first; + final Typeable second; + + HaveSameType(this.first, this.second); +} + +/// Dependency declaring that, if no better option is found, [target] should +/// have the specified [defaultType]. +class DefaultType extends TypeRelationship { + final Typeable target; + final ResolvedType defaultType; + + DefaultType(this.target, this.defaultType); +} + enum CastMode { numeric, boolean } /// Dependency declaring that [target] has the same type as [other] after diff --git a/sqlparser/lib/src/analysis/types2/resolving_visitor.dart b/sqlparser/lib/src/analysis/types2/resolving_visitor.dart index 193dc9c3..1415920f 100644 --- a/sqlparser/lib/src/analysis/types2/resolving_visitor.dart +++ b/sqlparser/lib/src/analysis/types2/resolving_visitor.dart @@ -93,6 +93,69 @@ class TypeResolver extends RecursiveVisitor { } } + @override + void visitBinaryExpression(BinaryExpression e, TypeExpectation arg) { + switch (e.operator.type) { + case TokenType.and: + case TokenType.or: + session.checkAndResolve(e, const ResolvedType.bool(), arg); + session.addRelationship(NullableIfSomeOtherIs(e, [e.left, e.right])); + + // logic expressions, so children must be boolean + visitChildren(e, const ExactTypeExpectation.laxly(ResolvedType.bool())); + break; + case TokenType.equal: + case TokenType.exclamationEqual: + case TokenType.lessMore: + case TokenType.less: + case TokenType.lessEqual: + case TokenType.more: + case TokenType.moreEqual: + // comparison. Returns bool, copying nullability from children. + session.checkAndResolve(e, const ResolvedType.bool(), arg); + session.addRelationship(NullableIfSomeOtherIs(e, [e.left, e.right])); + // Not technically a requirement, but assume lhs and rhs have the same + // type. + session.addRelationship(HaveSameType(e.left, e.right)); + visitChildren(e, arg); + break; + case TokenType.plus: + case TokenType.minus: + session.addRelationship(CopyEncapsulating(e, [e.left, e.right])); + break; + // all of those only really make sense for integers + case TokenType.shiftLeft: + case TokenType.shiftRight: + case TokenType.pipe: + case TokenType.ampersand: + case TokenType.percent: + const type = ResolvedType(type: BasicType.int); + session.checkAndResolve(e, type, arg); + session.addRelationship(NullableIfSomeOtherIs(e, [e.left, e.right])); + visitChildren(e, const ExactTypeExpectation.laxly(type)); + break; + case TokenType.doublePipe: + // string concatenation. + const stringType = ResolvedType(type: BasicType.text); + session.checkAndResolve(e, stringType, arg); + session.addRelationship(NullableIfSomeOtherIs(e, [e.left, e.right])); + const childExpectation = ExactTypeExpectation.laxly(stringType); + visit(e.left, childExpectation); + visit(e.right, childExpectation); + break; + default: + throw StateError('Binary operator ${e.operator.type} not recognized ' + 'by types2. At $e'); + } + } + + @override + void visitIsExpression(IsExpression e, TypeExpectation arg) { + session.checkAndResolve(e, const ResolvedType.bool(), arg); + session.hintNullability(e, false); + visitChildren(e, const NoTypeExpectation()); + } + void _handleWhereClause(HasWhereClause stmt) { // assume that a where statement is a boolean expression. Sqlite internally // casts (https://www.sqlite.org/lang_expr.html#booleanexpr), so be lax diff --git a/sqlparser/test/analysis/types2/resolver_test.dart b/sqlparser/test/analysis/types2/resolver_test.dart index cd7fc179..6444f8fc 100644 --- a/sqlparser/test/analysis/types2/resolver_test.dart +++ b/sqlparser/test/analysis/types2/resolver_test.dart @@ -56,4 +56,9 @@ void main() { const ResolvedType.bool(), ); }); + + test('infers type in a string concatenation', () { + expect(_resolveFirstVariable("SELECT '' || :foo"), + const ResolvedType(type: BasicType.text)); + }); }