Handle function calls in type resolution

This commit is contained in:
Simon Binder 2019-06-29 17:02:38 +02:00
parent 74257e0c83
commit 7a07a1ae78
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
4 changed files with 145 additions and 15 deletions

View File

@ -12,15 +12,5 @@ class AnalysisContext {
errors.add(error);
}
ResolveResult typeOf(Typeable t) {
if (t is Column) {
return types.resolveColumn(t);
} else if (t is Variable) {
return types.inferType(t);
} else if (t is Expression) {
return types.resolveExpression(t);
}
throw StateError('Unknown typeable $t');
}
ResolveResult typeOf(Typeable t) => types.resolveOrInfer(t);
}

View File

@ -29,6 +29,28 @@ class TypeResolver {
return calculated;
}
ResolveResult resolveOrInfer(Typeable t) {
if (t is Column) {
return resolveColumn(t);
} else if (t is Variable) {
return inferType(t);
} else if (t is Expression) {
return resolveExpression(t);
}
throw StateError('Unknown typeable $t');
}
ResolveResult justResolve(Typeable t) {
if (t is Column) {
return resolveColumn(t);
} else if (t is Expression) {
return resolveExpression(t);
}
throw StateError('Unknown typeable $t');
}
ResolveResult resolveColumn(Column column) {
return _cache((column) {
if (column is TableColumn) {
@ -97,8 +119,109 @@ class TypeResolver {
}
ResolveResult resolveFunctionCall(FunctionExpression call) {
// todo
return const ResolveResult.unknown();
return _cache((FunctionExpression call) {
List<Typeable> parameters;
final sqlParameters = call.parameters;
if (sqlParameters is ExprFunctionParameters) {
parameters = sqlParameters.parameters;
} else if (sqlParameters is StarFunctionParameter) {
parameters = call.scope.availableColumns;
}
final firstNullable = justResolve(parameters.first).nullable;
final anyNullable = parameters.map(justResolve).any((r) => r.nullable);
switch (call.name.toLowerCase()) {
case 'round':
// if there is only one param, returns an int. otherwise real
if (parameters.length == 1) {
return ResolveResult(
ResolvedType(type: BasicType.int, nullable: firstNullable));
} else {
return ResolveResult(
ResolvedType(type: BasicType.real, nullable: anyNullable));
}
break;
case 'sum':
final firstType = justResolve(parameters.first);
if (firstType.type?.type == BasicType.int) {
return firstType;
} else {
return ResolveResult(ResolvedType(
type: BasicType.real, nullable: firstType.nullable));
}
break; // can't happen, though
case 'lower':
case 'ltrim':
case 'printf':
case 'replace':
case 'rtrim':
case 'substr':
case 'trim':
case 'upper':
case 'group_concat':
return ResolveResult(
ResolvedType(type: BasicType.text, nullable: firstNullable));
case 'date':
case 'time':
case 'datetime':
case 'julianday':
case 'strftime':
case 'char':
case 'hex':
case 'quote':
case 'soundex':
case 'sqlite_compileoption_set':
case 'sqlite_version':
case 'typeof':
return const ResolveResult(ResolvedType(type: BasicType.text));
case 'changes':
case 'last_insert_rowid':
case 'random':
case 'sqlite_compileoption_used':
case 'total_changes':
case 'count':
return const ResolveResult(ResolvedType(type: BasicType.int));
case 'instr':
case 'length':
case 'unicode':
return ResolveResult(
ResolvedType(type: BasicType.int, nullable: anyNullable));
case 'randomblob':
case 'zeroblob':
return const ResolveResult(ResolvedType(type: BasicType.blob));
case 'total':
case 'avg':
return const ResolveResult(ResolvedType(type: BasicType.real));
case 'abs':
case 'likelihood':
case 'likely':
case 'unlikely':
return justResolve(parameters.first);
case 'coalesce':
case 'ifnull':
return ResolveResult(_encapsulate(parameters,
[BasicType.int, BasicType.real, BasicType.text, BasicType.blob]));
case 'nullif':
return justResolve(parameters.first).withNullable(true);
case 'max':
return ResolveResult(_encapsulate(parameters, [
BasicType.int,
BasicType.real,
BasicType.text,
BasicType.blob
])).withNullable(true);
case 'min':
return ResolveResult(_encapsulate(parameters, [
BasicType.blob,
BasicType.text,
BasicType.int,
BasicType.real
])).withNullable(true);
}
throw StateError('Unknown function: ${call.name}');
}, call);
}
ResolveResult inferType(Expression e) {
@ -126,6 +249,8 @@ class TypeResolver {
return resolveExpression(relevant as Expression);
} else if (parent is Parentheses || parent is UnaryExpression) {
return const ResolveResult.needsContext();
} else if (parent is FunctionExpression) {
return resolveFunctionCall(parent);
}
throw StateError('Cannot infer argument type: $parent');
@ -134,9 +259,9 @@ class TypeResolver {
/// Returns the type of an expression in [expressions] that has the highest
/// order in [types].
ResolvedType _encapsulate(
Iterable<Expression> expressions, List<BasicType> types) {
Iterable<Typeable> expressions, List<BasicType> types) {
final argTypes = expressions
.map((expr) => resolveExpression(expr).type)
.map((expr) => justResolve(expr).type)
.where((t) => t != null);
final type = types.lastWhere((t) => argTypes.any((arg) => arg.type == t));
final notNull = argTypes.any((t) => !t.nullable);
@ -162,6 +287,18 @@ class ResolveResult {
needsContext = false,
unknown = true;
bool get nullable => type?.nullable ?? true;
ResolveResult withNullable(bool nullable) {
if (type != null) {
return ResolveResult(type.withNullable(nullable));
} else if (needsContext != null) {
return const ResolveResult.needsContext();
} else {
return const ResolveResult.unknown();
}
}
@override
bool operator ==(other) {
return identical(this, other) ||

View File

@ -36,6 +36,7 @@ class SelectStatement extends Statement with ResultSet {
if (where != null) where,
...columns,
if (from != null) ...from,
if (groupBy != null) groupBy,
if (limit != null) limit,
if (orderBy != null) orderBy,
];

View File

@ -10,6 +10,8 @@ Map<String, ResolveResult> _types = {
const ResolveResult(ResolvedType(type: BasicType.text)),
'SELECT * FROM demo LIMIT ?':
const ResolveResult(ResolvedType(type: BasicType.int)),
'SELECT 1 FROM demo GROUP BY id HAVING COUNT(*) = ?':
const ResolveResult(ResolvedType(type: BasicType.int)),
};
void main() {