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 { } else {
return const ResolveResult.unknown(); return const ResolveResult.unknown();
} }
} else if (expr is SqlInvocation) { } else if (expr is ExpressionInvocation) {
return resolveFunctionCall(expr); return resolveFunctionCall(expr);
} else if (expr is IsExpression || } else if (expr is IsExpression ||
expr is IsNullExpression || expr is IsNullExpression ||
@ -167,8 +167,8 @@ class TypeResolver {
}, l); }, l);
} }
ResolveResult resolveFunctionCall(SqlInvocation call) { ResolveResult resolveFunctionCall(ExpressionInvocation call) {
return _cache((SqlInvocation call) { return _cache((ExpressionInvocation call) {
final parameters = call.expandParameters(); final parameters = call.expandParameters();
final firstNullable = final firstNullable =
// ignore: avoid_bool_literals_in_conditional_expressions // ignore: avoid_bool_literals_in_conditional_expressions
@ -413,7 +413,7 @@ class TypeResolver {
parent is Tuple || parent is Tuple ||
parent is UnaryExpression) { parent is UnaryExpression) {
return const ResolveResult.needsContext(); 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 // 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 // that. Otherwise, just assume that the argument has the same type as the
// return type of the function // return type of the function

View File

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

View File

@ -8,10 +8,12 @@ abstract class Queryable extends AstNode {
return visitor.visitQueryable(this, arg); return visitor.visitQueryable(this, arg);
} }
// todo remove this, introduce more visit methods for subclasses
T when<T>({ T when<T>({
@required T Function(TableReference) isTable, @required T Function(TableReference) isTable,
@required T Function(SelectStatementAsSource) isSelect, @required T Function(SelectStatementAsSource) isSelect,
@required T Function(JoinClause) isJoin, @required T Function(JoinClause) isJoin,
@required T Function(TableValuedFunction) isTableFunction,
}) { }) {
if (this is TableReference) { if (this is TableReference) {
return isTable(this as TableReference); return isTable(this as TableReference);
@ -19,6 +21,8 @@ abstract class Queryable extends AstNode {
return isSelect(this as SelectStatementAsSource); return isSelect(this as SelectStatementAsSource);
} else if (this is JoinClause) { } else if (this is JoinClause) {
return isJoin(this as JoinClause); return isJoin(this as JoinClause);
} else if (this is TableValuedFunction) {
return isTableFunction(this as TableValuedFunction);
} }
throw StateError('Unknown subclass'); throw StateError('Unknown subclass');
@ -165,3 +169,25 @@ class UsingConstraint extends JoinConstraint {
UsingConstraint({@required this.columnNames}); 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'; part of '../ast.dart';
class AggregateExpression extends Expression class AggregateExpression extends Expression
implements SqlInvocation, ReferenceOwner { implements ExpressionInvocation, ReferenceOwner {
final IdentifierToken function; final IdentifierToken function;
@override @override

View File

@ -2,16 +2,19 @@ part of '../ast.dart';
/// Interface for function calls, either a [FunctionExpression] or a /// Interface for function calls, either a [FunctionExpression] or a
/// [AggregateExpression]. /// [AggregateExpression].
abstract class SqlInvocation extends Expression { abstract class SqlInvocation implements AstNode {
/// The name of the function being called /// The name of the function being called
String get name; String get name;
FunctionParameters get parameters; FunctionParameters get parameters;
} }
/// Interface for [SqlInvocation]s that are also expressions.
abstract class ExpressionInvocation implements SqlInvocation, Expression {}
class FunctionExpression extends Expression class FunctionExpression extends Expression
with ReferenceOwner with ReferenceOwner
implements SqlInvocation { implements ExpressionInvocation {
@override @override
final String name; final String name;
@override @override
@ -75,6 +78,6 @@ class ExprFunctionParameters extends FunctionParameters {
@override @override
bool contentEquals(ExprFunctionParameters other) { 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 @override
R visitFunction(FunctionExpression e, A arg) { R visitFunction(FunctionExpression e, A arg) {
return visitInvocation(e, arg); return visitExpressionInvocation(e, arg);
} }
R visitFunctionParameters(FunctionParameters e, A arg) { R visitFunctionParameters(FunctionParameters e, A arg) {
@ -351,7 +351,7 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
@override @override
R visitAggregateExpression(AggregateExpression e, A arg) { R visitAggregateExpression(AggregateExpression e, A arg) {
return visitInvocation(e, arg); return visitExpressionInvocation(e, arg);
} }
@override @override
@ -403,8 +403,12 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
return visitExpression(e, arg); return visitExpression(e, arg);
} }
R visitExpressionInvocation(ExpressionInvocation e, A arg) {
return visitInvocation(e, arg);
}
R visitInvocation(SqlInvocation e, A arg) { R visitInvocation(SqlInvocation e, A arg) {
return visitExpression(e, arg); return visitChildren(e, arg);
} }
R visitExpression(Expression e, A arg) { R visitExpression(Expression e, A arg) {

View File

@ -258,9 +258,22 @@ mixin CrudParser on ParserBase {
TableOrSubquery _tableOrSubquery() { TableOrSubquery _tableOrSubquery() {
// this is what we're parsing: https://www.sqlite.org/syntax/table-or-subquery.html // 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(); final tableRef = _tableReference();
if (tableRef != null) { 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; return tableRef;
} else if (_matchOne(TokenType.leftParen)) { } else if (_matchOne(TokenType.leftParen)) {
final first = _previous; final first = _previous;

View File

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

View File

@ -201,6 +201,9 @@ abstract class ParserBase {
/// Parses a block, which consists of statements between `BEGIN` and `END`. /// Parses a block, which consists of statements between `BEGIN` and `END`.
Block _consumeBlock(); Block _consumeBlock();
/// Parses function parameters, without the surrounding parentheses.
FunctionParameters _functionParameters();
/// Skips all tokens until it finds one with [type]. If [skipTarget] is true, /// Skips all tokens until it finds one with [type]. If [skipTarget] is true,
/// that token will be skipped as well. /// 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); return Token(type, null);
} }
StringLiteralToken stringLiteral(String value) {
return StringLiteralToken(value, null);
}
InlineDartToken inlineDart(String dartCode) { InlineDartToken inlineDart(String dartCode) {
return InlineDartToken(fakeSpan('`$dartCode`')); return InlineDartToken(fakeSpan('`$dartCode`'));
} }