mirror of https://github.com/AMT-Cheif/drift.git
Run tests for types1 for types2 resolver
This commit is contained in:
parent
8ae68707f8
commit
4a2184110f
|
@ -7,7 +7,7 @@ targets:
|
|||
use_column_name_as_json_key_when_defined_in_moor_file: true
|
||||
generate_connect_constructor: true
|
||||
write_from_json_string_constructor: true
|
||||
# use_experimental_inference: true
|
||||
use_experimental_inference: true
|
||||
sqlite_modules:
|
||||
- json1
|
||||
- fts5
|
|
@ -2,7 +2,7 @@ part of '../types.dart';
|
|||
|
||||
/// Dependency declaring that [target] is nullable if any in [from] is
|
||||
/// nullable.
|
||||
class NullableIfSomeOtherIs extends TypeRelationship
|
||||
class NullableIfSomeOtherIs extends TypeRelation
|
||||
implements MultiSourceRelation {
|
||||
@override
|
||||
final Typeable target;
|
||||
|
@ -13,17 +13,21 @@ class NullableIfSomeOtherIs extends TypeRelationship
|
|||
}
|
||||
|
||||
/// Dependency declaring that [target] has exactly the same type as [other].
|
||||
class CopyTypeFrom extends TypeRelationship implements DirectedRelation {
|
||||
class CopyTypeFrom extends TypeRelation implements DirectedRelation {
|
||||
@override
|
||||
final Typeable target;
|
||||
final Typeable other;
|
||||
|
||||
CopyTypeFrom(this.target, this.other);
|
||||
/// When true, [target] will be the array-variant of [other]. When false,
|
||||
/// [target] will be the scalar variant of [other]. When null, nothing will be
|
||||
/// transformed.
|
||||
final bool array;
|
||||
|
||||
CopyTypeFrom(this.target, this.other, {this.array});
|
||||
}
|
||||
|
||||
/// Dependency declaring that [target] has a type that matches all of [from].
|
||||
class CopyEncapsulating extends TypeRelationship
|
||||
implements MultiSourceRelation {
|
||||
class CopyEncapsulating extends TypeRelation implements MultiSourceRelation {
|
||||
@override
|
||||
final Typeable target;
|
||||
@override
|
||||
|
@ -35,7 +39,7 @@ class CopyEncapsulating extends TypeRelationship
|
|||
/// 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 {
|
||||
class HaveSameType extends TypeRelation {
|
||||
final Typeable first;
|
||||
final Typeable second;
|
||||
|
||||
|
@ -49,7 +53,7 @@ class HaveSameType extends TypeRelationship {
|
|||
|
||||
/// Dependency declaring that, if no better option is found, [target] should
|
||||
/// have the specified [defaultType].
|
||||
class DefaultType extends TypeRelationship implements DirectedRelation {
|
||||
class DefaultType extends TypeRelation implements DirectedRelation {
|
||||
@override
|
||||
final Typeable target;
|
||||
final ResolvedType defaultType;
|
||||
|
@ -61,7 +65,7 @@ enum CastMode { numeric, boolean }
|
|||
|
||||
/// Dependency declaring that [target] has the same type as [other] after
|
||||
/// casting it with [cast].
|
||||
class CopyAndCast extends TypeRelationship implements DirectedRelation {
|
||||
class CopyAndCast extends TypeRelation implements DirectedRelation {
|
||||
@override
|
||||
final Typeable target;
|
||||
final Typeable other;
|
||||
|
|
|
@ -6,9 +6,9 @@ class TypeGraph {
|
|||
final Map<Typeable, ResolvedType> _knownTypes = {};
|
||||
final Map<Typeable, bool> _knownNullability = {};
|
||||
|
||||
final List<TypeRelationship> _relationships = [];
|
||||
final List<TypeRelation> _relations = [];
|
||||
|
||||
final Map<Typeable, List<TypeRelationship>> _edges = {};
|
||||
final Map<Typeable, List<TypeRelation>> _edges = {};
|
||||
final List<DefaultType> _defaultTypes = [];
|
||||
|
||||
TypeGraph();
|
||||
|
@ -35,8 +35,8 @@ class TypeGraph {
|
|||
|
||||
bool knowsType(Typeable t) => _knownTypes.containsKey(variables.normalize(t));
|
||||
|
||||
void addRelation(TypeRelationship relation) {
|
||||
_relationships.add(relation);
|
||||
void addRelation(TypeRelation relation) {
|
||||
_relations.add(relation);
|
||||
}
|
||||
|
||||
void performResolve() {
|
||||
|
@ -61,17 +61,19 @@ class TypeGraph {
|
|||
// propagate changes
|
||||
for (final edge in _edges[t]) {
|
||||
if (edge is CopyTypeFrom) {
|
||||
_copyType(resolved, edge.other, edge.target);
|
||||
var type = this[edge.other];
|
||||
if (edge.array != null) {
|
||||
type = type.toArray(edge.array);
|
||||
}
|
||||
_copyType(resolved, edge.other, edge.target, type);
|
||||
} else if (edge is HaveSameType) {
|
||||
_copyType(resolved, t, edge.getOther(t));
|
||||
} else if (edge is CopyAndCast) {
|
||||
_copyType(resolved, t, edge.target, this[t].cast(edge.cast));
|
||||
} else if (edge is MultiSourceRelation) {
|
||||
// handle many-to-one changes, if all targets have been resolved
|
||||
final typedEdge = edge as MultiSourceRelation;
|
||||
|
||||
if (typedEdge.from.every(knowsType)) {
|
||||
_propagateManyToOne(typedEdge, resolved, t);
|
||||
if (edge.from.every(knowsType)) {
|
||||
_propagateManyToOne(edge, resolved, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,17 +110,17 @@ class TypeGraph {
|
|||
void _indexRelationships() {
|
||||
_edges.clear();
|
||||
|
||||
void put(Typeable t, TypeRelationship r) {
|
||||
void put(Typeable t, TypeRelation r) {
|
||||
_edges.putIfAbsent(t, () => []).add(r);
|
||||
}
|
||||
|
||||
void putAll(Iterable<Typeable> t, TypeRelationship r) {
|
||||
void putAll(Iterable<Typeable> t, TypeRelation r) {
|
||||
for (final element in t) {
|
||||
put(element, r);
|
||||
}
|
||||
}
|
||||
|
||||
for (final relation in _relationships) {
|
||||
for (final relation in _relations) {
|
||||
if (relation is NullableIfSomeOtherIs) {
|
||||
putAll(relation.from, relation);
|
||||
} else if (relation is CopyTypeFrom) {
|
||||
|
@ -139,12 +141,20 @@ class TypeGraph {
|
|||
}
|
||||
}
|
||||
|
||||
abstract class TypeRelationship {}
|
||||
/// Describes how the type of different [Typeable] instances has an effect on
|
||||
/// others.
|
||||
///
|
||||
/// Note that all logic is handled in the type graph, these are logic-less model
|
||||
/// classes only.
|
||||
abstract class TypeRelation {}
|
||||
|
||||
abstract class DirectedRelation {
|
||||
/// Relation that only has an effect on one [Typeable] -- namely, [target].
|
||||
abstract class DirectedRelation implements TypeRelation {
|
||||
/// The only [Typeable] effected by this relation.
|
||||
Typeable get target;
|
||||
}
|
||||
|
||||
/// Relation where the type of multiple [Typeable] instances must be known.
|
||||
abstract class MultiSourceRelation implements DirectedRelation {
|
||||
List<Typeable> get from;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
// node, which means this visitor doesn't find them
|
||||
root.acceptWithoutArg(_ResultColumnVisitor(this));
|
||||
|
||||
session.finish();
|
||||
session._finish();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -72,6 +72,9 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
visit(select.stmt, SelectTypeExpectation(expectations));
|
||||
},
|
||||
isValues: (values) {
|
||||
// todo: It would be nice to remove this special case. Can we generalize
|
||||
// the SelectTypeExpectation so that it works for tuples and just visit
|
||||
// e.source?
|
||||
for (final tuple in values.values) {
|
||||
for (var i = 0; i < tuple.expressions.length; i++) {
|
||||
final expectation = i < expectations.length
|
||||
|
@ -114,31 +117,49 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSetComponent(SetComponent e, TypeExpectation arg) {
|
||||
visit(e.column, const NoTypeExpectation());
|
||||
_lazyCopy(e.expression, e.column);
|
||||
visit(e.expression, const NoTypeExpectation());
|
||||
}
|
||||
|
||||
@override
|
||||
void visitLimit(Limit e, TypeExpectation arg) {
|
||||
visit(e.count, _expectInt);
|
||||
visitNullable(e.offset, _expectInt);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFrameSpec(FrameSpec e, TypeExpectation arg) {
|
||||
// handle something like "RANGE BETWEEN ? PRECEDING AND ? FOLLOWING
|
||||
if (e.start.isExpressionOffset) {
|
||||
visit(e.start.offset, _expectInt);
|
||||
}
|
||||
if (e.end.isExpressionOffset) {
|
||||
visit(e.end.offset, _expectInt);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitLiteral(Literal e, TypeExpectation arg) {
|
||||
ResolvedType type;
|
||||
|
||||
if (e is NullLiteral) {
|
||||
type = const ResolvedType(type: BasicType.nullType, nullable: true);
|
||||
session.hintNullability(e, true);
|
||||
session._hintNullability(e, true);
|
||||
} else if (e is StringLiteral) {
|
||||
type = e.isBinary ? const ResolvedType(type: BasicType.blob) : _textType;
|
||||
session.hintNullability(e, false);
|
||||
session._hintNullability(e, false);
|
||||
} else if (e is BooleanLiteral) {
|
||||
type = const ResolvedType.bool();
|
||||
session.hintNullability(e, false);
|
||||
session._hintNullability(e, false);
|
||||
} else if (e is NumericLiteral) {
|
||||
type = e.isInt ? _intType : _realType;
|
||||
session.hintNullability(e, false);
|
||||
session._hintNullability(e, false);
|
||||
}
|
||||
|
||||
session.checkAndResolve(e, type, arg);
|
||||
session._checkAndResolve(e, type, arg);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -163,9 +184,9 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
resolved ??= _inferFromContext(arg);
|
||||
|
||||
if (resolved != null) {
|
||||
session.checkAndResolve(e, resolved, arg);
|
||||
session._checkAndResolve(e, resolved, arg);
|
||||
} else if (arg is RoughTypeExpectation) {
|
||||
session.addRelationship(DefaultType(e, arg.defaultType()));
|
||||
session._addRelation(DefaultType(e, arg.defaultType()));
|
||||
}
|
||||
|
||||
visitChildren(e, arg);
|
||||
|
@ -177,22 +198,22 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
|
||||
if (operatorType == TokenType.plus) {
|
||||
// plus is a no-op, so copy type from child
|
||||
session.addRelationship(CopyTypeFrom(e, e.inner));
|
||||
session._addRelation(CopyTypeFrom(e, e.inner));
|
||||
visit(e.inner, arg);
|
||||
} else if (operatorType == TokenType.not) {
|
||||
// unary not expression - boolean, but nullability depends on child node.
|
||||
session.checkAndResolve(e, const ResolvedType.bool(nullable: null), arg);
|
||||
session.addRelationship(NullableIfSomeOtherIs(e, [e.inner]));
|
||||
session._checkAndResolve(e, const ResolvedType.bool(nullable: null), arg);
|
||||
session._addRelation(NullableIfSomeOtherIs(e, [e.inner]));
|
||||
visit(e.inner, const ExactTypeExpectation.laxly(ResolvedType.bool()));
|
||||
} else if (operatorType == TokenType.minus) {
|
||||
// unary minus - can be int or real depending on child node
|
||||
session.addRelationship(CopyAndCast(e, e.inner, CastMode.numeric));
|
||||
session._addRelation(CopyAndCast(e, e.inner, CastMode.numeric));
|
||||
visit(e.inner, const RoughTypeExpectation.numeric());
|
||||
} else if (operatorType == TokenType.tilde) {
|
||||
// bitwise negation - definitely int, but nullability depends on child
|
||||
session.checkAndResolve(
|
||||
session._checkAndResolve(
|
||||
e, const ResolvedType(type: BasicType.int, nullable: null), arg);
|
||||
session.addRelationship(NullableIfSomeOtherIs(e, [e.inner]));
|
||||
session._addRelation(NullableIfSomeOtherIs(e, [e.inner]));
|
||||
|
||||
visit(e.inner, const NoTypeExpectation());
|
||||
} else {
|
||||
|
@ -201,14 +222,22 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitTuple(Tuple e, TypeExpectation arg) {
|
||||
// make children non-arrays
|
||||
for (final child in e.childNodes) {
|
||||
session._addRelation(CopyTypeFrom(child, e, array: false));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitBetweenExpression(BetweenExpression e, TypeExpectation arg) {
|
||||
visitChildren(e, _expectNum);
|
||||
|
||||
session
|
||||
..addRelationship(NullableIfSomeOtherIs(e, e.childNodes))
|
||||
..addRelationship(HaveSameType(e.lower, e.upper))
|
||||
..addRelationship(HaveSameType(e.check, e.lower));
|
||||
.._addRelation(NullableIfSomeOtherIs(e, e.childNodes))
|
||||
.._addRelation(HaveSameType(e.lower, e.upper))
|
||||
.._addRelation(HaveSameType(e.check, e.lower));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -216,8 +245,8 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
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]));
|
||||
session._checkAndResolve(e, const ResolvedType.bool(), arg);
|
||||
session._addRelation(NullableIfSomeOtherIs(e, [e.left, e.right]));
|
||||
|
||||
// logic expressions, so children must be boolean
|
||||
visitChildren(e, const ExactTypeExpectation.laxly(ResolvedType.bool()));
|
||||
|
@ -230,16 +259,16 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
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]));
|
||||
session._checkAndResolve(e, const ResolvedType.bool(), arg);
|
||||
session._addRelation(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));
|
||||
session._addRelation(HaveSameType(e.left, e.right));
|
||||
visitChildren(e, const NoTypeExpectation());
|
||||
break;
|
||||
case TokenType.plus:
|
||||
case TokenType.minus:
|
||||
session.addRelationship(CopyEncapsulating(e, [e.left, e.right]));
|
||||
session._addRelation(CopyEncapsulating(e, [e.left, e.right]));
|
||||
break;
|
||||
// all of those only really make sense for integers
|
||||
case TokenType.shiftLeft:
|
||||
|
@ -248,15 +277,15 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
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]));
|
||||
session._checkAndResolve(e, type, arg);
|
||||
session._addRelation(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]));
|
||||
session._checkAndResolve(e, stringType, arg);
|
||||
session._addRelation(NullableIfSomeOtherIs(e, [e.left, e.right]));
|
||||
const childExpectation = ExactTypeExpectation.laxly(stringType);
|
||||
visit(e.left, childExpectation);
|
||||
visit(e.right, childExpectation);
|
||||
|
@ -269,27 +298,37 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
|
||||
@override
|
||||
void visitIsExpression(IsExpression e, TypeExpectation arg) {
|
||||
session.checkAndResolve(e, const ResolvedType.bool(), arg);
|
||||
session.hintNullability(e, false);
|
||||
session._checkAndResolve(e, const ResolvedType.bool(), arg);
|
||||
session._hintNullability(e, false);
|
||||
visitChildren(e, const NoTypeExpectation());
|
||||
}
|
||||
|
||||
@override
|
||||
void visitIsNullExpression(IsNullExpression e, TypeExpectation arg) {
|
||||
session.checkAndResolve(e, const ResolvedType.bool(), arg);
|
||||
session.hintNullability(e, false);
|
||||
session._checkAndResolve(e, const ResolvedType.bool(), arg);
|
||||
session._hintNullability(e, false);
|
||||
visitChildren(e, const NoTypeExpectation());
|
||||
}
|
||||
|
||||
@override
|
||||
void visitInExpression(InExpression e, TypeExpectation arg) {
|
||||
session._checkAndResolve(e, const ResolvedType.bool(), arg);
|
||||
session._addRelation(NullableIfSomeOtherIs(e, e.childNodes));
|
||||
|
||||
session._addRelation(CopyTypeFrom(e.inside, e.left, array: true));
|
||||
|
||||
visitChildren(e, const NoTypeExpectation());
|
||||
}
|
||||
|
||||
@override
|
||||
void visitCaseExpression(CaseExpression e, TypeExpectation arg) {
|
||||
session.addRelationship(CopyEncapsulating(e, [
|
||||
session._addRelation(CopyEncapsulating(e, [
|
||||
for (final when in e.whens) when.then,
|
||||
if (e.elseExpr != null) e.elseExpr,
|
||||
]));
|
||||
|
||||
if (e.base != null) {
|
||||
session.addRelationship(
|
||||
session._addRelation(
|
||||
CopyEncapsulating(e.base, [for (final when in e.whens) when.when]),
|
||||
);
|
||||
}
|
||||
|
@ -303,7 +342,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
final parent = e.parent;
|
||||
if (parent is CaseExpression && parent.base != null) {
|
||||
// case expressions with base -> condition is compared to base
|
||||
session.addRelationship(CopyTypeFrom(e.when, parent.base));
|
||||
session._addRelation(CopyTypeFrom(e.when, parent.base));
|
||||
visit(e.when, const NoTypeExpectation());
|
||||
} else {
|
||||
// case expression without base -> the conditions are booleans
|
||||
|
@ -316,16 +355,16 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
@override
|
||||
void visitCastExpression(CastExpression e, TypeExpectation arg) {
|
||||
final type = session.context.schemaSupport.resolveColumnType(e.typeName);
|
||||
session.checkAndResolve(e, type, arg);
|
||||
session.addRelationship(NullableIfSomeOtherIs(e, [e.operand]));
|
||||
session._checkAndResolve(e, type, arg);
|
||||
session._addRelation(NullableIfSomeOtherIs(e, [e.operand]));
|
||||
visit(e.operand, const NoTypeExpectation());
|
||||
}
|
||||
|
||||
@override
|
||||
void visitStringComparison(
|
||||
StringComparisonExpression e, TypeExpectation arg) {
|
||||
session.checkAndResolve(e, const ResolvedType(type: BasicType.text), arg);
|
||||
session.addRelationship(NullableIfSomeOtherIs(
|
||||
session._checkAndResolve(e, const ResolvedType(type: BasicType.text), arg);
|
||||
session._addRelation(NullableIfSomeOtherIs(
|
||||
e,
|
||||
[
|
||||
e.left,
|
||||
|
@ -352,15 +391,21 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
void visitInvocation(SqlInvocation e, TypeExpectation arg) {
|
||||
final type = _resolveInvocation(e);
|
||||
if (type != null) {
|
||||
session.checkAndResolve(e, type, arg);
|
||||
session._checkAndResolve(e, type, arg);
|
||||
}
|
||||
|
||||
final visited = _resolveFunctionArguments(e);
|
||||
for (final child in e.childNodes) {
|
||||
if (!visited.contains(child)) {
|
||||
visit(child, const NoTypeExpectation());
|
||||
}
|
||||
}
|
||||
visitChildren(e, const NoTypeExpectation());
|
||||
}
|
||||
|
||||
ResolvedType _resolveInvocation(SqlInvocation e) {
|
||||
final params = e.expandParameters();
|
||||
void nullableIfChildIs() {
|
||||
session.addRelationship(NullableIfSomeOtherIs(e, params));
|
||||
session._addRelation(NullableIfSomeOtherIs(e, params));
|
||||
}
|
||||
|
||||
final lowercaseName = e.name.toLowerCase();
|
||||
|
@ -376,8 +421,8 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
// ignore: dead_code
|
||||
throw AssertionError(); // required so that this switch compiles
|
||||
case 'sum':
|
||||
session.addRelationship(CopyAndCast(e, params.first, CastMode.numeric));
|
||||
session.addRelationship(DefaultType(e, _realType));
|
||||
session._addRelation(CopyAndCast(e, params.first, CastMode.numeric));
|
||||
session._addRelation(DefaultType(e, _realType));
|
||||
nullableIfChildIs();
|
||||
return null;
|
||||
case 'lower':
|
||||
|
@ -432,27 +477,27 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
case 'likelihood':
|
||||
case 'likely':
|
||||
case 'unlikely':
|
||||
session.addRelationship(CopyTypeFrom(e, params.first));
|
||||
session._addRelation(CopyTypeFrom(e, params.first));
|
||||
return null;
|
||||
case 'coalesce':
|
||||
case 'ifnull':
|
||||
session.addRelationship(CopyEncapsulating(e, params));
|
||||
session._addRelation(CopyEncapsulating(e, params));
|
||||
return null;
|
||||
case 'nullif':
|
||||
session.hintNullability(e, true);
|
||||
session.addRelationship(CopyTypeFrom(e, params.first));
|
||||
session._hintNullability(e, true);
|
||||
session._addRelation(CopyTypeFrom(e, params.first));
|
||||
return null;
|
||||
case 'first_value':
|
||||
case 'last_value':
|
||||
case 'lag':
|
||||
case 'lead':
|
||||
case 'nth_value':
|
||||
session.addRelationship(CopyTypeFrom(e, params.first));
|
||||
session._addRelation(CopyTypeFrom(e, params.first));
|
||||
return null;
|
||||
case 'max':
|
||||
case 'min':
|
||||
session.hintNullability(e, true);
|
||||
session.addRelationship(CopyEncapsulating(e, params));
|
||||
session._hintNullability(e, true);
|
||||
session._addRelation(CopyEncapsulating(e, params));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -495,11 +540,26 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
return null;
|
||||
}
|
||||
|
||||
Set<AstNode> _resolveFunctionArguments(SqlInvocation e) {
|
||||
final params = e.expandParameters();
|
||||
final visited = <AstNode>{};
|
||||
final name = e.name.toLowerCase();
|
||||
|
||||
if (name == 'nth_value' && params.length >= 2 && params[1] is Expression) {
|
||||
// the second argument of nth_value is always an integer
|
||||
final secondParam = params[1] as Expression;
|
||||
visit(secondParam, _expectInt);
|
||||
visited.add(secondParam);
|
||||
}
|
||||
|
||||
return visited;
|
||||
}
|
||||
|
||||
void _handleColumn(Column column) {
|
||||
if (session.graph.knowsType(column)) return;
|
||||
|
||||
if (column is TableColumn) {
|
||||
session.markTypeResolved(column, column.type);
|
||||
session._markTypeResolved(column, column.type);
|
||||
} else if (column is ExpressionColumn) {
|
||||
_lazyCopy(column, column.expression);
|
||||
} else if (column is DelegatedColumn && column.innerColumn != null) {
|
||||
|
@ -510,9 +570,9 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
|
||||
void _lazyCopy(Typeable to, Typeable from) {
|
||||
if (session.graph.knowsType(from)) {
|
||||
session.markTypeResolved(to, session.typeOf(from));
|
||||
session._markTypeResolved(to, session.typeOf(from));
|
||||
} else {
|
||||
session.addRelationship(CopyTypeFrom(to, from));
|
||||
session._addRelation(CopyTypeFrom(to, from));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,31 +19,41 @@ class TypeInferenceSession {
|
|||
results = TypeInferenceResults._(this);
|
||||
}
|
||||
|
||||
void markTypeResolved(Typeable t, ResolvedType r) {
|
||||
void _markTypeResolved(Typeable t, ResolvedType r) {
|
||||
graph[t] = r;
|
||||
}
|
||||
|
||||
void checkAndResolve(
|
||||
void _checkAndResolve(
|
||||
Typeable t, ResolvedType r, TypeExpectation expectation) {
|
||||
expectIsPossible(r, expectation);
|
||||
markTypeResolved(t, r);
|
||||
_expectIsPossible(r, expectation);
|
||||
_markTypeResolved(t, r);
|
||||
}
|
||||
|
||||
/// Returns the inferred type of [t], or `null` if it couldn't be inferred.
|
||||
ResolvedType typeOf(Typeable t) {
|
||||
return graph[t];
|
||||
}
|
||||
|
||||
void addRelationship(TypeRelationship relationship) {
|
||||
void _addRelation(TypeRelation relationship) {
|
||||
graph.addRelation(relationship);
|
||||
}
|
||||
|
||||
void expectIsPossible(ResolvedType r, TypeExpectation expectation) {}
|
||||
/// Check that [r] is compatible with [expectation].
|
||||
///
|
||||
/// This is not currently implemented.
|
||||
void _expectIsPossible(ResolvedType r, TypeExpectation expectation) {}
|
||||
|
||||
void hintNullability(Typeable t, bool nullable) {
|
||||
/// This is not currently implemented.
|
||||
void _hintNullability(Typeable t, bool nullable) {
|
||||
assert(nullable != null);
|
||||
}
|
||||
|
||||
void finish() {
|
||||
/// Asks the underlying [TypeGraph] to propagate known types via known
|
||||
/// [TypeRelation]s.
|
||||
///
|
||||
/// The [SqlEngine] will call this method when analyzing a statement. There's
|
||||
/// no need to call it from user code.
|
||||
void _finish() {
|
||||
graph.performResolve();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class Tuple extends Expression {
|
|||
}
|
||||
|
||||
@override
|
||||
Iterable<AstNode> get childNodes => expressions;
|
||||
List<Expression> get childNodes => expressions;
|
||||
|
||||
@override
|
||||
bool contentEquals(Tuple other) => true;
|
||||
|
|
|
@ -169,7 +169,7 @@ class InExpression extends Expression {
|
|||
}
|
||||
|
||||
@override
|
||||
Iterable<AstNode> get childNodes => [left, inside];
|
||||
List<Expression> get childNodes => [left, inside];
|
||||
|
||||
@override
|
||||
bool contentEquals(InExpression other) => other.not == not;
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import 'package:sqlparser/sqlparser.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../data.dart';
|
||||
|
||||
// Cases copied from the regular type inference algorithm test
|
||||
const Map<String, ResolvedType> _types = {
|
||||
'SELECT * FROM demo WHERE id = ?': ResolvedType(type: BasicType.int),
|
||||
'SELECT * FROM demo WHERE content = ?': ResolvedType(type: BasicType.text),
|
||||
'SELECT * FROM demo LIMIT ?': ResolvedType(type: BasicType.int),
|
||||
'SELECT 1 FROM demo GROUP BY id HAVING COUNT(*) = ?':
|
||||
ResolvedType(type: BasicType.int),
|
||||
'SELECT 1 FROM demo WHERE id BETWEEN 3 AND ?':
|
||||
ResolvedType(type: BasicType.int),
|
||||
'UPDATE demo SET content = ? WHERE id = 3':
|
||||
ResolvedType(type: BasicType.text),
|
||||
'SELECT * FROM demo WHERE content LIKE ?': ResolvedType(type: BasicType.text),
|
||||
"SELECT * FROM demo WHERE content LIKE '%e' ESCAPE ?":
|
||||
ResolvedType(type: BasicType.text),
|
||||
'SELECT * FROM demo WHERE content IN ?':
|
||||
ResolvedType(type: BasicType.text, isArray: true),
|
||||
'SELECT * FROM demo WHERE content IN (?)':
|
||||
ResolvedType(type: BasicType.text, isArray: false),
|
||||
'SELECT * FROM demo JOIN tbl ON demo.id = tbl.id WHERE date = ?':
|
||||
ResolvedType(type: BasicType.int, hint: IsDateTime()),
|
||||
'SELECT row_number() OVER (RANGE ? PRECEDING)':
|
||||
ResolvedType(type: BasicType.int),
|
||||
'SELECT ?;': null,
|
||||
'SELECT CAST(3 AS TEXT) = ?': ResolvedType(type: BasicType.text),
|
||||
};
|
||||
|
||||
SqlEngine _spawnEngine() {
|
||||
return SqlEngine.withOptions(
|
||||
EngineOptions(enableExperimentalTypeInference: true))
|
||||
..registerTable(demoTable)
|
||||
..registerTable(anotherTable);
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('miscellaneous type inference cases', () {
|
||||
_types.forEach((sql, expected) {
|
||||
test('for $sql', () {
|
||||
final engine = _spawnEngine();
|
||||
final content = engine.analyze(sql);
|
||||
|
||||
final variable = content.root.allDescendants
|
||||
.firstWhere((node) => node is Variable) as Typeable;
|
||||
|
||||
expect(content.typeOf(variable).type, equals(expected));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -142,6 +142,23 @@ void main() {
|
|||
expect(escapedType, const ResolvedType(type: BasicType.text));
|
||||
});
|
||||
|
||||
test('handles nth_value', () {
|
||||
final resolver = _obtainResolver("SELECT nth_value('string', ?1) = ?2");
|
||||
final variables = resolver.session.context.root.allDescendants
|
||||
.whereType<Variable>()
|
||||
.iterator;
|
||||
variables.moveNext();
|
||||
final firstVar = variables.current;
|
||||
variables.moveNext();
|
||||
final secondVar = variables.current;
|
||||
|
||||
expect(resolver.session.typeOf(firstVar),
|
||||
equals(const ResolvedType(type: BasicType.int)));
|
||||
|
||||
expect(resolver.session.typeOf(secondVar),
|
||||
equals(const ResolvedType(type: BasicType.text)));
|
||||
});
|
||||
|
||||
group('case expressions', () {
|
||||
test('infers base clause from when', () {
|
||||
final type = _resolveFirstVariable("SELECT CASE ? WHEN 1 THEN 'two' END");
|
||||
|
@ -163,6 +180,7 @@ void main() {
|
|||
"WHEN true THEN 'two' ELSE 'three' END;");
|
||||
expect(type, const ResolvedType(type: BasicType.text));
|
||||
});
|
||||
});
|
||||
|
||||
test('can select columns', () {
|
||||
final type = _resolveResultColumn('SELECT id FROM demo;');
|
||||
|
@ -193,5 +211,27 @@ WITH RECURSIVE
|
|||
|
||||
expect(type, const ResolvedType(type: BasicType.int));
|
||||
});
|
||||
|
||||
test('handles set components in updates', () {
|
||||
final type = _resolveFirstVariable('UPDATE demo SET id = ?');
|
||||
expect(type, const ResolvedType(type: BasicType.int));
|
||||
});
|
||||
|
||||
test('infers offsets in frame specs', () {
|
||||
final type =
|
||||
_resolveFirstVariable('SELECT SUM(id) OVER (ROWS ? PRECEDING)');
|
||||
expect(type, const ResolvedType(type: BasicType.int));
|
||||
});
|
||||
|
||||
group('IS IN expressions', () {
|
||||
test('infer the variable as an array type', () {
|
||||
final type = _resolveFirstVariable('SELECT 3 IN ?');
|
||||
expect(type, const ResolvedType(type: BasicType.int, isArray: true));
|
||||
});
|
||||
|
||||
test('does not infer the variable as an array when in a tuple', () {
|
||||
final type = _resolveFirstVariable('SELECT 3 IN (?)');
|
||||
expect(type, const ResolvedType(type: BasicType.int, isArray: false));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue