/*
* Licensed 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 com.facebook.presto.operator.window;
import com.facebook.presto.operator.PagesHashStrategy;
import com.facebook.presto.operator.PagesIndex;
import com.facebook.presto.spi.PageBuilder;
import com.facebook.presto.spi.function.WindowIndex;
import com.facebook.presto.sql.tree.FrameBound;
import com.google.common.collect.ImmutableList;
import java.util.List;
import static com.facebook.presto.spi.StandardErrorCode.INVALID_WINDOW_FRAME;
import static com.facebook.presto.sql.tree.FrameBound.Type.FOLLOWING;
import static com.facebook.presto.sql.tree.FrameBound.Type.PRECEDING;
import static com.facebook.presto.sql.tree.FrameBound.Type.UNBOUNDED_FOLLOWING;
import static com.facebook.presto.sql.tree.FrameBound.Type.UNBOUNDED_PRECEDING;
import static com.facebook.presto.sql.tree.WindowFrame.Type.RANGE;
import static com.facebook.presto.util.Failures.checkCondition;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.Math.toIntExact;
public final class WindowPartition
{
private final PagesIndex pagesIndex;
private final int partitionStart;
private final int partitionEnd;
private final int[] outputChannels;
private final List<FramedWindowFunction> windowFunctions;
private final PagesHashStrategy peerGroupHashStrategy;
private int peerGroupStart;
private int peerGroupEnd;
private int currentPosition;
public WindowPartition(PagesIndex pagesIndex,
int partitionStart,
int partitionEnd,
int[] outputChannels,
List<FramedWindowFunction> windowFunctions,
PagesHashStrategy peerGroupHashStrategy)
{
this.pagesIndex = pagesIndex;
this.partitionStart = partitionStart;
this.partitionEnd = partitionEnd;
this.outputChannels = outputChannels;
this.windowFunctions = ImmutableList.copyOf(windowFunctions);
this.peerGroupHashStrategy = peerGroupHashStrategy;
// reset functions for new partition
WindowIndex windowIndex = new PagesWindowIndex(pagesIndex, partitionStart, partitionEnd);
for (FramedWindowFunction framedWindowFunction : windowFunctions) {
framedWindowFunction.getFunction().reset(windowIndex);
}
currentPosition = partitionStart;
updatePeerGroup();
}
public int getPartitionEnd()
{
return partitionEnd;
}
public boolean hasNext()
{
return currentPosition < partitionEnd;
}
public void processNextRow(PageBuilder pageBuilder)
{
checkState(hasNext(), "No more rows in partition");
// copy output channels
pageBuilder.declarePosition();
int channel = 0;
while (channel < outputChannels.length) {
pagesIndex.appendTo(outputChannels[channel], currentPosition, pageBuilder.getBlockBuilder(channel));
channel++;
}
// check for new peer group
if (currentPosition == peerGroupEnd) {
updatePeerGroup();
}
for (FramedWindowFunction framedFunction : windowFunctions) {
Range range = getFrameRange(framedFunction.getFrame());
framedFunction.getFunction().processRow(
pageBuilder.getBlockBuilder(channel),
peerGroupStart - partitionStart,
peerGroupEnd - partitionStart - 1,
range.getStart(),
range.getEnd());
channel++;
}
currentPosition++;
}
private static class Range
{
private final int start;
private final int end;
Range(int start, int end)
{
this.start = start;
this.end = end;
}
public int getStart()
{
return start;
}
public int getEnd()
{
return end;
}
}
private void updatePeerGroup()
{
peerGroupStart = currentPosition;
// find end of peer group
peerGroupEnd = peerGroupStart + 1;
while ((peerGroupEnd < partitionEnd) && pagesIndex.positionEqualsPosition(peerGroupHashStrategy, peerGroupStart, peerGroupEnd)) {
peerGroupEnd++;
}
}
private Range getFrameRange(FrameInfo frameInfo)
{
int rowPosition = currentPosition - partitionStart;
int endPosition = partitionEnd - partitionStart - 1;
// handle empty frame
if (emptyFrame(frameInfo, rowPosition, endPosition)) {
return new Range(-1, -1);
}
int frameStart;
int frameEnd;
// frame start
if (frameInfo.getStartType() == UNBOUNDED_PRECEDING) {
frameStart = 0;
}
else if (frameInfo.getStartType() == PRECEDING) {
frameStart = preceding(rowPosition, getStartValue(frameInfo));
}
else if (frameInfo.getStartType() == FOLLOWING) {
frameStart = following(rowPosition, endPosition, getStartValue(frameInfo));
}
else if (frameInfo.getType() == RANGE) {
frameStart = peerGroupStart - partitionStart;
}
else {
frameStart = rowPosition;
}
// frame end
if (frameInfo.getEndType() == UNBOUNDED_FOLLOWING) {
frameEnd = endPosition;
}
else if (frameInfo.getEndType() == PRECEDING) {
frameEnd = preceding(rowPosition, getEndValue(frameInfo));
}
else if (frameInfo.getEndType() == FOLLOWING) {
frameEnd = following(rowPosition, endPosition, getEndValue(frameInfo));
}
else if (frameInfo.getType() == RANGE) {
frameEnd = peerGroupEnd - partitionStart - 1;
}
else {
frameEnd = rowPosition;
}
return new Range(frameStart, frameEnd);
}
private boolean emptyFrame(FrameInfo frameInfo, int rowPosition, int endPosition)
{
FrameBound.Type startType = frameInfo.getStartType();
FrameBound.Type endType = frameInfo.getEndType();
int positions = endPosition - rowPosition;
if ((startType == UNBOUNDED_PRECEDING) && (endType == PRECEDING)) {
return getEndValue(frameInfo) > rowPosition;
}
if ((startType == FOLLOWING) && (endType == UNBOUNDED_FOLLOWING)) {
return getStartValue(frameInfo) > positions;
}
if (startType != endType) {
return false;
}
FrameBound.Type type = frameInfo.getStartType();
if ((type != PRECEDING) && (type != FOLLOWING)) {
return false;
}
long start = getStartValue(frameInfo);
long end = getEndValue(frameInfo);
if (type == PRECEDING) {
return (start < end) || ((start > rowPosition) && (end > rowPosition));
}
return (start > end) || ((start > positions) && (end > positions));
}
private static int preceding(int rowPosition, long value)
{
if (value > rowPosition) {
return 0;
}
return toIntExact(rowPosition - value);
}
private static int following(int rowPosition, int endPosition, long value)
{
if (value > (endPosition - rowPosition)) {
return endPosition;
}
return toIntExact(rowPosition + value);
}
private long getStartValue(FrameInfo frameInfo)
{
return getFrameValue(frameInfo.getStartChannel(), "starting");
}
private long getEndValue(FrameInfo frameInfo)
{
return getFrameValue(frameInfo.getEndChannel(), "ending");
}
private long getFrameValue(int channel, String type)
{
checkCondition(!pagesIndex.isNull(channel, currentPosition), INVALID_WINDOW_FRAME, "Window frame %s offset must not be null", type);
long value = pagesIndex.getLong(channel, currentPosition);
checkCondition(value >= 0, INVALID_WINDOW_FRAME, "Window frame %s offset must not be negative", value);
return value;
}
}