Support parsing table valued functions

This commit is contained in:
Simon Binder 2020-01-26 13:13:44 +01:00
parent ceb300e32e
commit 223f1615ab
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
11 changed files with 101 additions and 15 deletions

View File

@ -112,7 +112,7 @@ class TypeResolver {
} else {
return const ResolveResult.unknown();
}
} else if (expr is SqlInvocation) {
} else if (expr is ExpressionInvocation) {
return resolveFunctionCall(expr);
} else if (expr is IsExpression ||
expr is IsNullExpression ||
@ -167,8 +167,8 @@ class TypeResolver {
}, l);
}
ResolveResult resolveFunctionCall(SqlInvocation call) {
return _cache((SqlInvocation call) {
ResolveResult resolveFunctionCall(ExpressionInvocation call) {
return _cache((ExpressionInvocation call) {
final parameters = call.expandParameters();
final firstNullable =
// ignore: avoid_bool_literals_in_conditional_expressions
@ -413,7 +413,7 @@ class TypeResolver {
parent is Tuple ||
parent is UnaryExpression) {
return const ResolveResult.needsContext();
} else if (parent is SqlInvocation) {
} else if (parent is ExpressionInvocation) {
// if we have a special case for the mix of function and argument, use
// that. Otherwise, just assume that the argument has the same type as the
// return type of the function

View File

@ -397,7 +397,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
}
@override
void visitInvocation(SqlInvocation e, TypeExpectation arg) {
void visitExpressionInvocation(ExpressionInvocation e, TypeExpectation arg) {
final type = _resolveInvocation(e);
if (type != null) {
session._checkAndResolve(e, type, arg);
@ -411,7 +411,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
}
}
ResolvedType _resolveInvocation(SqlInvocation e) {
ResolvedType _resolveInvocation(ExpressionInvocation e) {
final params = e.expandParameters();
void nullableIfChildIs() {
session._addRelation(NullableIfSomeOtherIs(e, params));

View File

@ -8,10 +8,12 @@ abstract class Queryable extends AstNode {
return visitor.visitQueryable(this, arg);
}
// todo remove this, introduce more visit methods for subclasses
T when<T>({
@required T Function(TableReference) isTable,
@required T Function(SelectStatementAsSource) isSelect,
@required T Function(JoinClause) isJoin,
@required T Function(TableValuedFunction) isTableFunction,
}) {
if (this is TableReference) {
return isTable(this as TableReference);
@ -19,6 +21,8 @@ abstract class Queryable extends AstNode {
return isSelect(this as SelectStatementAsSource);
} else if (this is JoinClause) {
return isJoin(this as JoinClause);
} else if (this is TableValuedFunction) {
return isTableFunction(this as TableValuedFunction);
}
throw StateError('Unknown subclass');
@ -165,3 +169,25 @@ class UsingConstraint extends JoinConstraint {
UsingConstraint({@required this.columnNames});
}
class TableValuedFunction extends Queryable
implements TableOrSubquery, SqlInvocation, Renamable {
@override
final String name;
@override
final String as;
@override
final FunctionParameters parameters;
TableValuedFunction(this.name, this.parameters, {this.as});
@override
Iterable<AstNode> get childNodes => [parameters];
@override
bool contentEquals(TableValuedFunction other) {
return other.name == name;
}
}

View File

@ -1,7 +1,7 @@
part of '../ast.dart';
class AggregateExpression extends Expression
implements SqlInvocation, ReferenceOwner {
implements ExpressionInvocation, ReferenceOwner {
final IdentifierToken function;
@override

View File

@ -2,16 +2,19 @@ part of '../ast.dart';
/// Interface for function calls, either a [FunctionExpression] or a
/// [AggregateExpression].
abstract class SqlInvocation extends Expression {
abstract class SqlInvocation implements AstNode {
/// The name of the function being called
String get name;
FunctionParameters get parameters;
}
/// Interface for [SqlInvocation]s that are also expressions.
abstract class ExpressionInvocation implements SqlInvocation, Expression {}
class FunctionExpression extends Expression
with ReferenceOwner
implements SqlInvocation {
implements ExpressionInvocation {
@override
final String name;
@override
@ -75,6 +78,6 @@ class ExprFunctionParameters extends FunctionParameters {
@override
bool contentEquals(ExprFunctionParameters other) {
return other.distinct == distinct && other.parameters == parameters;
return other.distinct == distinct;
}
}

View File

@ -332,7 +332,7 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
@override
R visitFunction(FunctionExpression e, A arg) {
return visitInvocation(e, arg);
return visitExpressionInvocation(e, arg);
}
R visitFunctionParameters(FunctionParameters e, A arg) {
@ -351,7 +351,7 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
@override
R visitAggregateExpression(AggregateExpression e, A arg) {
return visitInvocation(e, arg);
return visitExpressionInvocation(e, arg);
}
@override
@ -403,8 +403,12 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
return visitExpression(e, arg);
}
R visitExpressionInvocation(ExpressionInvocation e, A arg) {
return visitInvocation(e, arg);
}
R visitInvocation(SqlInvocation e, A arg) {
return visitExpression(e, arg);
return visitChildren(e, arg);
}
R visitExpression(Expression e, A arg) {

View File

@ -258,9 +258,22 @@ mixin CrudParser on ParserBase {
TableOrSubquery _tableOrSubquery() {
// this is what we're parsing: https://www.sqlite.org/syntax/table-or-subquery.html
// we currently only support regular tables and nested selects
// we currently only support regular tables, table functions and nested
// selects
final tableRef = _tableReference();
if (tableRef != null) {
// this is a bit hacky. If the table reference only consists of one
// identifer and it's followed by a (, it's a table-valued function
if (tableRef.as == null && _matchOne(TokenType.leftParen)) {
final params = _functionParameters();
_consume(TokenType.rightParen, 'Expected closing parenthesis');
final alias = _as();
return TableValuedFunction(tableRef.tableName, params,
as: alias?.identifier)
..setSpan(tableRef.first, _previous);
}
return tableRef;
} else if (_matchOne(TokenType.leftParen)) {
final first = _previous;

View File

@ -354,6 +354,7 @@ mixin ExpressionParser on ParserBase {
return null;
}
@override
FunctionParameters _functionParameters() {
if (_matchOne(TokenType.star)) {
return StarFunctionParameter()

View File

@ -201,6 +201,9 @@ abstract class ParserBase {
/// Parses a block, which consists of statements between `BEGIN` and `END`.
Block _consumeBlock();
/// Parses function parameters, without the surrounding parentheses.
FunctionParameters _functionParameters();
/// Skips all tokens until it finds one with [type]. If [skipTarget] is true,
/// that token will be skipped as well.
///

View File

@ -86,5 +86,37 @@ void main() {
),
]);
});
test('table valued function', () {
testStatement(
'''
SELECT DISTINCT user.name
FROM user, json_each(user.phone)
WHERE json_each.value LIKE '704-%';
''',
SelectStatement(
distinct: true,
columns: [
ExpressionResultColumn(
expression: Reference(tableName: 'user', columnName: 'name'),
),
],
from: [
TableReference('user'),
TableValuedFunction(
'json_each',
ExprFunctionParameters(parameters: [
Reference(tableName: 'user', columnName: 'phone')
]),
),
],
where: StringComparisonExpression(
left: Reference(tableName: 'json_each', columnName: 'value'),
operator: token(TokenType.like),
right: StringLiteral(stringLiteral('704-%')),
),
),
);
});
});
}

View File

@ -10,6 +10,10 @@ Token token(TokenType type) {
return Token(type, null);
}
StringLiteralToken stringLiteral(String value) {
return StringLiteralToken(value, null);
}
InlineDartToken inlineDart(String dartCode) {
return InlineDartToken(fakeSpan('`$dartCode`'));
}