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 {
|
} 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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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.
|
||||||
///
|
///
|
||||||
|
|
|
@ -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);
|
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`'));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue