blob: c6854abd0c0321ce57fd6ce8d3e9c4b64581b6b4 [file] [log] [blame]
/*
* 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.apache.phoenix.util;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import java.util.Map.Entry;
import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.phoenix.compile.ExpressionCompiler;
import org.apache.phoenix.compile.OrderPreservingTracker.Info;
import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
import org.apache.phoenix.expression.AndExpression;
import org.apache.phoenix.expression.ColumnExpression;
import org.apache.phoenix.expression.ComparisonExpression;
import org.apache.phoenix.expression.Determinism;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.expression.IsNullExpression;
import org.apache.phoenix.expression.LiteralExpression;
import org.apache.phoenix.expression.OrderByExpression;
import org.apache.phoenix.expression.RowKeyColumnExpression;
import org.apache.phoenix.expression.visitor.StatelessTraverseAllExpressionVisitor;
import org.apache.phoenix.expression.visitor.StatelessTraverseNoExpressionVisitor;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.schema.ColumnRef;
import org.apache.phoenix.schema.PColumn;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTableType;
import org.apache.phoenix.schema.ProjectedColumn;
import org.apache.phoenix.schema.RowKeyValueAccessor;
import org.apache.phoenix.schema.TableRef;
import org.apache.phoenix.schema.types.PBoolean;
import org.apache.phoenix.schema.types.PDataType;
public class ExpressionUtil {
private ExpressionUtil() {
}
public static boolean isConstant(Expression expression) {
return (expression.isStateless() && isContantForStatement(expression));
}
/**
* this method determines if expression is constant if all children of it are constants.
* @param expression
* @return
*/
public static boolean isContantForStatement(Expression expression) {
return (expression.getDeterminism() == Determinism.ALWAYS
|| expression.getDeterminism() == Determinism.PER_STATEMENT);
}
public static LiteralExpression getConstantExpression(Expression expression, ImmutableBytesWritable ptr)
throws SQLException {
Object value = null;
PDataType type = expression.getDataType();
if (expression.evaluate(null, ptr) && ptr.getLength() != 0) {
value = type.toObject(ptr.get(), ptr.getOffset(), ptr.getLength(), type, expression.getSortOrder(), expression.getMaxLength(), expression.getScale());
}
return LiteralExpression.newConstant(value, type, expression.getDeterminism());
}
public static boolean isNull(Expression expression, ImmutableBytesWritable ptr) {
return isConstant(expression) && (!expression.evaluate(null, ptr) || ptr.getLength() == 0);
}
public static LiteralExpression getNullExpression(Expression expression) throws SQLException {
return LiteralExpression.newConstant(null, expression.getDataType(), expression.getDeterminism());
}
public static boolean evaluatesToTrue(Expression expression) {
if (isConstant(expression)) {
ImmutableBytesWritable ptr = new ImmutableBytesWritable();
expression.evaluate(null, ptr);
return Boolean.TRUE.equals(PBoolean.INSTANCE.toObject(ptr));
}
return false;
}
public static boolean isPkPositionChanging(TableRef tableRef, List<Expression> projectedExpressions) throws SQLException {
for (int i = 0; i < tableRef.getTable().getPKColumns().size(); i++) {
PColumn column = tableRef.getTable().getPKColumns().get(i);
Expression source = projectedExpressions.get(i);
if (source == null || !source
.equals(new ColumnRef(tableRef, column.getPosition()).newColumnExpression())) { return true; }
}
return false;
}
/**
* check the whereExpression to see if the columnExpression is constant.
* eg. for "where a =3 and b > 9", a is constant,but b is not.
* @param columnExpression
* @param whereExpression
* @return
*/
public static boolean isColumnExpressionConstant(ColumnExpression columnExpression, Expression whereExpression) {
if(whereExpression == null) {
return false;
}
IsColumnConstantExpressionVisitor isColumnConstantExpressionVisitor =
new IsColumnConstantExpressionVisitor(columnExpression);
whereExpression.accept(isColumnConstantExpressionVisitor);
return isColumnConstantExpressionVisitor.isConstant();
}
private static class IsColumnConstantExpressionVisitor extends StatelessTraverseNoExpressionVisitor<Void> {
private final Expression columnExpression ;
private Expression firstRhsConstantExpression = null;
private int rhsConstantCount = 0;
private boolean isNullExpressionVisited = false;
public IsColumnConstantExpressionVisitor(Expression columnExpression) {
this.columnExpression = columnExpression;
}
/**
* only consider and,for "where a = 3 or b = 9", neither a or b is constant.
*/
@Override
public Iterator<Expression> visitEnter(AndExpression andExpression) {
if(rhsConstantCount > 1) {
return null;
}
return andExpression.getChildren().iterator();
}
/**
* <pre>
* We just consider {@link ComparisonExpression} because:
* 1.for {@link InListExpression} as "a in ('2')", the {@link InListExpression} is rewritten to
* {@link ComparisonExpression} in {@link InListExpression#create}
* 2.for {@link RowValueConstructorExpression} as "(a,b)=(1,2)",{@link RowValueConstructorExpression}
* is rewritten to {@link ComparisonExpression} in {@link ComparisonExpression#create}
* 3.not consider {@link CoerceExpression}, because for "where cast(a as integer)=2", when a is double,
* a is not constant.
* </pre>
*/
@Override
public Iterator<Expression> visitEnter(ComparisonExpression comparisonExpression) {
if(rhsConstantCount > 1) {
return null;
}
if(comparisonExpression.getFilterOp() != CompareOp.EQUAL) {
return null;
}
Expression lhsExpresssion = comparisonExpression.getChildren().get(0);
if(!this.columnExpression.equals(lhsExpresssion)) {
return null;
}
Expression rhsExpression = comparisonExpression.getChildren().get(1);
if(rhsExpression == null) {
return null;
}
Boolean isConstant = rhsExpression.accept(new IsCompositeLiteralExpressionVisitor());
if(isConstant != null && isConstant.booleanValue()) {
checkConstantValue(rhsExpression);
}
return null;
}
public boolean isConstant() {
return this.rhsConstantCount == 1;
}
@Override
public Iterator<Expression> visitEnter(IsNullExpression isNullExpression) {
if(rhsConstantCount > 1) {
return null;
}
if(isNullExpression.isNegate()) {
return null;
}
Expression lhsExpresssion = isNullExpression.getChildren().get(0);
if(!this.columnExpression.equals(lhsExpresssion)) {
return null;
}
this.checkConstantValue(null);
return null;
}
private void checkConstantValue(Expression rhsExpression) {
if(!this.isNullExpressionVisited && this.firstRhsConstantExpression == null) {
this.firstRhsConstantExpression = rhsExpression;
rhsConstantCount++;
if(rhsExpression == null) {
this.isNullExpressionVisited = true;
}
return;
}
if(!isExpressionEquals(this.isNullExpressionVisited ? null : this.firstRhsConstantExpression, rhsExpression)) {
rhsConstantCount++;
return;
}
}
private static boolean isExpressionEquals(Expression oldExpression,Expression newExpression) {
if(oldExpression == null) {
if(newExpression == null) {
return true;
}
return ExpressionUtil.isNull(newExpression, new ImmutableBytesWritable());
}
if(newExpression == null) {
return ExpressionUtil.isNull(oldExpression, new ImmutableBytesWritable());
}
return oldExpression.equals(newExpression);
}
}
private static class IsCompositeLiteralExpressionVisitor extends StatelessTraverseAllExpressionVisitor<Boolean> {
@Override
public Boolean defaultReturn(Expression expression, List<Boolean> childResultValues) {
if (!ExpressionUtil.isContantForStatement(expression) ||
childResultValues.size() < expression.getChildren().size()) {
return Boolean.FALSE;
}
for (Boolean childResultValue : childResultValues) {
if (!childResultValue) {
return Boolean.FALSE;
}
}
return Boolean.TRUE;
}
@Override
public Boolean visit(LiteralExpression literalExpression) {
return Boolean.TRUE;
}
}
/**
* <pre>
* Infer OrderBys from the rowkey columns of {@link PTable},for projected table may be there is no rowkey columns,
* so we should move forward to inspect {@link ProjectedColumn} by {@link #getOrderByFromProjectedTable}.
* The second part of the return pair is the rowkey column offset we must skip when we create OrderBys, because for table with salted/multiTenant/viewIndexId,
* some leading rowkey columns should be skipped.
* </pre>
* @param tableRef
* @param phoenixConnection
* @param orderByReverse
* @return
* @throws SQLException
*/
public static Pair<OrderBy,Integer> getOrderByFromTable(
TableRef tableRef,
PhoenixConnection phoenixConnection,
boolean orderByReverse) throws SQLException {
PTable table = tableRef.getTable();
Pair<OrderBy,Integer> orderByAndRowKeyColumnOffset =
getOrderByFromTableByRowKeyColumn(table, phoenixConnection, orderByReverse);
if(orderByAndRowKeyColumnOffset.getFirst() != OrderBy.EMPTY_ORDER_BY) {
return orderByAndRowKeyColumnOffset;
}
if(table.getType() == PTableType.PROJECTED) {
orderByAndRowKeyColumnOffset =
getOrderByFromProjectedTable(tableRef, phoenixConnection, orderByReverse);
if(orderByAndRowKeyColumnOffset.getFirst() != OrderBy.EMPTY_ORDER_BY) {
return orderByAndRowKeyColumnOffset;
}
}
return new Pair<OrderBy,Integer>(OrderBy.EMPTY_ORDER_BY, 0);
}
/**
* Infer OrderBys from the rowkey columns of {@link PTable}.
* The second part of the return pair is the rowkey column offset we must skip when we create OrderBys, because for table with salted/multiTenant/viewIndexId,
* some leading rowkey columns should be skipped.
* @param table
* @param phoenixConnection
* @param orderByReverse
* @return
*/
public static Pair<OrderBy,Integer> getOrderByFromTableByRowKeyColumn(
PTable table,
PhoenixConnection phoenixConnection,
boolean orderByReverse) {
Pair<List<RowKeyColumnExpression>,Integer> rowKeyColumnExpressionsAndRowKeyColumnOffset =
ExpressionUtil.getRowKeyColumnExpressionsFromTable(table, phoenixConnection);
List<RowKeyColumnExpression> rowKeyColumnExpressions = rowKeyColumnExpressionsAndRowKeyColumnOffset.getFirst();
int rowKeyColumnOffset = rowKeyColumnExpressionsAndRowKeyColumnOffset.getSecond();
if(rowKeyColumnExpressions.isEmpty()) {
return new Pair<OrderBy,Integer>(OrderBy.EMPTY_ORDER_BY,0);
}
return new Pair<OrderBy,Integer>(
convertRowKeyColumnExpressionsToOrderBy(rowKeyColumnExpressions, orderByReverse),
rowKeyColumnOffset);
}
/**
* For projected table may be there is no rowkey columns,
* so we should move forward to inspect {@link ProjectedColumn} to check if the source column is rowkey column.
* The second part of the return pair is the rowkey column offset we must skip when we create OrderBys, because for table with salted/multiTenant/viewIndexId,
* some leading rowkey columns should be skipped.
* @param projectedTableRef
* @param phoenixConnection
* @param orderByReverse
* @return
* @throws SQLException
*/
public static Pair<OrderBy,Integer> getOrderByFromProjectedTable(
TableRef projectedTableRef,
PhoenixConnection phoenixConnection,
boolean orderByReverse) throws SQLException {
PTable projectedTable = projectedTableRef.getTable();
assert projectedTable.getType() == PTableType.PROJECTED;
TableRef sourceTableRef = null;
TreeMap<Integer,ColumnRef> sourceRowKeyColumnIndexToProjectedColumnRef =
new TreeMap<Integer, ColumnRef>();
for(PColumn column : projectedTable.getColumns()) {
if(!(column instanceof ProjectedColumn)) {
continue;
}
ProjectedColumn projectedColumn = (ProjectedColumn)column;
ColumnRef sourceColumnRef = projectedColumn.getSourceColumnRef();
TableRef currentSourceTableRef = sourceColumnRef.getTableRef();
if(sourceTableRef == null) {
sourceTableRef = currentSourceTableRef;
}
else if(!sourceTableRef.equals(currentSourceTableRef)) {
return new Pair<OrderBy,Integer>(OrderBy.EMPTY_ORDER_BY, 0);
}
int sourceRowKeyColumnIndex = sourceColumnRef.getPKSlotPosition();
if(sourceRowKeyColumnIndex >= 0) {
ColumnRef projectedColumnRef =
new ColumnRef(projectedTableRef, projectedColumn.getPosition());
sourceRowKeyColumnIndexToProjectedColumnRef.put(
Integer.valueOf(sourceRowKeyColumnIndex), projectedColumnRef);
}
}
if(sourceTableRef == null) {
return new Pair<OrderBy,Integer>(OrderBy.EMPTY_ORDER_BY, 0);
}
final int sourceRowKeyColumnOffset = getRowKeyColumnOffset(sourceTableRef.getTable(), phoenixConnection);
List<OrderByExpression> orderByExpressions = new LinkedList<OrderByExpression>();
int matchedSourceRowKeyColumnOffset = sourceRowKeyColumnOffset;
for(Entry<Integer,ColumnRef> entry : sourceRowKeyColumnIndexToProjectedColumnRef.entrySet()) {
int currentRowKeyColumnOffset = entry.getKey();
if(currentRowKeyColumnOffset < matchedSourceRowKeyColumnOffset) {
continue;
}
else if(currentRowKeyColumnOffset == matchedSourceRowKeyColumnOffset) {
matchedSourceRowKeyColumnOffset++;
}
else {
break;
}
ColumnRef projectedColumnRef = entry.getValue();
Expression projectedValueColumnExpression = projectedColumnRef.newColumnExpression();
OrderByExpression orderByExpression =
OrderByExpression.convertExpressionToOrderByExpression(projectedValueColumnExpression, orderByReverse);
orderByExpressions.add(orderByExpression);
}
if(orderByExpressions.isEmpty()) {
return new Pair<OrderBy,Integer>(OrderBy.EMPTY_ORDER_BY, 0);
}
return new Pair<OrderBy,Integer>(new OrderBy(orderByExpressions), sourceRowKeyColumnOffset);
}
/**
* For table with salted/multiTenant/viewIndexId,some leading rowkey columns should be skipped.
* @param table
* @param phoenixConnection
* @return
*/
public static int getRowKeyColumnOffset(PTable table, PhoenixConnection phoenixConnection) {
boolean isSalted = table.getBucketNum() != null;
boolean isMultiTenant = phoenixConnection.getTenantId() != null && table.isMultiTenant();
boolean isSharedViewIndex = table.getViewIndexId() != null;
return (isSalted ? 1 : 0) + (isMultiTenant ? 1 : 0) + (isSharedViewIndex ? 1 : 0);
}
/**
* Create {@link RowKeyColumnExpression} from {@link PTable}.
* The second part of the return pair is the rowkey column offset we must skip when we create OrderBys, because for table with salted/multiTenant/viewIndexId,
* some leading rowkey columns should be skipped.
* @param table
* @param phoenixConnection
* @return
*/
public static Pair<List<RowKeyColumnExpression>,Integer> getRowKeyColumnExpressionsFromTable(PTable table, PhoenixConnection phoenixConnection) {
int pkPositionOffset = getRowKeyColumnOffset(table, phoenixConnection);
List<PColumn> pkColumns = table.getPKColumns();
if(pkPositionOffset >= pkColumns.size()) {
return new Pair<List<RowKeyColumnExpression>,Integer>(Collections.<RowKeyColumnExpression> emptyList(), 0);
}
List<RowKeyColumnExpression> rowKeyColumnExpressions = new ArrayList<RowKeyColumnExpression>(pkColumns.size() - pkPositionOffset);
for(int index = pkPositionOffset; index < pkColumns.size(); index++) {
RowKeyColumnExpression rowKeyColumnExpression =
new RowKeyColumnExpression(pkColumns.get(index), new RowKeyValueAccessor(pkColumns, index));
rowKeyColumnExpressions.add(rowKeyColumnExpression);
}
return new Pair<List<RowKeyColumnExpression>,Integer>(rowKeyColumnExpressions, pkPositionOffset);
}
/**
* Create OrderByExpression by RowKeyColumnExpression,isNullsLast is the default value "false",isAscending is based on {@link Expression#getSortOrder()}.
* If orderByReverse is true, reverse the isNullsLast and isAscending.
* @param rowKeyColumnExpressions
* @param orderByReverse
* @return
*/
public static OrderBy convertRowKeyColumnExpressionsToOrderBy(List<RowKeyColumnExpression> rowKeyColumnExpressions, boolean orderByReverse) {
return convertRowKeyColumnExpressionsToOrderBy(
rowKeyColumnExpressions, Collections.<Info> emptyList(), orderByReverse);
}
/**
* Create OrderByExpression by RowKeyColumnExpression, if the orderPreservingTrackInfos is not null, use isNullsLast and isAscending from orderPreservingTrackInfos.
* If orderByReverse is true, reverse the isNullsLast and isAscending.
* @param rowKeyColumnExpressions
* @param orderPreservingTrackInfos
* @param orderByReverse
* @return
*/
public static OrderBy convertRowKeyColumnExpressionsToOrderBy(
List<RowKeyColumnExpression> rowKeyColumnExpressions,
List<Info> orderPreservingTrackInfos,
boolean orderByReverse) {
if(rowKeyColumnExpressions.isEmpty()) {
return OrderBy.EMPTY_ORDER_BY;
}
List<OrderByExpression> orderByExpressions = new ArrayList<OrderByExpression>(rowKeyColumnExpressions.size());
Iterator<Info> orderPreservingTrackInfosIter = null;
if(orderPreservingTrackInfos != null && orderPreservingTrackInfos.size() > 0) {
if(orderPreservingTrackInfos.size() != rowKeyColumnExpressions.size()) {
throw new IllegalStateException(
"orderPreservingTrackInfos.size():[" + orderPreservingTrackInfos.size() +
"] should equals rowKeyColumnExpressions.size():[" + rowKeyColumnExpressions.size()+"]!");
}
orderPreservingTrackInfosIter = orderPreservingTrackInfos.iterator();
}
for(RowKeyColumnExpression rowKeyColumnExpression : rowKeyColumnExpressions) {
Info orderPreservingTrackInfo = null;
if(orderPreservingTrackInfosIter != null) {
assert orderPreservingTrackInfosIter.hasNext();
orderPreservingTrackInfo = orderPreservingTrackInfosIter.next();
}
OrderByExpression orderByExpression =
OrderByExpression.convertExpressionToOrderByExpression(rowKeyColumnExpression, orderPreservingTrackInfo, orderByReverse);
orderByExpressions.add(orderByExpression);
}
return new OrderBy(orderByExpressions);
}
/**
* Convert the GroupBy to OrderBy, expressions in GroupBy should be converted to {@link RowKeyColumnExpression}.
* @param groupBy
* @param orderByReverse
* @return
*/
public static OrderBy convertGroupByToOrderBy(GroupBy groupBy, boolean orderByReverse) {
if(groupBy.isEmpty()) {
return OrderBy.EMPTY_ORDER_BY;
}
List<RowKeyColumnExpression> rowKeyColumnExpressions = convertGroupByToRowKeyColumnExpressions(groupBy);
List<Info> orderPreservingTrackInfos = Collections.<Info> emptyList();
if(groupBy.isOrderPreserving()) {
orderPreservingTrackInfos = groupBy.getOrderPreservingTrackInfos();
}
return convertRowKeyColumnExpressionsToOrderBy(rowKeyColumnExpressions, orderPreservingTrackInfos, orderByReverse);
}
/**
* Convert the expressions in GroupBy to {@link RowKeyColumnExpression}, the convert logic is same as {@link ExpressionCompiler#wrapGroupByExpression}.
* @param groupBy
* @return
*/
public static List<RowKeyColumnExpression> convertGroupByToRowKeyColumnExpressions(GroupBy groupBy) {
if(groupBy.isEmpty()) {
return Collections.<RowKeyColumnExpression> emptyList();
}
List<Expression> groupByExpressions = groupBy.getExpressions();
List<RowKeyColumnExpression> rowKeyColumnExpressions = new ArrayList<RowKeyColumnExpression>(groupByExpressions.size());
int columnIndex = 0;
for(Expression groupByExpression : groupByExpressions) {
RowKeyColumnExpression rowKeyColumnExpression =
convertGroupByExpressionToRowKeyColumnExpression(groupBy, groupByExpression, columnIndex++);
rowKeyColumnExpressions.add(rowKeyColumnExpression);
}
return rowKeyColumnExpressions;
}
/**
* Convert the expressions in GroupBy to {@link RowKeyColumnExpression}, a typical case is in {@link ExpressionCompiler#wrapGroupByExpression}.
* @param groupBy
* @param originalExpression
* @param groupByColumnIndex
* @return
*/
public static RowKeyColumnExpression convertGroupByExpressionToRowKeyColumnExpression(
GroupBy groupBy,
Expression originalExpression,
int groupByColumnIndex) {
RowKeyValueAccessor rowKeyValueAccessor = new RowKeyValueAccessor(groupBy.getKeyExpressions(), groupByColumnIndex);
return new RowKeyColumnExpression(
originalExpression,
rowKeyValueAccessor,
groupBy.getKeyExpressions().get(groupByColumnIndex).getDataType());
}
}