/* * 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.*; import org.eigenbase.rex.*; import org.eigenbase.util.Pair; import org.eigenbase.util.Util; import net.hydromatic.linq4j.Ord; import net.hydromatic.optiq.util.BitSets; import com.google.common.base.Predicate; import com.google.common.collect.*; /** * 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 final class WindowRel extends WindowRelBase { /** * Creates a WindowRel. * * @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 WindowRel( RelOptCluster cluster, RelTraitSet traits, RelNode child, List<RexLiteral> constants, RelDataType rowType, List<Window> windows) { super(cluster, traits, child, constants, rowType, windows); } @Override public WindowRel copy(RelTraitSet traitSet, List<RelNode> inputs) { return new WindowRel( getCluster(), traitSet, sole(inputs), constants, rowType, windows); } /** * Creates a WindowRel. */ public static RelNode create( RelOptCluster cluster, RelTraitSet traitSet, RelNode child, final RexProgram program, RelDataType outRowType) { // Build a list of distinct windows, partitions and aggregate // functions. final Multimap<WindowKey, RexOver> windowMap = LinkedListMultimap.create(); final int inputFieldCount = child.getRowType().getFieldCount(); final Map<RexLiteral, RexInputRef> constantPool = new HashMap<RexLiteral, RexInputRef>(); final List<RexLiteral> constants = new ArrayList<RexLiteral>(); // Identify constants in the expression tree and replace them with // references to newly generated constant pool. RexShuttle replaceConstants = new RexShuttle() { @Override public RexNode visitLiteral(RexLiteral literal) { RexInputRef ref = constantPool.get(literal); if (ref != null) { return ref; } constants.add(literal); ref = new RexInputRef(constantPool.size() + inputFieldCount, literal.getType()); constantPool.put(literal, ref); return ref; } }; // Build a list of windows, partitions, and aggregate functions. Each // aggregate function will add its arguments as outputs of the input // program. for (RexNode agg : program.getExprList()) { if (agg instanceof RexOver) { RexOver over = (RexOver) agg; over = (RexOver) over.accept(replaceConstants); addWindows(windowMap, over, inputFieldCount); } } final Map<RexOver, WindowRelBase.RexWinAggCall> aggMap = new HashMap<RexOver, WindowRelBase.RexWinAggCall>(); List<Window> windowList = new ArrayList<Window>(); for (Map.Entry<WindowKey, Collection<RexOver>> entry : windowMap.asMap().entrySet()) { final WindowKey windowKey = entry.getKey(); final List<RexWinAggCall> aggCalls = new ArrayList<RexWinAggCall>(); for (RexOver over : entry.getValue()) { final RexWinAggCall aggCall = new RexWinAggCall( over.getAggOperator(), over.getType(), toInputRefs(over.operands), aggMap.size()); aggCalls.add(aggCall); aggMap.put(over, aggCall); } RexShuttle toInputRefs = new RexShuttle() { @Override public RexNode visitLocalRef(RexLocalRef localRef) { return new RexInputRef(localRef.getIndex(), localRef.getType()); } }; windowList.add( new Window( windowKey.groupSet, windowKey.isRows, windowKey.lowerBound.accept(toInputRefs), windowKey.upperBound.accept(toInputRefs), windowKey.orderKeys, aggCalls)); } // Figure out the type of the inputs to the output program. // They are: the inputs to this rel, followed by the outputs of // each window. final List<WindowRelBase.RexWinAggCall> flattenedAggCallList = new ArrayList<WindowRelBase.RexWinAggCall>(); List<Map.Entry<String, RelDataType>> fieldList = new ArrayList<Map.Entry<String, RelDataType>>( child.getRowType().getFieldList()); final int offset = fieldList.size(); // Use better field names for agg calls that are projected. Map<Integer, String> fieldNames = new HashMap<Integer, String>(); for (Ord<RexLocalRef> ref : Ord.zip(program.getProjectList())) { final int index = ref.e.getIndex(); if (index >= offset) { fieldNames.put( index - offset, outRowType.getFieldNames().get(ref.i)); } } for (Ord<Window> window : Ord.zip(windowList)) { for (Ord<RexWinAggCall> over : Ord.zip(window.e.aggCalls)) { // Add the k-th over expression of // the i-th window to the output of the program. String name = fieldNames.get(over.i); if (name == null || name.startsWith("$")) { name = "w" + window.i + "$o" + over.i; } fieldList.add(Pair.of(name, over.e.getType())); flattenedAggCallList.add(over.e); } } final RelDataType intermediateRowType = cluster.getTypeFactory().createStructType(fieldList); // The output program is the windowed agg's program, combined with // the output calc (if it exists). RexShuttle shuttle = new RexShuttle() { public RexNode visitOver(RexOver over) { // Look up the aggCall which this expr was translated to. final WindowRelBase.RexWinAggCall aggCall = aggMap.get(over); assert aggCall != null; assert RelOptUtil.eq( "over", over.getType(), "aggCall", aggCall.getType(), true); // Find the index of the aggCall among all partitions of all // windows. final int aggCallIndex = flattenedAggCallList.indexOf(aggCall); assert aggCallIndex >= 0; // Replace expression with a reference to the window slot. final int index = inputFieldCount + aggCallIndex; assert RelOptUtil.eq( "over", over.getType(), "intermed", intermediateRowType.getFieldList().get(index).getType(), true); return new RexInputRef( index, over.getType()); } public RexNode visitLocalRef(RexLocalRef localRef) { final int index = localRef.getIndex(); if (index < inputFieldCount) { // Reference to input field. return localRef; } return new RexLocalRef( flattenedAggCallList.size() + index, localRef.getType()); } }; // TODO: The order that the "over" calls occur in the windows and // partitions may not match the order in which they occurred in the // original expression. We should add a project to permute them. WindowRel window = new WindowRel( cluster, traitSet, child, constants, intermediateRowType, windowList); return RelOptUtil.createProject( window, toInputRefs(program.getProjectList()), outRowType.getFieldNames()); } private static List<RexNode> toInputRefs( final List<? extends RexNode> operands) { return new AbstractList<RexNode>() { public int size() { return operands.size(); } public RexNode get(int index) { final RexNode operand = operands.get(index); if (operand instanceof RexInputRef) { return operand; } assert operand instanceof RexLocalRef; final RexLocalRef ref = (RexLocalRef) operand; return new RexInputRef(ref.getIndex(), ref.getType()); } }; } /** Window specification. All windowed aggregates over the same window * (regardless of how it is specified, in terms of a named window or specified * attribute by attribute) will end up with the same window key. */ private static class WindowKey { private final BitSet groupSet; private final RelCollation orderKeys; private final boolean isRows; private final RexWindowBound lowerBound; private final RexWindowBound upperBound; public WindowKey( BitSet groupSet, RelCollation orderKeys, boolean isRows, RexWindowBound lowerBound, RexWindowBound upperBound) { this.groupSet = groupSet; this.orderKeys = orderKeys; this.isRows = isRows; this.lowerBound = lowerBound; this.upperBound = upperBound; } @Override public int hashCode() { return Util.hashV( groupSet, orderKeys, isRows, lowerBound, upperBound); } @Override public boolean equals(Object obj) { return obj == this || obj instanceof WindowKey && groupSet.equals(((WindowKey) obj).groupSet) && orderKeys.equals(((WindowKey) obj).orderKeys) && Util.equal(lowerBound, ((WindowKey) obj).lowerBound) && Util.equal(upperBound, ((WindowKey) obj).upperBound) && isRows == ((WindowKey) obj).isRows; } } private static void addWindows( Multimap<WindowKey, RexOver> windowMap, RexOver over, final int inputFieldCount) { final RexWindow aggWindow = over.getWindow(); // Look up or create a window. RelCollation orderKeys = getCollation( Lists.newArrayList( Iterables.filter(aggWindow.orderKeys, new Predicate<RexFieldCollation>() { public boolean apply(RexFieldCollation rexFieldCollation) { // If ORDER BY references constant (i.e. RexInputRef), // then we can ignore such ORDER BY key. return rexFieldCollation.left instanceof RexLocalRef; } }))); BitSet groupSet = BitSets.of(getProjectOrdinals(aggWindow.partitionKeys)); final int groupLength = groupSet.length(); if (inputFieldCount < groupLength) { // If PARTITION BY references constant, we can ignore such partition key. // All the inputs after inputFieldCount are literals, thus we can clear. groupSet.clear(inputFieldCount, groupLength); } WindowKey windowKey = new WindowKey( groupSet, orderKeys, aggWindow.isRows(), aggWindow.getLowerBound(), aggWindow.getUpperBound()); windowMap.put(windowKey, over); } } // End WindowRel.java