/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to you under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eigenbase.rel; import java.util.*; import org.eigenbase.relopt.*; import org.eigenbase.reltype.RelDataType; import org.eigenbase.rex.*; import org.eigenbase.sql.SqlAggFunction; import org.eigenbase.util.ImmutableIntList; import org.eigenbase.util.Util; import net.hydromatic.linq4j.Ord; import com.google.common.collect.ImmutableList; /** * A relational expression representing a set of window aggregates. * * <p>A window rel can handle several window aggregate functions, over several * partitions, with pre- and post-expressions, and an optional post-filter. * Each of the partitions is defined by a partition key (zero or more columns) * and a range (logical or physical). The partitions expect the data to be * sorted correctly on input to the relational expression. * * <p>Each {@link org.eigenbase.rel.WindowRelBase.Window} has a set of * {@link org.eigenbase.rex.RexOver} objects. * * <p>Created by {@link org.eigenbase.rel.rules.WindowedAggSplitterRule}. */ public abstract class WindowRelBase extends SingleRel { public final ImmutableList<Window> windows; public final List<RexLiteral> constants; /** * Creates a window relational expression. * * @param cluster Cluster * @param child Input relational expression * @param constants List of constants that are additional inputs * @param rowType Output row type * @param windows Windows */ public WindowRelBase( RelOptCluster cluster, RelTraitSet traits, RelNode child, List<RexLiteral> constants, RelDataType rowType, List<Window> windows) { super(cluster, traits, child); this.constants = ImmutableList.copyOf(constants); assert rowType != null; this.rowType = rowType; this.windows = ImmutableList.copyOf(windows); } @Override public boolean isValid(boolean fail) { // In the window specifications, an aggregate call such as // 'SUM(RexInputRef #10)' refers to expression #10 of inputProgram. // (Not its projections.) final RelDataType childRowType = getChild().getRowType(); final int childFieldCount = childRowType.getFieldCount(); final int inputSize = childFieldCount + constants.size(); final List<RelDataType> inputTypes = new AbstractList<RelDataType>() { @Override public RelDataType get(int index) { return index < childFieldCount ? childRowType.getFieldList().get(index).getType() : constants.get(index - childFieldCount).getType(); } @Override public int size() { return inputSize; } }; final RexChecker checker = new RexChecker(inputTypes, fail); int count = 0; for (Window window : windows) { for (RexWinAggCall over : window.aggCalls) { ++count; if (!checker.isValid(over)) { return false; } } } if (count == 0) { assert !fail : "empty"; return false; } return true; } public RelWriter explainTerms(RelWriter pw) { super.explainTerms(pw); for (Ord<Window> window : Ord.zip(windows)) { pw.item("window#" + window.i, window.e.toString()); } return pw; } static ImmutableIntList getProjectOrdinals(final List<RexNode> exprs) { return ImmutableIntList.copyOf( new AbstractList<Integer>() { public Integer get(int index) { return ((RexSlot) exprs.get(index)).getIndex(); } public int size() { return exprs.size(); } }); } static RelCollation getCollation(final List<RexFieldCollation> collations) { return RelCollationImpl.of( new AbstractList<RelFieldCollation>() { public RelFieldCollation get(int index) { final RexFieldCollation collation = collations.get(index); return new RelFieldCollation( ((RexLocalRef) collation.left).getIndex(), collation.getDirection(), collation.getNullDirection()); } public int size() { return collations.size(); } }); } /** * Returns constants that are additional inputs of current relation. * @return constants that are additional inputs of current relation */ public List<RexLiteral> getConstants() { return constants; } /** * A Window is a range of input rows, defined by an upper and lower bound. * It also has zero or more partitioning columns. * * <p>A window is either logical or physical. A physical window is measured * in terms of row count. A logical window is measured in terms of rows * within a certain distance from the current sort key. * * <p>For example: * * <ul> * <li><code>ROWS BETWEEN 10 PRECEDING and 5 FOLLOWING</code> is a physical * window with an upper and lower bound; * <li><code>RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND UNBOUNDED * FOLLOWING</code> is a logical window with only a lower bound; * <li><code>RANGE INTERVAL '10' MINUTES PRECEDING</code> (which is * equivalent to <code>RANGE BETWEEN INTERVAL '10' MINUTES PRECEDING AND * CURRENT ROW</code>) is a logical window with an upper and lower bound. * </ul> */ public static class Window { public final BitSet groupSet; public final boolean isRows; public final RexWindowBound lowerBound; public final RexWindowBound upperBound; public final RelCollation orderKeys; private final String digest; /** * List of {@link org.eigenbase.rel.WindowRelBase.RexWinAggCall} * objects, each of which is a call to a * {@link org.eigenbase.sql.SqlAggFunction}. */ public final ImmutableList<RexWinAggCall> aggCalls; public Window( BitSet groupSet, boolean isRows, RexWindowBound lowerBound, RexWindowBound upperBound, RelCollation orderKeys, List<RexWinAggCall> aggCalls) { assert orderKeys != null : "precondition: ordinals != null"; assert groupSet != null; this.groupSet = groupSet; this.isRows = isRows; this.lowerBound = lowerBound; this.upperBound = upperBound; this.orderKeys = orderKeys; this.aggCalls = ImmutableList.copyOf(aggCalls); this.digest = computeString(); } public String toString() { return digest; } private String computeString() { final StringBuilder buf = new StringBuilder(); buf.append("window(partition "); buf.append(groupSet); buf.append(" order by "); buf.append(orderKeys); buf.append(isRows ? " rows " : " range "); if (lowerBound != null) { if (upperBound != null) { buf.append("between "); buf.append(lowerBound); buf.append(" and "); buf.append(upperBound); } else { buf.append(lowerBound); } } else if (upperBound != null) { buf.append(upperBound); } buf.append(" aggs "); buf.append(aggCalls); buf.append(")"); return buf.toString(); } @Override public boolean equals(Object obj) { return this == obj || obj instanceof Window && this.digest.equals(((Window) obj).digest); } @Override public int hashCode() { return digest.hashCode(); } public RelCollation collation() { return orderKeys; } /** * Returns if the window is guaranteed to have rows. * This is useful to refine data type of window aggregates. * For instance sum(non-nullable) over (empty window) is NULL. * @return true when the window is non-empty * @see org.eigenbase.sql.SqlWindow#isAlwaysNonEmpty() * @see org.eigenbase.sql.SqlOperatorBinding#getGroupCount() * @see org.eigenbase.sql.validate.SqlValidatorImpl#resolveWindow(org.eigenbase.sql.SqlNode, org.eigenbase.sql.validate.SqlValidatorScope, boolean) */ public boolean isAlwaysNonEmpty() { int lowerKey = lowerBound.getOrderKey(); int upperKey = upperBound.getOrderKey(); return lowerKey > -1 && lowerKey <= upperKey; } /** * Presents a view of the {@link RexWinAggCall} list as a list of * {@link AggregateCall}. */ public List<AggregateCall> getAggregateCalls(WindowRelBase windowRel) { final List<String> fieldNames = Util.skip(windowRel.getRowType().getFieldNames(), windowRel.getChild().getRowType().getFieldCount()); return new AbstractList<AggregateCall>() { public int size() { return aggCalls.size(); } public AggregateCall get(int index) { final RexWinAggCall aggCall = aggCalls.get(index); return new AggregateCall( (Aggregation) aggCall.getOperator(), false, getProjectOrdinals(aggCall.getOperands()), aggCall.getType(), fieldNames.get(aggCall.ordinal)); } }; } } /** * A call to a windowed aggregate function. * * <p>Belongs to a {@link org.eigenbase.rel.WindowRelBase.Window}. * * <p>It's a bastard son of a {@link org.eigenbase.rex.RexCall}; similar * enough that it gets visited by a {@link org.eigenbase.rex.RexVisitor}, * but it also has some extra data members. */ public static class RexWinAggCall extends RexCall { /** * Ordinal of this aggregate within its partition. */ public final int ordinal; /** * Creates a RexWinAggCall. * * @param aggFun Aggregate function * @param type Result type * @param operands Operands to call * @param ordinal Ordinal within its partition */ public RexWinAggCall( SqlAggFunction aggFun, RelDataType type, List<RexNode> operands, int ordinal) { super(type, aggFun, operands); this.ordinal = ordinal; } @Override public RexCall clone(RelDataType type, List<RexNode> operands) { throw new UnsupportedOperationException(); } } } // End WindowRelBase.java