AnalyticWindow.java
// 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.
// This file is copied from
// https://github.com/apache/impala/blob/branch-2.9.0/fe/src/main/java/org/apache/impala/AnalyticWindow.java
// and modified by Doris
package org.apache.doris.analysis;
import org.apache.doris.catalog.TableIf;
import org.apache.doris.catalog.TableIf.TableType;
import org.apache.doris.thrift.TAnalyticWindow;
import org.apache.doris.thrift.TAnalyticWindowBoundary;
import org.apache.doris.thrift.TAnalyticWindowBoundaryType;
import org.apache.doris.thrift.TAnalyticWindowType;
import com.google.common.base.Preconditions;
import java.math.BigDecimal;
import java.util.Objects;
/**
* Windowing clause of an analytic expr
* Both left and right boundaries are always non-null after analyze().
*/
public class AnalyticWindow {
// default window used when an analytic expr was given an order by but no window
public static final AnalyticWindow DEFAULT_WINDOW = new AnalyticWindow(Type.RANGE,
new Boundary(BoundaryType.UNBOUNDED_PRECEDING, null),
new Boundary(BoundaryType.CURRENT_ROW, null));
public enum Type {
ROWS("ROWS"),
RANGE("RANGE");
private final String description;
private Type(String d) {
description = d;
}
@Override
public String toString() {
return description;
}
public TAnalyticWindowType toThrift() {
return this == ROWS ? TAnalyticWindowType.ROWS : TAnalyticWindowType.RANGE;
}
}
public enum BoundaryType {
UNBOUNDED_PRECEDING("UNBOUNDED PRECEDING"),
UNBOUNDED_FOLLOWING("UNBOUNDED FOLLOWING"),
CURRENT_ROW("CURRENT ROW"),
PRECEDING("PRECEDING"),
FOLLOWING("FOLLOWING");
private final String description;
private BoundaryType(String d) {
description = d;
}
@Override
public String toString() {
return description;
}
public TAnalyticWindowBoundaryType toThrift() {
Preconditions.checkState(!isAbsolutePos());
if (this == CURRENT_ROW) {
return TAnalyticWindowBoundaryType.CURRENT_ROW;
} else if (this == PRECEDING) {
return TAnalyticWindowBoundaryType.PRECEDING;
} else if (this == FOLLOWING) {
return TAnalyticWindowBoundaryType.FOLLOWING;
}
return null;
}
public boolean isAbsolutePos() {
return this == UNBOUNDED_PRECEDING || this == UNBOUNDED_FOLLOWING;
}
public boolean isOffset() {
return this == PRECEDING || this == FOLLOWING;
}
public boolean isPreceding() {
return this == UNBOUNDED_PRECEDING || this == PRECEDING;
}
public boolean isFollowing() {
return this == UNBOUNDED_FOLLOWING || this == FOLLOWING;
}
public BoundaryType converse() {
switch (this) {
case UNBOUNDED_PRECEDING:
return UNBOUNDED_FOLLOWING;
case UNBOUNDED_FOLLOWING:
return UNBOUNDED_PRECEDING;
case PRECEDING:
return FOLLOWING;
case FOLLOWING:
return PRECEDING;
default:
return CURRENT_ROW;
}
}
}
public static class Boundary {
private final BoundaryType type;
// Offset expr. Only set for PRECEDING/FOLLOWING. Needed for toSql().
private final Expr expr;
// The offset value. Set during analysis after evaluating expr_. Integral valued
// for ROWS windows.
private BigDecimal offsetValue;
public BoundaryType getType() {
return type;
}
public Expr getExpr() {
return expr;
}
public Boundary(BoundaryType type, Expr e) {
this(type, e, null);
}
// c'tor used by clone()
public Boundary(BoundaryType type, Expr e, BigDecimal offsetValue) {
Preconditions.checkState(
(type.isOffset() && e != null)
|| (!type.isOffset() && e == null));
this.type = type;
this.expr = e;
this.offsetValue = offsetValue;
}
public String toSql() {
StringBuilder sb = new StringBuilder();
if (expr != null) {
sb.append(expr.toSql()).append(" ");
}
sb.append(type.toString());
return sb.toString();
}
public String toSql(boolean disableTableName, boolean needExternalSql, TableType tableType,
TableIf table) {
StringBuilder sb = new StringBuilder();
if (expr != null) {
sb.append(expr.toSql(disableTableName, needExternalSql, tableType, table)).append(" ");
}
sb.append(type.toString());
return sb.toString();
}
public String toDigest() {
StringBuilder sb = new StringBuilder();
if (expr != null) {
sb.append(expr.toDigest()).append(" ");
}
sb.append(type.toString());
return sb.toString();
}
public TAnalyticWindowBoundary toThrift(Type windowType) {
TAnalyticWindowBoundary result = new TAnalyticWindowBoundary(type.toThrift());
if (type.isOffset() && windowType == Type.ROWS) {
result.setRowsOffsetValue(offsetValue.longValue());
}
// TODO: range windows need range_offset_predicate
return result;
}
@Override
public int hashCode() {
return Objects.hash(type, expr);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != this.getClass()) {
return false;
}
Boundary o = (Boundary) obj;
boolean exprEqual = (expr == null) == (o.expr == null);
if (exprEqual && expr != null) {
exprEqual = expr.equals(o.expr);
}
return type == o.type && exprEqual;
}
public Boundary converse() {
Boundary result = new Boundary(type.converse(),
(expr != null) ? expr.clone() : null);
result.offsetValue = offsetValue;
return result;
}
@Override
public Boundary clone() {
return new Boundary(type, expr != null ? expr.clone() : null, offsetValue);
}
}
private final Type type;
private final Boundary leftBoundary;
private Boundary rightBoundary; // may be null before analyze()
private String toSqlString; // cached after analysis
public Type getType() {
return type;
}
public Boundary getLeftBoundary() {
return leftBoundary;
}
public Boundary getRightBoundary() {
return rightBoundary;
}
public Boundary setRightBoundary(Boundary b) {
return rightBoundary = b;
}
public AnalyticWindow(Type type, Boundary b) {
this.type = type;
Preconditions.checkNotNull(b);
leftBoundary = b;
rightBoundary = null;
}
public AnalyticWindow(Type type, Boundary l, Boundary r) {
this.type = type;
Preconditions.checkNotNull(l);
leftBoundary = l;
Preconditions.checkNotNull(r);
rightBoundary = r;
}
/**
* Clone c'tor
*/
private AnalyticWindow(AnalyticWindow other) {
type = other.type;
Preconditions.checkNotNull(other.leftBoundary);
leftBoundary = other.leftBoundary.clone();
if (other.rightBoundary != null) {
rightBoundary = other.rightBoundary.clone();
}
toSqlString = other.toSqlString; // safe to share
}
public AnalyticWindow reverse() {
Boundary newRightBoundary = leftBoundary.converse();
Boundary newLeftBoundary = null;
if (rightBoundary == null) {
newLeftBoundary = new Boundary(leftBoundary.getType(), null);
} else {
newLeftBoundary = rightBoundary.converse();
}
return new AnalyticWindow(type, newLeftBoundary, newRightBoundary);
}
public String toSql() {
if (toSqlString != null) {
return toSqlString;
}
StringBuilder sb = new StringBuilder();
sb.append(type.toString()).append(" ");
if (rightBoundary == null) {
sb.append(leftBoundary.toSql());
} else {
sb.append("BETWEEN ").append(leftBoundary.toSql()).append(" AND ");
sb.append(rightBoundary.toSql());
}
return sb.toString();
}
public String toSql(boolean disableTableName, boolean needExternalSql, TableType tableType,
TableIf table) {
if (toSqlString != null) {
return toSqlString;
}
StringBuilder sb = new StringBuilder();
sb.append(type.toString()).append(" ");
if (rightBoundary == null) {
sb.append(leftBoundary.toSql(disableTableName, needExternalSql, tableType, table));
} else {
sb.append("BETWEEN ").append(leftBoundary.toSql(disableTableName, needExternalSql, tableType, table))
.append(" AND ");
sb.append(rightBoundary.toSql(disableTableName, needExternalSql, tableType, table));
}
return sb.toString();
}
public String toDigest() {
StringBuilder sb = new StringBuilder();
sb.append(type.toString()).append(" ");
if (rightBoundary == null) {
sb.append(leftBoundary.toDigest());
} else {
sb.append("BETWEEN ").append(leftBoundary.toDigest()).append(" AND ");
sb.append(rightBoundary.toDigest());
}
return sb.toString();
}
public TAnalyticWindow toThrift() {
TAnalyticWindow result = new TAnalyticWindow(type.toThrift());
if (leftBoundary.getType() != BoundaryType.UNBOUNDED_PRECEDING) {
result.setWindowStart(leftBoundary.toThrift(type));
}
Preconditions.checkNotNull(rightBoundary);
if (rightBoundary.getType() != BoundaryType.UNBOUNDED_FOLLOWING) {
result.setWindowEnd(rightBoundary.toThrift(type));
}
return result;
}
@Override
public int hashCode() {
return Objects.hash(type, leftBoundary, rightBoundary);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != this.getClass()) {
return false;
}
AnalyticWindow o = (AnalyticWindow) obj;
boolean rightBoundaryEqual = (rightBoundary == null) == (o.rightBoundary == null);
if (rightBoundaryEqual && rightBoundary != null) {
rightBoundaryEqual = rightBoundary.equals(o.rightBoundary);
}
return type == o.type
&& leftBoundary.equals(o.leftBoundary)
&& rightBoundaryEqual;
}
@Override
public AnalyticWindow clone() {
return new AnalyticWindow(this);
}
}