blob: afcd00ea311c4f32cd61edcd948445e72c60013e [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.compile;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.phoenix.expression.AndExpression;
import org.apache.phoenix.expression.CoerceExpression;
import org.apache.phoenix.expression.ComparisonExpression;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.expression.IsNullExpression;
import org.apache.phoenix.expression.RowKeyColumnExpression;
import org.apache.phoenix.parse.CastParseNode;
import org.apache.phoenix.parse.ColumnParseNode;
import org.apache.phoenix.parse.EqualParseNode;
import org.apache.phoenix.parse.FilterableStatement;
import org.apache.phoenix.parse.HintNode;
import org.apache.phoenix.parse.OffsetNode;
import org.apache.phoenix.parse.ParseNode;
import org.apache.phoenix.parse.RowValueConstructorParseNode;
import org.apache.phoenix.query.KeyRange;
import org.apache.phoenix.schema.PColumn;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTableType;
import org.apache.phoenix.schema.RowKeySchema;
import org.apache.phoenix.schema.RowValueConstructorOffsetInternalErrorException;
import org.apache.phoenix.schema.RowValueConstructorOffsetNotAllowedInQueryException;
import org.apache.phoenix.schema.RowValueConstructorOffsetNotCoercibleException;
import org.apache.phoenix.schema.TypeMismatchException;
import org.apache.phoenix.util.IndexUtil;
import org.apache.phoenix.util.ScanUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class RVCOffsetCompiler {
private final static Logger LOGGER = LoggerFactory.getLogger(RVCOffsetCompiler.class);
private final static RVCOffsetCompiler INSTANCE = new RVCOffsetCompiler();
private RVCOffsetCompiler() {
}
public static RVCOffsetCompiler getInstance() {
return INSTANCE;
}
public CompiledOffset getRVCOffset(StatementContext context, FilterableStatement statement,
boolean inJoin, boolean inUnion, OffsetNode offsetNode) throws SQLException {
// We have a RVC offset. See PHOENIX-4845
// This is a EqualParseNode with LHS and RHS RowValueConstructorParseNodes
// This is enforced as part of the grammar
EqualParseNode equalParseNode = (EqualParseNode) offsetNode.getOffsetParseNode();
RowValueConstructorParseNode
rvcColumnsParseNode =
(RowValueConstructorParseNode) equalParseNode.getLHS();
RowValueConstructorParseNode
rvcConstantParseNode =
(RowValueConstructorParseNode) equalParseNode.getRHS();
// disallow use with aggregations
if (statement.isAggregate()) {
throw new RowValueConstructorOffsetNotAllowedInQueryException("RVC Offset not allowed in Aggregates");
}
// Get the Select Type should not be join/union
// Note cannot use the SelectStatement as for Left/Right joins we won't get passed in the join context
if (inJoin || inUnion) {
throw new RowValueConstructorOffsetNotAllowedInQueryException("RVC Offset not allowed in Joins or Unions");
}
// Get the tables primary keys
if (context.getResolver().getTables().size() != 1) {
throw new RowValueConstructorOffsetNotAllowedInQueryException("RVC Offset not allowed with zero or multiple tables");
}
PTable pTable = context.getCurrentTable().getTable();
List<PColumn> columns = pTable.getPKColumns();
int numUserColumns = columns.size(); // columns specified by the user
int userColumnIndex = 0; // index into the ordered list, columns, of where user specified start
// if we are salted we need to take a subset of the pk
Integer buckets = pTable.getBucketNum();
if (buckets != null && buckets > 0) { // We are salted
numUserColumns--;
userColumnIndex++;
}
if (pTable.isMultiTenant() && context.getConnection().getTenantId() != null) {
// the tenantId is one of the pks and will be handled automatically
numUserColumns--;
userColumnIndex++;
}
boolean isIndex = false;
if (PTableType.INDEX.equals(pTable.getType())) {
isIndex = true;
// If we are a view index we have to handle the idxId column
// Note that viewIndexId comes before tenantId (what about salt byte?)
if (pTable.getViewIndexId() != null) {
numUserColumns--;
userColumnIndex++;
}
}
// Sanity check that they are providing all the user defined keys to this table
if (numUserColumns != rvcConstantParseNode.getChildren().size()) {
throw new RowValueConstructorOffsetNotCoercibleException(
"RVC Offset must exactly cover the tables PK.");
}
// Make sure the order is the same and all the user defined columns are mentioned in the column RVC
if (numUserColumns != rvcColumnsParseNode.getChildren().size()) {
throw new RowValueConstructorOffsetNotCoercibleException(
"RVC Offset must specify the tables PKs.");
}
List<ColumnParseNode>
rvcColumnParseNodeList = buildListOfColumnParseNodes(rvcColumnsParseNode, isIndex);
// Make sure we have all column parse nodes for the left hand
if (rvcColumnParseNodeList.size() != numUserColumns) {
throw new RowValueConstructorOffsetNotCoercibleException(
"RVC Offset must specify the tables PKs.");
}
// We resolve the mini-where now so we can compare to tables pks PColumns and to produce a row offset
// Construct a mini where clause
ParseNode miniWhere = equalParseNode;
Set<HintNode.Hint> originalHints = statement.getHint().getHints();
WhereCompiler.WhereExpressionCompiler whereCompiler = new WhereCompiler.WhereExpressionCompiler(context);
Expression whereExpression;
try {
whereExpression = miniWhere.accept(whereCompiler);
}catch(TypeMismatchException e) {
throw new RowValueConstructorOffsetNotCoercibleException(
"RVC Offset could not be coerced to the tables PKs. " + e.getMessage());
} catch (Exception e) {
LOGGER.error("Unexpected error while compiling RVC Offset, got null expression.",e);
throw new RowValueConstructorOffsetInternalErrorException(
"RVC Offset unexpected failure.");
}
if (whereExpression == null) {
LOGGER.error("Unexpected error while compiling RVC Offset, got null expression.");
throw new RowValueConstructorOffsetInternalErrorException(
"RVC Offset unexpected failure.");
}
Expression expression;
try {
expression =
WhereOptimizer
.pushKeyExpressionsToScan(context, originalHints, whereExpression, null,
Optional.<byte[]>absent());
} catch (Exception e) {
LOGGER.error("Unexpected error while compiling RVC Offset, got null expression.");
throw new RowValueConstructorOffsetInternalErrorException(
"RVC Offset unexpected failure.");
}
//If the whereExpression is a single term comparison/isNull it will be entirely removed
if (expression == null && whereExpression instanceof AndExpression) {
LOGGER.error("Unexpected error while compiling RVC Offset, got null expression.");
throw new RowValueConstructorOffsetInternalErrorException(
"RVC Offset unexpected failure.");
}
// Now that columns etc have been resolved lets check to make sure they match the pk order
RowKeyColumnExpressionOutput rowKeyColumnExpressionOutput =
buildListOfRowKeyColumnExpressions(whereExpression, isIndex);
List<RowKeyColumnExpression>
rowKeyColumnExpressionList = rowKeyColumnExpressionOutput.getRowKeyColumnExpressions();
if (rowKeyColumnExpressionList.size() != numUserColumns) {
LOGGER.warn("Unexpected error while compiling RVC Offset, expected " + numUserColumns
+ " found " + rowKeyColumnExpressionList.size());
throw new RowValueConstructorOffsetInternalErrorException(
"RVC Offset must specify the table's PKs.");
}
for (int i = 0; i < numUserColumns; i++) {
PColumn column = columns.get(i + userColumnIndex);
ColumnParseNode columnParseNode = rvcColumnParseNodeList.get(i);
String columnParseNodeString = columnParseNode.getFullName();
if (isIndex) {
columnParseNodeString = IndexUtil.getDataColumnName(columnParseNodeString);
}
RowKeyColumnExpression rowKeyColumnExpression = rowKeyColumnExpressionList.get(i);
String expressionName = rowKeyColumnExpression.getName();
// Not sure why it is getting quoted
expressionName = expressionName.replace("\"", "");
if (isIndex) {
expressionName = IndexUtil.getDataColumnName(expressionName);
}
if (!StringUtils.equals(expressionName, columnParseNodeString)) {
throw new RowValueConstructorOffsetNotCoercibleException(
"RVC Offset must specify the table's PKs.");
}
String columnString = column.getName().getString();
if (isIndex) {
columnString = IndexUtil.getDataColumnName(columnString);
}
if (!StringUtils.equals(expressionName, columnString)) {
throw new RowValueConstructorOffsetNotCoercibleException(
"RVC Offset must specify the table's PKs.");
}
}
byte[] key;
// check to see if this was a single key expression
ScanRanges scanRanges = context.getScanRanges();
//We do not generate a point lookup today in phoenix if the rowkey has a trailing null, we generate a range scan.
if (!scanRanges.isPointLookup()) {
//Since we use a range scan to guarantee we get only the null value and the upper bound is unset this suffices
//sanity check
if (!rowKeyColumnExpressionOutput.isTrailingNull()) {
throw new RowValueConstructorOffsetNotCoercibleException(
"RVC Offset must be a point lookup.");
}
key = scanRanges.getScanRange().getUpperRange();
} else {
RowKeySchema.RowKeySchemaBuilder builder = new RowKeySchema.RowKeySchemaBuilder(columns.size());
for (PColumn column : columns) {
builder.addField(column, column.isNullable(), column.getSortOrder());
}
RowKeySchema rowKeySchema = builder.build();
//we make a ScanRange with 1 keyslots that cover the entire PK to reuse the code
KeyRange pointKeyRange = scanRanges.getScanRange();
KeyRange keyRange = KeyRange.getKeyRange(pointKeyRange.getLowerRange(), false, KeyRange.UNBOUND, true);
List<KeyRange> myRangeList = Lists.newArrayList(keyRange);
List<List<KeyRange>> slots = new ArrayList<>();
slots.add(myRangeList);
int[] slotSpan = new int[1];
//subtract 1 see ScanUtil.SINGLE_COLUMN_SLOT_SPAN is 0
slotSpan[0] = columns.size() - 1;
key = ScanUtil.getMinKey(rowKeySchema, slots, slotSpan);
}
// Note the use of ByteUtil.nextKey() to generate exclusive offset
CompiledOffset
compiledOffset =
new CompiledOffset(Optional.<Integer>absent(),
Optional.of(key));
return compiledOffset;
}
@VisibleForTesting
static class RowKeyColumnExpressionOutput {
public RowKeyColumnExpressionOutput(List<RowKeyColumnExpression> rowKeyColumnExpressions, boolean trailingNull) {
this.rowKeyColumnExpressions = rowKeyColumnExpressions;
this.trailingNull = trailingNull;
}
private final List<RowKeyColumnExpression> rowKeyColumnExpressions;
private final boolean trailingNull;
public List<RowKeyColumnExpression> getRowKeyColumnExpressions() {
return rowKeyColumnExpressions;
}
public boolean isTrailingNull() {
return trailingNull;
}
}
@VisibleForTesting
RowKeyColumnExpressionOutput buildListOfRowKeyColumnExpressions (
Expression whereExpression, boolean isIndex)
throws RowValueConstructorOffsetNotCoercibleException, RowValueConstructorOffsetInternalErrorException {
boolean trailingNull = false;
List<Expression> expressions;
if((whereExpression instanceof AndExpression)) {
expressions = whereExpression.getChildren();
} else if (whereExpression instanceof ComparisonExpression || whereExpression instanceof IsNullExpression) {
expressions = Lists.newArrayList(whereExpression);
} else {
LOGGER.warn("Unexpected error while compiling RVC Offset, expected either a Comparison/IsNull Expression of a AndExpression got "
+ whereExpression.getClass().getName());
throw new RowValueConstructorOffsetInternalErrorException(
"RVC Offset must specify the tables PKs.");
}
List<RowKeyColumnExpression>
rowKeyColumnExpressionList =
new ArrayList<RowKeyColumnExpression>();
for (int i = 0; i < expressions.size(); i++) {
Expression child = expressions.get(i);
if (!(child instanceof ComparisonExpression || child instanceof IsNullExpression)) {
LOGGER.warn("Unexpected error while compiling RVC Offset");
throw new RowValueConstructorOffsetNotCoercibleException(
"RVC Offset must specify the tables PKs.");
}
//if this is the last position
if(i == expressions.size() - 1) {
if(child instanceof IsNullExpression) {
trailingNull = true;
}
}
//For either case comparison/isNull the first child should be the rowkey
Expression possibleRowKeyColumnExpression = child.getChildren().get(0);
// Note that since we store indexes in variable length form there may be casts from fixed types to
// variable length
if (isIndex) {
if (possibleRowKeyColumnExpression instanceof CoerceExpression) {
// Cast today has 1 child
possibleRowKeyColumnExpression =
((CoerceExpression) possibleRowKeyColumnExpression).getChild();
}
}
if (!(possibleRowKeyColumnExpression instanceof RowKeyColumnExpression)) {
LOGGER.warn("Unexpected error while compiling RVC Offset");
throw new RowValueConstructorOffsetNotCoercibleException(
"RVC Offset must specify the tables PKs.");
}
rowKeyColumnExpressionList.add((RowKeyColumnExpression) possibleRowKeyColumnExpression);
}
return new RowKeyColumnExpressionOutput(rowKeyColumnExpressionList,trailingNull);
}
@VisibleForTesting List<ColumnParseNode> buildListOfColumnParseNodes(
RowValueConstructorParseNode rvcColumnsParseNode, boolean isIndex)
throws RowValueConstructorOffsetNotCoercibleException {
List<ColumnParseNode> nodes = new ArrayList<ColumnParseNode>();
for (ParseNode node : rvcColumnsParseNode.getChildren()) {
// Note that since we store indexes in variable length form there may be casts from fixed types to
// variable length
if (isIndex) {
if (node instanceof CastParseNode) {
// Cast today has 1 child
node = node.getChildren().get(0);
}
}
if (!(node instanceof ColumnParseNode)) {
throw new RowValueConstructorOffsetNotCoercibleException(
"RVC Offset must specify the tables PKs.");
} else {
nodes.add((ColumnParseNode) node);
}
}
return nodes;
}
}