mirror of https://github.com/AMT-Cheif/drift.git
Support parsing table valued functions
This commit is contained in:
parent
ceb300e32e
commit
223f1615ab
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
part of '../ast.dart';
|
||||
|
||||
class AggregateExpression extends Expression
|
||||
implements SqlInvocation, ReferenceOwner {
|
||||
implements ExpressionInvocation, ReferenceOwner {
|
||||
final IdentifierToken function;
|
||||
|
||||
@override
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -257,10 +257,23 @@ 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
|
||||
// this is what we're parsing: https://www.sqlite.org/syntax/table-or-subquery.html
|
||||
// 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;
|
||||
|
|
|
@ -354,6 +354,7 @@ mixin ExpressionParser on ParserBase {
|
|||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
FunctionParameters _functionParameters() {
|
||||
if (_matchOne(TokenType.star)) {
|
||||
return StarFunctionParameter()
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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-%')),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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`'));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue