mirror of https://github.com/AMT-Cheif/drift.git
215 lines
6.3 KiB
Dart
215 lines
6.3 KiB
Dart
part of '../ast.dart';
|
|
|
|
class AggregateExpression extends Expression
|
|
implements SqlInvocation, ReferenceOwner {
|
|
final IdentifierToken function;
|
|
|
|
@override
|
|
String get name => function.identifier;
|
|
|
|
@override
|
|
final FunctionParameters parameters;
|
|
final Expression filter;
|
|
|
|
@override
|
|
Referencable resolved;
|
|
WindowDefinition get over {
|
|
if (windowDefinition != null) return windowDefinition;
|
|
return (resolved as NamedWindowDeclaration)?.definition;
|
|
}
|
|
|
|
/// The window definition as declared in the `OVER` clause in sql. If this
|
|
/// aggregate expression didn't declare a window (e.g. it instead uses a
|
|
/// window via a name declared in the surrounding `SELECT` statement), we're
|
|
/// this field will be null. Either [windowDefinition] or [windowName] are
|
|
/// null. The resolved [WindowDefinition] is available in [over] in either
|
|
/// case.
|
|
final WindowDefinition windowDefinition;
|
|
|
|
/// An aggregate expression can be written as `OVER <window-name>` instead of
|
|
/// declaring its own [windowDefinition]. Either [windowDefinition] or
|
|
/// [windowName] are null. The resolved [WindowDefinition] is available in
|
|
/// [over] in either case.
|
|
final String windowName;
|
|
|
|
AggregateExpression(
|
|
{@required this.function,
|
|
@required this.parameters,
|
|
this.filter,
|
|
this.windowDefinition,
|
|
this.windowName})
|
|
// either window definition or name must be null
|
|
: assert((windowDefinition == null) != (windowName == null));
|
|
|
|
@override
|
|
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
|
|
return visitor.visitAggregateExpression(this, arg);
|
|
}
|
|
|
|
@override
|
|
Iterable<AstNode> get childNodes {
|
|
return [
|
|
if (parameters is ExprFunctionParameters)
|
|
...(parameters as ExprFunctionParameters).parameters,
|
|
if (filter != null) filter,
|
|
if (windowDefinition != null) windowDefinition,
|
|
];
|
|
}
|
|
|
|
@override
|
|
bool contentEquals(AggregateExpression other) {
|
|
return other.name == name && other.windowName == windowName;
|
|
}
|
|
}
|
|
|
|
/// A window declaration that appears in a `SELECT` statement like
|
|
/// `WINDOW <name> AS <window-defn>`. It can be referenced from an
|
|
/// [AggregateExpression] if it uses the same name.
|
|
class NamedWindowDeclaration with Referencable {
|
|
final String name;
|
|
final WindowDefinition definition;
|
|
|
|
NamedWindowDeclaration(this.name, this.definition);
|
|
}
|
|
|
|
class WindowDefinition extends AstNode {
|
|
final String baseWindowName;
|
|
final List<Expression> partitionBy;
|
|
final OrderByBase orderBy;
|
|
final FrameSpec frameSpec;
|
|
|
|
WindowDefinition(
|
|
{this.baseWindowName,
|
|
this.partitionBy = const [],
|
|
this.orderBy,
|
|
@required this.frameSpec});
|
|
|
|
@override
|
|
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
|
|
return visitor.visitWindowDefinition(this, arg);
|
|
}
|
|
|
|
@override
|
|
Iterable<AstNode> get childNodes =>
|
|
[...partitionBy, if (orderBy != null) orderBy, frameSpec];
|
|
|
|
@override
|
|
bool contentEquals(WindowDefinition other) {
|
|
return other.baseWindowName == baseWindowName;
|
|
}
|
|
}
|
|
|
|
class FrameSpec extends AstNode {
|
|
final FrameType type;
|
|
final ExcludeMode excludeMode;
|
|
final FrameBoundary start;
|
|
final FrameBoundary end;
|
|
|
|
FrameSpec({
|
|
this.type = FrameType.range,
|
|
this.start = const FrameBoundary.unboundedPreceding(),
|
|
this.end = const FrameBoundary.currentRow(),
|
|
this.excludeMode = ExcludeMode.noOthers,
|
|
});
|
|
|
|
@override
|
|
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
|
|
return visitor.visitFrameSpec(this, arg);
|
|
}
|
|
|
|
@override
|
|
Iterable<AstNode> get childNodes => [
|
|
if (start.isExpressionOffset) start.offset,
|
|
if (end.isExpressionOffset) end.offset,
|
|
];
|
|
|
|
@override
|
|
bool contentEquals(FrameSpec other) {
|
|
return other.type == type &&
|
|
other.excludeMode == excludeMode &&
|
|
other.start == start &&
|
|
other.end == end;
|
|
}
|
|
}
|
|
|
|
/// Defines how a [FrameBoundary] count rows or groups. See
|
|
/// https://www.sqlite.org/windowfunctions.html#frame_type for details.
|
|
enum FrameType { rows, groups, range }
|
|
|
|
/// Defines if rows are omitted inside a [FrameBoundary].
|
|
enum ExcludeMode {
|
|
/// default, no rows are excluded from the window frame
|
|
noOthers,
|
|
|
|
/// the current row is excluded from the window frame
|
|
currentRow,
|
|
|
|
/// The row and and its peers (rows considered to be equal to the ORDER BY
|
|
/// clause) are excluded
|
|
group,
|
|
|
|
/// Peers of the row are excluded (see [group]), but not the row itself.
|
|
ties,
|
|
}
|
|
|
|
enum _BoundaryType {
|
|
currentRow,
|
|
exprOffset,
|
|
unboundedOffset,
|
|
}
|
|
|
|
/// Defines how many rows before or after a current row should be included in
|
|
/// a window.
|
|
class FrameBoundary {
|
|
final _BoundaryType _type;
|
|
|
|
/// The (integer) expression that specifies the amount of rows to include
|
|
/// before or after the row being processed.
|
|
final Expression offset;
|
|
|
|
/// Whether this boundary refers to a row before the current row.
|
|
final bool preceding;
|
|
bool get following => !preceding;
|
|
|
|
/// Whether this boundary is a `<expr> PRECEDING` or `<expr> FOLLOWING`
|
|
/// boundary.
|
|
bool get isExpressionOffset => _type == _BoundaryType.exprOffset;
|
|
|
|
/// Whether this boundary is a `UNBOUNDED PRECEDING` or `UNBOUNDED FOLLOWING`.
|
|
bool get isUnbounded => _type == _BoundaryType.unboundedOffset;
|
|
|
|
/// Whether this boundary only refers to the current row.
|
|
bool get isCurrentRow => _type == _BoundaryType.currentRow;
|
|
|
|
const FrameBoundary._(this._type, this.preceding, {this.offset});
|
|
|
|
const FrameBoundary.unboundedPreceding()
|
|
: this._(_BoundaryType.unboundedOffset, true);
|
|
const FrameBoundary.unboundedFollowing()
|
|
: this._(_BoundaryType.unboundedOffset, false);
|
|
|
|
const FrameBoundary.currentRow() : this._(_BoundaryType.currentRow, false);
|
|
|
|
const FrameBoundary.preceding(Expression amount)
|
|
: this._(_BoundaryType.exprOffset, true, offset: amount);
|
|
const FrameBoundary.following(Expression amount)
|
|
: this._(_BoundaryType.exprOffset, false, offset: amount);
|
|
|
|
@override
|
|
int get hashCode {
|
|
return (preceding ? 2 : 3) * (5 * offset.hashCode + _type.hashCode);
|
|
}
|
|
|
|
@override
|
|
bool operator ==(dynamic other) {
|
|
if (identical(this, other)) return true;
|
|
if (other.runtimeType != runtimeType) return false;
|
|
|
|
// lint bug: https://github.com/dart-lang/linter/issues/1397
|
|
final typedOther = other as FrameBoundary; // ignore: test_types_in_equals
|
|
return typedOther._type == _type &&
|
|
typedOther.offset.contentEquals(offset) &&
|
|
typedOther.preceding == preceding;
|
|
}
|
|
}
|