blob: 5ccd538a9e39f13c762b9d5e38aee332285e7431 [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 static org.apache.phoenix.util.EncodedColumnsUtil.isPossibleToUseEncodedCQFilter;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.exception.SQLExceptionInfo;
import org.apache.phoenix.expression.AndExpression;
import org.apache.phoenix.expression.Determinism;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.expression.KeyValueColumnExpression;
import org.apache.phoenix.expression.LiteralExpression;
import org.apache.phoenix.expression.visitor.KeyValueExpressionVisitor;
import org.apache.phoenix.filter.MultiCFCQKeyValueComparisonFilter;
import org.apache.phoenix.filter.MultiCQKeyValueComparisonFilter;
import org.apache.phoenix.filter.MultiEncodedCQKeyValueComparisonFilter;
import org.apache.phoenix.filter.RowKeyComparisonFilter;
import org.apache.phoenix.filter.SingleCFCQKeyValueComparisonFilter;
import org.apache.phoenix.filter.SingleCQKeyValueComparisonFilter;
import org.apache.phoenix.parse.ColumnParseNode;
import org.apache.phoenix.parse.FilterableStatement;
import org.apache.phoenix.parse.ParseNode;
import org.apache.phoenix.parse.ParseNodeFactory;
import org.apache.phoenix.parse.SelectStatement;
import org.apache.phoenix.parse.StatelessTraverseAllParseNodeVisitor;
import org.apache.phoenix.parse.SubqueryParseNode;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.AmbiguousColumnException;
import org.apache.phoenix.schema.ColumnNotFoundException;
import org.apache.phoenix.schema.ColumnRef;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTable.IndexType;
import org.apache.phoenix.schema.PTable.ImmutableStorageScheme;
import org.apache.phoenix.schema.PTable.QualifierEncodingScheme;
import org.apache.phoenix.schema.PTableType;
import org.apache.phoenix.schema.TableRef;
import org.apache.phoenix.schema.TypeMismatchException;
import org.apache.phoenix.schema.types.PBoolean;
import org.apache.phoenix.util.ByteUtil;
import org.apache.phoenix.util.ExpressionUtil;
import org.apache.phoenix.util.EncodedColumnsUtil;
import org.apache.phoenix.util.ScanUtil;
import org.apache.phoenix.util.SchemaUtil;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
*
* Class to build the filter of a scan
*
*
* @since 0.1
*/
public class WhereCompiler {
protected static final ParseNodeFactory NODE_FACTORY = new ParseNodeFactory();
private WhereCompiler() {
}
public static Expression compile(StatementContext context, FilterableStatement statement) throws SQLException {
return compile(context, statement, null, null);
}
public static Expression compile(StatementContext context, ParseNode whereNode) throws SQLException {
WhereExpressionCompiler viewWhereCompiler = new WhereExpressionCompiler(context, true);
return whereNode.accept(viewWhereCompiler);
}
/**
* Pushes where clause filter expressions into scan by building and setting a filter.
* @param context the shared context during query compilation
* @param statement TODO
* @throws SQLException if mismatched types are found, bind value do not match binds,
* or invalid function arguments are encountered.
* @throws SQLFeatureNotSupportedException if an unsupported expression is encountered.
* @throws ColumnNotFoundException if column name could not be resolved
* @throws AmbiguousColumnException if an unaliased column name is ambiguous across multiple tables
*/
public static Expression compile(StatementContext context, FilterableStatement statement, ParseNode viewWhere, Set<SubqueryParseNode> subqueryNodes) throws SQLException {
return compile(context, statement, viewWhere, Collections.<Expression>emptyList(), false, subqueryNodes);
}
/**
* Optimize scan ranges by applying dynamically generated filter expressions.
* @param context the shared context during query compilation
* @param statement TODO
* @throws SQLException if mismatched types are found, bind value do not match binds,
* or invalid function arguments are encountered.
* @throws SQLFeatureNotSupportedException if an unsupported expression is encountered.
* @throws ColumnNotFoundException if column name could not be resolved
* @throws AmbiguousColumnException if an unaliased column name is ambiguous across multiple tables
*/
public static Expression compile(StatementContext context, FilterableStatement statement, ParseNode viewWhere, List<Expression> dynamicFilters, boolean hashJoinOptimization, Set<SubqueryParseNode> subqueryNodes) throws SQLException {
ParseNode where = statement.getWhere();
if (subqueryNodes != null) { // if the subqueryNodes passed in is null, we assume there will be no sub-queries in the WHERE clause.
SubqueryParseNodeVisitor subqueryVisitor = new SubqueryParseNodeVisitor(context, subqueryNodes);
if (where != null) {
where.accept(subqueryVisitor);
}
if (viewWhere != null) {
viewWhere.accept(subqueryVisitor);
}
if (!subqueryNodes.isEmpty()) {
return null;
}
}
Set<Expression> extractedNodes = Sets.<Expression>newHashSet();
WhereExpressionCompiler whereCompiler = new WhereExpressionCompiler(context);
Expression expression = where == null ? LiteralExpression.newConstant(true, PBoolean.INSTANCE,Determinism.ALWAYS) : where.accept(whereCompiler);
if (whereCompiler.isAggregate()) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.AGGREGATE_IN_WHERE).build().buildException();
}
if (expression.getDataType() != PBoolean.INSTANCE) {
throw TypeMismatchException.newException(PBoolean.INSTANCE, expression.getDataType(), expression.toString());
}
if (viewWhere != null) {
WhereExpressionCompiler viewWhereCompiler = new WhereExpressionCompiler(context, true);
Expression viewExpression = viewWhere.accept(viewWhereCompiler);
expression = AndExpression.create(Lists.newArrayList(expression, viewExpression));
}
if (!dynamicFilters.isEmpty()) {
List<Expression> filters = Lists.newArrayList(expression);
filters.addAll(dynamicFilters);
expression = AndExpression.create(filters);
}
if (context.getCurrentTable().getTable().getType() != PTableType.PROJECTED && context.getCurrentTable().getTable().getType() != PTableType.SUBQUERY) {
expression = WhereOptimizer.pushKeyExpressionsToScan(context, statement, expression, extractedNodes);
}
setScanFilter(context, statement, expression, whereCompiler.disambiguateWithFamily, hashJoinOptimization);
return expression;
}
private static class WhereExpressionCompiler extends ExpressionCompiler {
private boolean disambiguateWithFamily;
WhereExpressionCompiler(StatementContext context) {
super(context, true);
}
WhereExpressionCompiler(StatementContext context, boolean resolveViewConstants) {
super(context, resolveViewConstants);
}
@Override
public Expression visit(ColumnParseNode node) throws SQLException {
ColumnRef ref = resolveColumn(node);
TableRef tableRef = ref.getTableRef();
Expression newColumnExpression = ref.newColumnExpression(node.isTableNameCaseSensitive(), node.isCaseSensitive());
if (tableRef.equals(context.getCurrentTable()) && !SchemaUtil.isPKColumn(ref.getColumn())) {
byte[] cq = tableRef.getTable().getImmutableStorageScheme() == ImmutableStorageScheme.SINGLE_CELL_ARRAY_WITH_OFFSETS
? QueryConstants.SINGLE_KEYVALUE_COLUMN_QUALIFIER_BYTES : ref.getColumn().getColumnQualifierBytes();
// track the where condition columns. Later we need to ensure the Scan in HRS scans these column CFs
context.addWhereConditionColumn(ref.getColumn().getFamilyName().getBytes(), cq);
}
return newColumnExpression;
}
@Override
protected ColumnRef resolveColumn(ColumnParseNode node) throws SQLException {
ColumnRef ref = super.resolveColumn(node);
PTable table = ref.getTable();
// if current table in the context is local index and table in column reference is global means
// the column is not present in the local index. If where condition contains the column
// not present in the index then we need to go through main table for each row in index and get the
// missing column which is like full scan of index table and data table. Which is
// inefficient. Then we can skip this plan.
if (context.getCurrentTable().getTable().getIndexType() == IndexType.LOCAL
&& (table.getIndexType() == null || table.getIndexType() == IndexType.GLOBAL)) {
throw new ColumnNotFoundException(ref.getColumn().getName().getString());
}
// Track if we need to compare KeyValue during filter evaluation
// using column family. If the column qualifier is enough, we
// just use that.
try {
if (!SchemaUtil.isPKColumn(ref.getColumn())) {
table.getColumnForColumnName(ref.getColumn().getName().getString());
}
} catch (AmbiguousColumnException e) {
disambiguateWithFamily = true;
}
return ref;
}
}
private static final class Counter {
public enum Count {NONE, SINGLE, MULTIPLE};
private Count count = Count.NONE;
private KeyValueColumnExpression column;
public void increment(KeyValueColumnExpression column) {
switch (count) {
case NONE:
count = Count.SINGLE;
this.column = column;
break;
case SINGLE:
count = column.equals(this.column) ? Count.SINGLE : Count.MULTIPLE;
break;
case MULTIPLE:
break;
}
}
public Count getCount() {
return count;
}
}
/**
* Sets the start/stop key range based on the whereClause expression.
* @param context the shared context during query compilation
* @param whereClause the final where clause expression.
*/
private static void setScanFilter(StatementContext context, FilterableStatement statement, Expression whereClause, boolean disambiguateWithFamily, boolean hashJoinOptimization) {
Scan scan = context.getScan();
if (LiteralExpression.isBooleanFalseOrNull(whereClause)) {
context.setScanRanges(ScanRanges.NOTHING);
} else if (whereClause != null && !ExpressionUtil.evaluatesToTrue(whereClause) && !hashJoinOptimization) {
Filter filter = null;
final Counter counter = new Counter();
whereClause.accept(new KeyValueExpressionVisitor() {
@Override
public Iterator<Expression> defaultIterator(Expression node) {
// Stop traversal once we've found multiple KeyValue columns
if (counter.getCount() == Counter.Count.MULTIPLE) {
return Iterators.emptyIterator();
}
return super.defaultIterator(node);
}
@Override
public Void visit(KeyValueColumnExpression expression) {
counter.increment(expression);
return null;
}
});
QualifierEncodingScheme encodingScheme = context.getCurrentTable().getTable().getEncodingScheme();
ImmutableStorageScheme storageScheme = context.getCurrentTable().getTable().getImmutableStorageScheme();
switch (counter.getCount()) {
case NONE:
PTable table = context.getResolver().getTables().get(0).getTable();
byte[] essentialCF = table.getType() == PTableType.VIEW
? ByteUtil.EMPTY_BYTE_ARRAY
: SchemaUtil.getEmptyColumnFamily(table);
filter = new RowKeyComparisonFilter(whereClause, essentialCF);
break;
case SINGLE:
filter = disambiguateWithFamily ? new SingleCFCQKeyValueComparisonFilter(whereClause) : new SingleCQKeyValueComparisonFilter(whereClause);
break;
case MULTIPLE:
filter = isPossibleToUseEncodedCQFilter(encodingScheme, storageScheme) ? new MultiEncodedCQKeyValueComparisonFilter(
whereClause, encodingScheme) : (disambiguateWithFamily ? new MultiCFCQKeyValueComparisonFilter(
whereClause) : new MultiCQKeyValueComparisonFilter(whereClause));
break;
}
scan.setFilter(filter);
}
ScanRanges scanRanges = context.getScanRanges();
if (scanRanges.useSkipScanFilter()) {
ScanUtil.andFilterAtBeginning(scan, scanRanges.getSkipScanFilter());
}
}
private static class SubqueryParseNodeVisitor extends StatelessTraverseAllParseNodeVisitor {
private final StatementContext context;
private final Set<SubqueryParseNode> subqueryNodes;
SubqueryParseNodeVisitor(StatementContext context, Set<SubqueryParseNode> subqueryNodes) {
this.context = context;
this.subqueryNodes = subqueryNodes;
}
@Override
public Void visit(SubqueryParseNode node) throws SQLException {
SelectStatement select = node.getSelectNode();
if (!context.isSubqueryResultAvailable(select)) {
this.subqueryNodes.add(node);
}
return null;
}
}
}