diff --git a/sqlparser/lib/src/analysis/types2/resolving_visitor.dart b/sqlparser/lib/src/analysis/types2/resolving_visitor.dart index 7a5ee974..a07158a4 100644 --- a/sqlparser/lib/src/analysis/types2/resolving_visitor.dart +++ b/sqlparser/lib/src/analysis/types2/resolving_visitor.dart @@ -2,6 +2,8 @@ part of 'types.dart'; const _expectInt = ExactTypeExpectation.laxly(ResolvedType(type: BasicType.int)); +const _expectString = + ExactTypeExpectation.laxly(ResolvedType(type: BasicType.text)); class TypeResolver extends RecursiveVisitor { final TypeInferenceSession session; @@ -116,16 +118,20 @@ class TypeResolver extends RecursiveVisitor { if (e is NullLiteral) { type = const ResolvedType(type: BasicType.nullType, nullable: true); + session.hintNullability(e, true); } else if (e is StringLiteral) { type = e.isBinary ? const ResolvedType(type: BasicType.blob) : const ResolvedType(type: BasicType.text); + session.hintNullability(e, false); } else if (e is BooleanLiteral) { type = const ResolvedType.bool(); + session.hintNullability(e, false); } else if (e is NumericLiteral) { type = e.isInt ? const ResolvedType(type: BasicType.int) : const ResolvedType(type: BasicType.real); + session.hintNullability(e, false); } session.checkAndResolve(e, type, arg); @@ -243,6 +249,24 @@ class TypeResolver extends RecursiveVisitor { visit(e.operand, const NoTypeExpectation()); } + @override + void visitStringComparison( + StringComparisonExpression e, TypeExpectation arg) { + session.checkAndResolve(e, const ResolvedType(type: BasicType.text), arg); + session.addRelationship(NullableIfSomeOtherIs( + e, + [ + e.left, + e.right, + if (e.escape != null) e.escape, + ], + )); + + visit(e.left, _expectString); + visit(e.right, _expectString); + visitNullable(e.escape, _expectString); + } + @override void visitReference(Reference e, TypeExpectation arg) { final resolved = e.resolvedColumn; diff --git a/sqlparser/lib/src/ast/expressions/simple.dart b/sqlparser/lib/src/ast/expressions/simple.dart index adc5aa68..1ee45551 100644 --- a/sqlparser/lib/src/ast/expressions/simple.dart +++ b/sqlparser/lib/src/ast/expressions/simple.dart @@ -57,6 +57,7 @@ class BinaryExpression extends Expression { } } +/// A like, glob, match or regexp expression. class StringComparisonExpression extends Expression { final bool not; final Token operator; diff --git a/sqlparser/test/analysis/types2/resolver_test.dart b/sqlparser/test/analysis/types2/resolver_test.dart index 8944df1b..ea9a8872 100644 --- a/sqlparser/test/analysis/types2/resolver_test.dart +++ b/sqlparser/test/analysis/types2/resolver_test.dart @@ -127,4 +127,14 @@ void main() { final offsetType = _resolveFirstVariable('SELECT 0 LIMIT 1, ?'); expect(offsetType, int); }); + + test('handles string matching expressions', () { + final type = + _resolveFirstVariable('SELECT * FROM demo WHERE content LIKE ?'); + expect(type, const ResolvedType(type: BasicType.text)); + + final escapedType = _resolveFirstVariable( + "SELECT * FROM demo WHERE content LIKE 'foo' ESCAPE ?"); + expect(escapedType, const ResolvedType(type: BasicType.text)); + }); }