blob: 1743748c2f19004a033fa745f80b07a456beccdc [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.query.QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT;
import static org.apache.phoenix.schema.PTable.ImmutableStorageScheme.ONE_CELL_PER_COLUMN;
import static org.apache.phoenix.schema.PTable.QualifierEncodingScheme.NON_ENCODED_QUALIFIERS;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.exception.SQLExceptionInfo;
import org.apache.phoenix.expression.AndExpression;
import org.apache.phoenix.expression.CoerceExpression;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.expression.LiteralExpression;
import org.apache.phoenix.expression.function.MinAggregateFunction;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixStatement;
import org.apache.phoenix.parse.AliasedNode;
import org.apache.phoenix.parse.AndBooleanParseNodeVisitor;
import org.apache.phoenix.parse.AndParseNode;
import org.apache.phoenix.parse.AndRewriterBooleanParseNodeVisitor;
import org.apache.phoenix.parse.BindTableNode;
import org.apache.phoenix.parse.ColumnDef;
import org.apache.phoenix.parse.ColumnParseNode;
import org.apache.phoenix.parse.ComparisonParseNode;
import org.apache.phoenix.parse.ConcreteTableNode;
import org.apache.phoenix.parse.DerivedTableNode;
import org.apache.phoenix.parse.EqualParseNode;
import org.apache.phoenix.parse.HintNode.Hint;
import org.apache.phoenix.parse.JoinTableNode;
import org.apache.phoenix.parse.JoinTableNode.JoinType;
import org.apache.phoenix.parse.NamedTableNode;
import org.apache.phoenix.parse.OrderByNode;
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.TableName;
import org.apache.phoenix.parse.TableNode;
import org.apache.phoenix.parse.TableNodeVisitor;
import org.apache.phoenix.parse.TableWildcardParseNode;
import org.apache.phoenix.schema.ColumnNotFoundException;
import org.apache.phoenix.schema.ColumnRef;
import org.apache.phoenix.schema.LocalIndexDataColumnRef;
import org.apache.phoenix.schema.MetaDataEntityNotFoundException;
import org.apache.phoenix.schema.PColumn;
import org.apache.phoenix.schema.PNameFactory;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTable.IndexType;
import org.apache.phoenix.schema.PTableImpl;
import org.apache.phoenix.schema.PTableType;
import org.apache.phoenix.schema.ProjectedColumn;
import org.apache.phoenix.schema.SortOrder;
import org.apache.phoenix.schema.TableRef;
import org.apache.phoenix.schema.types.PBoolean;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.schema.types.PDate;
import org.apache.phoenix.schema.types.PDecimal;
import org.apache.phoenix.schema.types.PDouble;
import org.apache.phoenix.schema.types.PInteger;
import org.apache.phoenix.schema.types.PLong;
import org.apache.phoenix.schema.types.PSmallint;
import org.apache.phoenix.schema.types.PTimestamp;
import org.apache.phoenix.schema.types.PTinyint;
import org.apache.phoenix.schema.types.PVarbinary;
import org.apache.phoenix.schema.types.PVarchar;
import org.apache.phoenix.util.EncodedColumnsUtil;
import org.apache.phoenix.util.IndexUtil;
import org.apache.phoenix.util.ParseNodeUtil;
import org.apache.phoenix.util.SchemaUtil;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
public class JoinCompiler {
public enum Strategy {
HASH_BUILD_LEFT,
HASH_BUILD_RIGHT,
SORT_MERGE,
}
public enum ColumnRefType {
JOINLOCAL,
GENERAL,
}
private final PhoenixStatement statement;
private final SelectStatement select;
private final ColumnResolver origResolver;
private final boolean useStarJoin;
private final Map<ColumnRef, ColumnRefType> columnRefs;
private final Map<ColumnRef, ColumnParseNode> columnNodes;
private final boolean useSortMergeJoin;
private JoinCompiler(PhoenixStatement statement, SelectStatement select, ColumnResolver resolver) {
this.statement = statement;
this.select = select;
this.origResolver = resolver;
this.useStarJoin = !select.getHint().hasHint(Hint.NO_STAR_JOIN);
this.columnRefs = new HashMap<ColumnRef, ColumnRefType>();
this.columnNodes = new HashMap<ColumnRef, ColumnParseNode>();
this.useSortMergeJoin = select.getHint().hasHint(Hint.USE_SORT_MERGE_JOIN);
}
/**
* After this method is called, the inner state of the parameter resolver may be changed by
* {@link FromCompiler#refreshDerivedTableNode} because of some sql optimization,
* see also {@link Table#pruneSubselectAliasedNodes()}.
* @param statement
* @param select
* @param resolver
* @return
* @throws SQLException
*/
public static JoinTable compile(PhoenixStatement statement, SelectStatement select, ColumnResolver resolver) throws SQLException {
JoinCompiler compiler = new JoinCompiler(statement, select, resolver);
JoinTableConstructor constructor = compiler.new JoinTableConstructor();
Pair<Table, List<JoinSpec>> res = select.getFrom().accept(constructor);
JoinTable joinTable = res.getSecond() == null ? compiler.new JoinTable(res.getFirst()) : compiler.new JoinTable(res.getFirst(), res.getSecond());
if (select.getWhere() != null) {
joinTable.pushDownFilter(select.getWhere());
}
ColumnRefParseNodeVisitor generalRefVisitor = new ColumnRefParseNodeVisitor(resolver, statement.getConnection());
ColumnRefParseNodeVisitor joinLocalRefVisitor = new ColumnRefParseNodeVisitor(resolver, statement.getConnection());
joinTable.pushDownColumnRefVisitors(generalRefVisitor, joinLocalRefVisitor);
ParseNodeUtil.applyParseNodeVisitor(select, generalRefVisitor, false);
compiler.columnNodes.putAll(joinLocalRefVisitor.getColumnRefMap());
compiler.columnNodes.putAll(generalRefVisitor.getColumnRefMap());
for (ColumnRef ref : generalRefVisitor.getColumnRefMap().keySet()) {
compiler.columnRefs.put(ref, ColumnRefType.GENERAL);
}
for (ColumnRef ref : joinLocalRefVisitor.getColumnRefMap().keySet()) {
if (!compiler.columnRefs.containsKey(ref))
compiler.columnRefs.put(ref, ColumnRefType.JOINLOCAL);
}
/**
* After {@link ColumnRefParseNodeVisitor} is pushed down,
* pruning columns for each {@link JoinCompiler.Table} if
* {@link @link JoinCompiler.Table#isSubselect()}.
*/
joinTable.pruneSubselectAliasedNodes();
return joinTable;
}
private class JoinTableConstructor implements TableNodeVisitor<Pair<Table, List<JoinSpec>>> {
private TableRef resolveTable(String alias, TableName name) throws SQLException {
if (alias != null)
return origResolver.resolveTable(null, alias);
return origResolver.resolveTable(name.getSchemaName(), name.getTableName());
}
@Override
public Pair<Table, List<JoinSpec>> visit(BindTableNode boundTableNode) throws SQLException {
TableRef tableRef = resolveTable(boundTableNode.getAlias(), boundTableNode.getName());
boolean isWildCard = isWildCardSelectForTable(select.getSelect(), tableRef, origResolver);
Table table = new Table(boundTableNode, isWildCard, Collections.<ColumnDef>emptyList(), boundTableNode.getTableSamplingRate(), tableRef);
return new Pair<Table, List<JoinSpec>>(table, null);
}
@Override
public Pair<Table, List<JoinSpec>> visit(JoinTableNode joinNode) throws SQLException {
Pair<Table, List<JoinSpec>> lhs = joinNode.getLHS().accept(this);
Pair<Table, List<JoinSpec>> rhs = joinNode.getRHS().accept(this);
JoinTable joinTable = rhs.getSecond() == null ? new JoinTable(rhs.getFirst()) : new JoinTable(rhs.getFirst(), rhs.getSecond());
List<JoinSpec> joinSpecs = lhs.getSecond();
if (joinSpecs == null) {
joinSpecs = new ArrayList<JoinSpec>();
}
joinSpecs.add(new JoinSpec(joinNode.getType(), joinNode.getOnNode(), joinTable, joinNode.isSingleValueOnly(), origResolver));
return new Pair<Table, List<JoinSpec>>(lhs.getFirst(), joinSpecs);
}
@Override
public Pair<Table, List<JoinSpec>> visit(NamedTableNode namedTableNode)
throws SQLException {
TableRef tableRef = resolveTable(namedTableNode.getAlias(), namedTableNode.getName());
boolean isWildCard = isWildCardSelectForTable(select.getSelect(), tableRef, origResolver);
Table table = new Table(namedTableNode, isWildCard, namedTableNode.getDynamicColumns(), namedTableNode.getTableSamplingRate(), tableRef);
return new Pair<Table, List<JoinSpec>>(table, null);
}
@Override
public Pair<Table, List<JoinSpec>> visit(DerivedTableNode subselectNode)
throws SQLException {
TableRef tableRef = resolveTable(subselectNode.getAlias(), null);
boolean isWildCard = isWildCardSelectForTable(select.getSelect(), tableRef, origResolver);
Table table = new Table(subselectNode, isWildCard, tableRef);
return new Pair<Table, List<JoinSpec>>(table, null);
}
}
public class JoinTable {
private final Table leftTable;
private final List<JoinSpec> joinSpecs;
private List<ParseNode> postFilters;
private final List<Table> allTables;
private final List<TableRef> allTableRefs;
private final boolean allLeftJoin;
private final boolean isPrefilterAccepted;
private final List<JoinSpec> prefilterAcceptedTables;
private JoinTable(Table table) {
this.leftTable = table;
this.joinSpecs = Collections.<JoinSpec>emptyList();
this.postFilters = Collections.EMPTY_LIST;
this.allTables = Collections.<Table>singletonList(table);
this.allTableRefs = Collections.<TableRef>singletonList(table.getTableRef());
this.allLeftJoin = false;
this.isPrefilterAccepted = true;
this.prefilterAcceptedTables = Collections.<JoinSpec>emptyList();
}
private JoinTable(Table table, List<JoinSpec> joinSpecs) {
this.leftTable = table;
this.joinSpecs = joinSpecs;
this.postFilters = new ArrayList<ParseNode>();
this.allTables = new ArrayList<Table>();
this.allTableRefs = new ArrayList<TableRef>();
this.allTables.add(table);
boolean allLeftJoin = true;
int lastRightJoinIndex = -1;
boolean hasFullJoin = false;
for (int i = 0; i < joinSpecs.size(); i++) {
JoinSpec joinSpec = joinSpecs.get(i);
this.allTables.addAll(joinSpec.getRhsJoinTable().getAllTables());
allLeftJoin = allLeftJoin && joinSpec.getType() == JoinType.Left;
hasFullJoin = hasFullJoin || joinSpec.getType() == JoinType.Full;
if (joinSpec.getType() == JoinType.Right) {
lastRightJoinIndex = i;
}
}
for (Table t : this.allTables) {
this.allTableRefs.add(t.getTableRef());
}
this.allLeftJoin = allLeftJoin;
this.isPrefilterAccepted = !hasFullJoin && lastRightJoinIndex == -1;
this.prefilterAcceptedTables = new ArrayList<JoinSpec>();
for (int i = lastRightJoinIndex == -1 ? 0 : lastRightJoinIndex; i < joinSpecs.size(); i++) {
JoinSpec joinSpec = joinSpecs.get(i);
if (joinSpec.getType() != JoinType.Left && joinSpec.getType() != JoinType.Anti && joinSpec.getType() != JoinType.Full) {
prefilterAcceptedTables.add(joinSpec);
}
}
}
public Table getLeftTable() {
return leftTable;
}
public List<JoinSpec> getJoinSpecs() {
return joinSpecs;
}
public List<Table> getAllTables() {
return allTables;
}
public List<TableRef> getAllTableRefs() {
return allTableRefs;
}
public List<TableRef> getLeftTableRef() {
return Collections.<TableRef>singletonList(leftTable.getTableRef());
}
public boolean isAllLeftJoin() {
return allLeftJoin;
}
public SelectStatement getStatement() {
return select;
}
public ColumnResolver getOriginalResolver() {
return origResolver;
}
public Map<ColumnRef, ColumnRefType> getColumnRefs() {
return columnRefs;
}
public ParseNode getPostFiltersCombined() {
return combine(postFilters);
}
public void addPostJoinFilter(ParseNode parseNode) {
if(this.postFilters == Collections.EMPTY_LIST) {
this.postFilters = new ArrayList<ParseNode>();
}
this.postFilters.add(parseNode);
}
public void addLeftTableFilter(ParseNode parseNode) throws SQLException {
if (isPrefilterAccepted) {
leftTable.addFilter(parseNode);
} else {
addPostJoinFilter(parseNode);
}
}
public List<JoinSpec> getPrefilterAcceptedJoinSpecs() {
return this.prefilterAcceptedTables;
}
/**
* try to decompose filter and push down to single table.
* @param filter
* @throws SQLException
*/
public void pushDownFilter(ParseNode filter) throws SQLException {
if (joinSpecs.isEmpty()) {
leftTable.addFilter(filter);
return;
}
WhereNodeVisitor visitor = new WhereNodeVisitor(
origResolver,
this,
statement.getConnection());
filter.accept(visitor);
}
public void pushDownColumnRefVisitors(
ColumnRefParseNodeVisitor generalRefVisitor,
ColumnRefParseNodeVisitor joinLocalRefVisitor) throws SQLException {
for (ParseNode node : leftTable.getPostFilters()) {
node.accept(generalRefVisitor);
}
for (ParseNode node : postFilters) {
node.accept(generalRefVisitor);
}
for (JoinSpec joinSpec : joinSpecs) {
JoinTable joinTable = joinSpec.getRhsJoinTable();
boolean hasSubJoin = !joinTable.getJoinSpecs().isEmpty();
for (EqualParseNode node : joinSpec.getOnConditions()) {
node.getLHS().accept(generalRefVisitor);
if (hasSubJoin) {
node.getRHS().accept(generalRefVisitor);
} else {
node.getRHS().accept(joinLocalRefVisitor);
}
}
joinTable.pushDownColumnRefVisitors(generalRefVisitor, joinLocalRefVisitor);
}
}
/**
* Pruning columns for each {@link JoinCompiler.Table} if
* {@link @link JoinCompiler.Table#isSubselect()}.
* @throws SQLException
*/
public void pruneSubselectAliasedNodes() throws SQLException {
this.leftTable.pruneSubselectAliasedNodes();
for (JoinSpec joinSpec : joinSpecs) {
JoinTable rhsJoinTablesContext = joinSpec.getRhsJoinTable();;
rhsJoinTablesContext.pruneSubselectAliasedNodes();
}
}
public Expression compilePostFilterExpression(StatementContext context) throws SQLException {
List<ParseNode> filtersCombined = Lists.<ParseNode> newArrayList(postFilters);
return JoinCompiler.compilePostFilterExpression(context, filtersCombined);
}
/**
* Return a list of all applicable join strategies. The order of the strategies in the
* returned list is based on the static rule below. However, the caller can decide on
* an optimal join strategy by evaluating and comparing the costs.
* 1. If hint USE_SORT_MERGE_JOIN is specified,
* return a singleton list containing only SORT_MERGE.
* 2. If 1) matches pattern "A LEFT/INNER/SEMI/ANTI JOIN B"; or
* 2) matches pattern "A LEFT/INNER/SEMI/ANTI JOIN B (LEFT/INNER/SEMI/ANTI JOIN C)+"
* and hint NO_STAR_JOIN is not specified,
* add BUILD_RIGHT to the returned list.
* 3. If matches pattern "A RIGHT/INNER JOIN B", where B is either a named table reference
* or a flat sub-query,
* add BUILD_LEFT to the returned list.
* 4. add SORT_MERGE to the returned list.
*/
public List<Strategy> getApplicableJoinStrategies() {
List<Strategy> strategies = Lists.newArrayList();
if (useSortMergeJoin) {
strategies.add(Strategy.SORT_MERGE);
} else {
if (getStarJoinVector() != null) {
strategies.add(Strategy.HASH_BUILD_RIGHT);
}
JoinSpec lastJoinSpec = joinSpecs.get(joinSpecs.size() - 1);
JoinType type = lastJoinSpec.getType();
if ((type == JoinType.Right || type == JoinType.Inner)
&& lastJoinSpec.getRhsJoinTable().getJoinSpecs().isEmpty()
&& lastJoinSpec.getRhsJoinTable().getLeftTable().isFlat()) {
strategies.add(Strategy.HASH_BUILD_LEFT);
}
strategies.add(Strategy.SORT_MERGE);
}
return strategies;
}
/**
* Returns a boolean vector indicating whether the evaluation of join expressions
* can be evaluated at an early stage if the input JoinSpec can be taken as a
* star join. Otherwise returns null.
* @return a boolean vector for a star join; or null for non star join.
*/
public boolean[] getStarJoinVector() {
int count = joinSpecs.size();
if (!leftTable.isFlat() ||
(!useStarJoin
&& count > 1
&& joinSpecs.get(count - 1).getType() != JoinType.Left
&& joinSpecs.get(count - 1).getType() != JoinType.Semi
&& joinSpecs.get(count - 1).getType() != JoinType.Anti
&& !joinSpecs.get(count - 1).isSingleValueOnly()))
return null;
boolean[] vector = new boolean[count];
for (int i = 0; i < count; i++) {
JoinSpec joinSpec = joinSpecs.get(i);
if (joinSpec.getType() != JoinType.Left
&& joinSpec.getType() != JoinType.Inner
&& joinSpec.getType() != JoinType.Semi
&& joinSpec.getType() != JoinType.Anti)
return null;
vector[i] = true;
Iterator<TableRef> iter = joinSpec.getDependentTableRefs().iterator();
while (vector[i] == true && iter.hasNext()) {
TableRef tableRef = iter.next();
if (!tableRef.equals(leftTable.getTableRef())) {
vector[i] = false;
}
}
}
return vector;
}
/**
* create a new {@link JoinTable} exclude the last {@link JoinSpec},
* and try to push {@link #postFilters} to the new {@link JoinTable}.
* @param phoenixConnection
* @return
* @throws SQLException
*/
public JoinTable createSubJoinTable(
PhoenixConnection phoenixConnection) throws SQLException {
assert joinSpecs.size() > 0;
JoinTable newJoinTablesContext = joinSpecs.size() > 1 ?
new JoinTable(leftTable, joinSpecs.subList(0, joinSpecs.size() - 1)) :
new JoinTable(leftTable);
JoinType rightmostJoinType = joinSpecs.get(joinSpecs.size() - 1).getType();
if(rightmostJoinType == JoinType.Right || rightmostJoinType == JoinType.Full) {
return newJoinTablesContext;
}
if(this.postFilters.isEmpty()) {
return newJoinTablesContext;
}
PushDownPostFilterParseNodeVisitor pushDownPostFilterNodeVistor =
new PushDownPostFilterParseNodeVisitor(
JoinCompiler.this.origResolver,
newJoinTablesContext,
phoenixConnection);
int index = 0;
List<ParseNode> newPostFilterParseNodes = null;
for(ParseNode postFilterParseNode : this.postFilters) {
ParseNode newPostFilterParseNode =
postFilterParseNode.accept(pushDownPostFilterNodeVistor);
if(newPostFilterParseNode != postFilterParseNode &&
newPostFilterParseNodes == null) {
newPostFilterParseNodes =
new ArrayList<ParseNode>(this.postFilters.subList(0, index));
}
if(newPostFilterParseNodes != null && newPostFilterParseNode != null) {
newPostFilterParseNodes.add(newPostFilterParseNode);
}
index++;
}
if(newPostFilterParseNodes != null) {
this.postFilters = newPostFilterParseNodes;
}
return newJoinTablesContext;
}
public SelectStatement getAsSingleSubquery(SelectStatement query, boolean asSubquery) throws SQLException {
assert (isFlat(query));
if (asSubquery)
return query;
return NODE_FACTORY.select(select, query.getFrom(), query.getWhere());
}
public boolean hasPostReference() {
for (Table table : allTables) {
if (table.isWildCardSelect()) {
return true;
}
}
for (Map.Entry<ColumnRef, ColumnRefType> e : columnRefs.entrySet()) {
if (e.getValue() == ColumnRefType.GENERAL &&
allTableRefs.contains(e.getKey().getTableRef())) {
return true;
}
}
return false;
}
public boolean hasFilters() {
if (!postFilters.isEmpty())
return true;
if (isPrefilterAccepted && leftTable.hasFilters())
return true;
for (JoinSpec joinSpec : prefilterAcceptedTables) {
if (joinSpec.getRhsJoinTable().hasFilters())
return true;
}
return false;
}
}
public class JoinSpec {
private final JoinType type;
private final List<EqualParseNode> onConditions;
private final JoinTable rhsJoinTable;
private final boolean singleValueOnly;
private Set<TableRef> dependentTableRefs;
private OnNodeVisitor onNodeVisitor;
private JoinSpec(JoinType type, ParseNode onNode, JoinTable joinTable,
boolean singleValueOnly, ColumnResolver resolver) throws SQLException {
this.type = type;
this.onConditions = new ArrayList<EqualParseNode>();
this.rhsJoinTable = joinTable;
this.singleValueOnly = singleValueOnly;
this.dependentTableRefs = new HashSet<TableRef>();
this.onNodeVisitor = new OnNodeVisitor(resolver, this, statement.getConnection());
if (onNode != null) {
this.pushDownOnCondition(onNode);
}
}
/**
* <pre>
* 1.in {@link JoinSpec} ctor,try to push the filter in join on clause to where clause,
* eg. for "a join b on a.id = b.id and b.code = 1 where a.name is not null", try to
* push "b.code =1" in join on clause to where clause.
* 2.in{@link WhereNodeVisitor#visitLeave(ComparisonParseNode, List)}, for inner join,
* try to push the join on condition in where clause to join on clause,
* eg. for "a join b on a.id = b.id where a.name = b.name", try to push "a.name=b.name"
* in where clause to join on clause.
* </pre>
* @param node
* @throws SQLException
*/
public void pushDownOnCondition(ParseNode node) throws SQLException {
node.accept(onNodeVisitor);
}
public JoinType getType() {
return type;
}
public List<EqualParseNode> getOnConditions() {
return onConditions;
}
public JoinTable getRhsJoinTable() {
return rhsJoinTable;
}
public List<TableRef> getRhsJoinTableRefs() {
return this.rhsJoinTable.getAllTableRefs();
}
public void pushDownFilterToRhsJoinTable(ParseNode parseNode) throws SQLException {
this.rhsJoinTable.pushDownFilter(parseNode);
}
public void addOnCondition(EqualParseNode equalParseNode) {
this.onConditions.add(equalParseNode);
}
public void addDependentTableRefs(Collection<TableRef> tableRefs) {
this.dependentTableRefs.addAll(tableRefs);
}
public boolean isSingleValueOnly() {
return singleValueOnly;
}
public Set<TableRef> getDependentTableRefs() {
return dependentTableRefs;
}
public Pair<List<Expression>, List<Expression>> compileJoinConditions(StatementContext lhsCtx, StatementContext rhsCtx, Strategy strategy) throws SQLException {
if (onConditions.isEmpty()) {
return new Pair<List<Expression>, List<Expression>>(
Collections.<Expression> singletonList(LiteralExpression.newConstant(1)),
Collections.<Expression> singletonList(LiteralExpression.newConstant(1)));
}
List<Pair<Expression, Expression>> compiled = Lists.<Pair<Expression, Expression>> newArrayListWithExpectedSize(onConditions.size());
ExpressionCompiler lhsCompiler = new ExpressionCompiler(lhsCtx);
ExpressionCompiler rhsCompiler = new ExpressionCompiler(rhsCtx);
for (EqualParseNode condition : onConditions) {
lhsCompiler.reset();
Expression left = condition.getLHS().accept(lhsCompiler);
rhsCompiler.reset();
Expression right = condition.getRHS().accept(rhsCompiler);
PDataType toType = getCommonType(left.getDataType(), right.getDataType());
SortOrder toSortOrder = strategy == Strategy.SORT_MERGE ? SortOrder.ASC : (strategy == Strategy.HASH_BUILD_LEFT ? right.getSortOrder() : left.getSortOrder());
if (left.getDataType() != toType || left.getSortOrder() != toSortOrder) {
left = CoerceExpression.create(left, toType, toSortOrder, left.getMaxLength());
}
if (right.getDataType() != toType || right.getSortOrder() != toSortOrder) {
right = CoerceExpression.create(right, toType, toSortOrder, right.getMaxLength());
}
compiled.add(new Pair<Expression, Expression>(left, right));
}
// TODO PHOENIX-4618:
// For Stategy.SORT_MERGE, we probably need to re-order the join keys based on the
// specific ordering required by the join's parent, or re-order the following way
// to align with group-by expressions' re-ordering.
if (strategy != Strategy.SORT_MERGE) {
Collections.sort(compiled, new Comparator<Pair<Expression, Expression>>() {
@Override
public int compare(Pair<Expression, Expression> o1, Pair<Expression, Expression> o2) {
Expression e1 = o1.getFirst();
Expression e2 = o2.getFirst();
boolean isFixed1 = e1.getDataType().isFixedWidth();
boolean isFixed2 = e2.getDataType().isFixedWidth();
boolean isFixedNullable1 = e1.isNullable() &&isFixed1;
boolean isFixedNullable2 = e2.isNullable() && isFixed2;
if (isFixedNullable1 == isFixedNullable2) {
if (isFixed1 == isFixed2) {
return 0;
} else if (isFixed1) {
return -1;
} else {
return 1;
}
} else if (isFixedNullable1) {
return 1;
} else {
return -1;
}
}
});
}
List<Expression> lConditions = Lists.<Expression> newArrayListWithExpectedSize(compiled.size());
List<Expression> rConditions = Lists.<Expression> newArrayListWithExpectedSize(compiled.size());
for (Pair<Expression, Expression> pair : compiled) {
lConditions.add(pair.getFirst());
rConditions.add(pair.getSecond());
}
return new Pair<List<Expression>, List<Expression>>(lConditions, rConditions);
}
private PDataType getCommonType(PDataType lType, PDataType rType) throws SQLException {
if (lType == rType)
return lType;
if (!lType.isComparableTo(rType))
throw new SQLExceptionInfo.Builder(SQLExceptionCode.TYPE_MISMATCH)
.setMessage("On-clause LHS expression and RHS expression must be comparable. LHS type: " + lType + ", RHS type: " + rType)
.build().buildException();
if ((lType == null || lType.isCoercibleTo(PTinyint.INSTANCE))
&& (rType == null || rType.isCoercibleTo(PTinyint.INSTANCE))) {
return lType == null ? rType : lType; // to preserve UNSIGNED type
}
if ((lType == null || lType.isCoercibleTo(PSmallint.INSTANCE))
&& (rType == null || rType.isCoercibleTo(PSmallint.INSTANCE))) {
return lType == null ? rType : lType; // to preserve UNSIGNED type
}
if ((lType == null || lType.isCoercibleTo(PInteger.INSTANCE))
&& (rType == null || rType.isCoercibleTo(PInteger.INSTANCE))) {
return lType == null ? rType : lType; // to preserve UNSIGNED type
}
if ((lType == null || lType.isCoercibleTo(PLong.INSTANCE))
&& (rType == null || rType.isCoercibleTo(PLong.INSTANCE))) {
return lType == null ? rType : lType; // to preserve UNSIGNED type
}
if ((lType == null || lType.isCoercibleTo(PDouble.INSTANCE))
&& (rType == null || rType.isCoercibleTo(PDouble.INSTANCE))) {
return lType == null ? rType : lType; // to preserve UNSIGNED type
}
if ((lType == null || lType.isCoercibleTo(PDecimal.INSTANCE))
&& (rType == null || rType.isCoercibleTo(PDecimal.INSTANCE))) {
return PDecimal.INSTANCE;
}
if ((lType == null || lType.isCoercibleTo(PDate.INSTANCE))
&& (rType == null || rType.isCoercibleTo(PDate.INSTANCE))) {
return lType == null ? rType : lType;
}
if ((lType == null || lType.isCoercibleTo(PTimestamp.INSTANCE))
&& (rType == null || rType.isCoercibleTo(PTimestamp.INSTANCE))) {
return lType == null ? rType : lType;
}
if ((lType == null || lType.isCoercibleTo(PVarchar.INSTANCE))
&& (rType == null || rType.isCoercibleTo(PVarchar.INSTANCE))) {
return PVarchar.INSTANCE;
}
if ((lType == null || lType.isCoercibleTo(PBoolean.INSTANCE))
&& (rType == null || rType.isCoercibleTo(PBoolean.INSTANCE))) {
return PBoolean.INSTANCE;
}
return PVarbinary.INSTANCE;
}
}
public class Table {
private TableNode tableNode;
private final boolean isWildcard;
private final List<ColumnDef> dynamicColumns;
private final Double tableSamplingRate;
private SelectStatement subselect;
private TableRef tableRef;
private final List<ParseNode> preFilters;
private final List<ParseNode> postFilters;
private final boolean filterCanPushDownToSubselect;
private Table(TableNode tableNode, boolean isWildcard, List<ColumnDef> dynamicColumns,
Double tableSamplingRate, TableRef tableRef) {
this.tableNode = tableNode;
this.isWildcard = isWildcard;
this.dynamicColumns = dynamicColumns;
this.tableSamplingRate=tableSamplingRate;
this.subselect = null;
this.tableRef = tableRef;
this.preFilters = new ArrayList<ParseNode>();
this.postFilters = Collections.<ParseNode>emptyList();
this.filterCanPushDownToSubselect = false;
}
private Table(DerivedTableNode tableNode, boolean isWildcard, TableRef tableRef) throws SQLException {
this.tableNode = tableNode;
this.isWildcard = isWildcard;
this.dynamicColumns = Collections.<ColumnDef>emptyList();
this.tableSamplingRate=ConcreteTableNode.DEFAULT_TABLE_SAMPLING_RATE;
this.subselect = SubselectRewriter.flatten(tableNode.getSelect(), statement.getConnection());
this.tableRef = tableRef;
this.preFilters = new ArrayList<ParseNode>();
this.postFilters = new ArrayList<ParseNode>();
this.filterCanPushDownToSubselect = SubselectRewriter.isFilterCanPushDownToSelect(subselect);
}
public TableNode getTableNode() {
return tableNode;
}
public List<ColumnDef> getDynamicColumns() {
return dynamicColumns;
}
public Double getTableSamplingRate() {
return tableSamplingRate;
}
public boolean isSubselect() {
return subselect != null;
}
public SelectStatement getSubselect() {
return this.subselect;
}
/**
* Pruning columns if {@link #isSubselect()}.
* Note: If some columns are pruned, the {@link JoinCompiler#origResolver} should be refreshed.
* @throws SQLException
*/
public void pruneSubselectAliasedNodes() throws SQLException {
if(!this.isSubselect()) {
return;
}
Set<String> referencedColumnNames = this.getReferencedColumnNames();
SelectStatement newSubselectStatement =
SubselectRewriter.pruneSelectAliasedNodes(
this.subselect,
referencedColumnNames,
statement.getConnection());
if(!newSubselectStatement.getSelect().equals(this.subselect.getSelect())) {
/**
* The columns are pruned, so {@link ColumnResolver} should be refreshed.
*/
DerivedTableNode newDerivedTableNode =
NODE_FACTORY.derivedTable(this.tableNode.getAlias(), newSubselectStatement);
TableRef newTableRef =
FromCompiler.refreshDerivedTableNode(origResolver, newDerivedTableNode);
assert newTableRef != null;
this.subselect = newSubselectStatement;
this.tableRef = newTableRef;
this.tableNode = newDerivedTableNode;
}
}
/**
* Collect the referenced columns of this {@link Table}
* according to {@link JoinCompiler#columnNodes}.
* @return
* @throws SQLException
*/
private Set<String> getReferencedColumnNames() throws SQLException {
assert(this.isSubselect());
if (isWildCardSelect()) {
return null;
}
Set<String> referencedColumnNames = new HashSet<String>();
for (Map.Entry<ColumnRef, ColumnParseNode> entry : columnNodes.entrySet()) {
if (tableRef.equals(entry.getKey().getTableRef())) {
ColumnParseNode columnParseNode = entry.getValue();
String normalizedColumnName = SchemaUtil.getNormalizedColumnName(columnParseNode);
referencedColumnNames.add(normalizedColumnName);
}
}
return referencedColumnNames;
}
/**
* Returns all the basic select nodes, no aggregation.
*/
public List<AliasedNode> getSelectNodes() {
if (isWildCardSelect()) {
return Collections.singletonList(NODE_FACTORY.aliasedNode(null, NODE_FACTORY.wildcard()));
}
List<AliasedNode> ret = new ArrayList<AliasedNode>();
for (Map.Entry<ColumnRef, ColumnParseNode> entry : columnNodes.entrySet()) {
if (tableRef.equals(entry.getKey().getTableRef())) {
ret.add(NODE_FACTORY.aliasedNode(null, entry.getValue()));
}
}
if (ret.isEmpty()) {
ret.add(NODE_FACTORY.aliasedNode(null, NODE_FACTORY.literal(1)));
}
return ret;
}
public List<ParseNode> getPreFilters() {
return preFilters;
}
public List<ParseNode> getPostFilters() {
return postFilters;
}
public TableRef getTableRef() {
return tableRef;
}
public void addFilter(ParseNode filter) throws SQLException {
if (!isSubselect() || filterCanPushDownToSubselect) {
this.addPreFilter(filter);
} else {
postFilters.add(filter);
}
}
/**
* If {@link #isSubselect()}, preFilterParseNode is at first rewritten by
* {@link SubselectRewriter#rewritePreFilterForSubselect}
* @param preFilterParseNode
* @throws SQLException
*/
private void addPreFilter(ParseNode preFilterParseNode) throws SQLException {
if(this.isSubselect()) {
preFilterParseNode =
SubselectRewriter.rewritePreFilterForSubselect(
preFilterParseNode,
this.subselect,
tableNode.getAlias());
}
preFilters.add(preFilterParseNode);
}
public ParseNode getPreFiltersCombined() {
return combine(preFilters);
}
public SelectStatement getAsSubquery(List<OrderByNode> orderBy) throws SQLException {
if (isSubselect()) {
return SubselectRewriter.applyOrderByAndPostFilters(
SubselectRewriter.applyPreFiltersForSubselect(subselect, preFilters, tableNode.getAlias()),
orderBy,
tableNode.getAlias(),
postFilters);
}
//for flat table, postFilters is empty , because it can safely pushed down as preFilters.
assert postFilters == null || postFilters.isEmpty();
return NODE_FACTORY.select(tableNode, select.getHint(), false, getSelectNodes(), getPreFiltersCombined(), null,
null, orderBy, null, null, 0, false, select.hasSequence(),
Collections.<SelectStatement> emptyList(), select.getUdfParseNodes());
}
public SelectStatement getAsSubqueryForOptimization(boolean applyGroupByOrOrderBy) throws SQLException {
assert (!isSubselect());
SelectStatement query = getAsSubquery(null);
if (!applyGroupByOrOrderBy)
return query;
boolean addGroupBy = false;
boolean addOrderBy = false;
if (select.getGroupBy() != null && !select.getGroupBy().isEmpty()) {
ColumnRefParseNodeVisitor groupByVisitor = new ColumnRefParseNodeVisitor(origResolver, statement.getConnection());
for (ParseNode node : select.getGroupBy()) {
node.accept(groupByVisitor);
}
Set<TableRef> set = groupByVisitor.getTableRefSet();
if (set.size() == 1 && tableRef.equals(set.iterator().next())) {
addGroupBy = true;
}
} else if (select.getOrderBy() != null && !select.getOrderBy().isEmpty()) {
ColumnRefParseNodeVisitor orderByVisitor = new ColumnRefParseNodeVisitor(origResolver, statement.getConnection());
for (OrderByNode node : select.getOrderBy()) {
node.getNode().accept(orderByVisitor);
}
Set<TableRef> set = orderByVisitor.getTableRefSet();
if (set.size() == 1 && tableRef.equals(set.iterator().next())) {
addOrderBy = true;
}
}
if (!addGroupBy && !addOrderBy)
return query;
List<AliasedNode> selectList = query.getSelect();
if (addGroupBy) {
assert (!isWildCardSelect());
selectList = new ArrayList<AliasedNode>(query.getSelect().size());
for (AliasedNode aliasedNode : query.getSelect()) {
ParseNode node = NODE_FACTORY.function(
MinAggregateFunction.NAME, Collections.singletonList(aliasedNode.getNode()));
selectList.add(NODE_FACTORY.aliasedNode(null, node));
}
}
return NODE_FACTORY.select(query.getFrom(), query.getHint(), query.isDistinct(), selectList,
query.getWhere(), addGroupBy ? select.getGroupBy() : query.getGroupBy(),
addGroupBy ? null : query.getHaving(), addOrderBy ? select.getOrderBy() : query.getOrderBy(),
query.getLimit(), query.getOffset(), query.getBindCount(), addGroupBy, query.hasSequence(),
query.getSelects(), query.getUdfParseNodes());
}
public boolean hasFilters() {
return isSubselect() ? (!postFilters.isEmpty() || subselect.getWhere() != null || subselect.getHaving() != null) : !preFilters.isEmpty();
}
public boolean isFlat() {
return subselect == null || JoinCompiler.isFlat(subselect);
}
protected boolean isWildCardSelect() {
return isWildcard;
}
public void projectColumns(Scan scan) {
assert(!isSubselect());
if (isWildCardSelect()) {
scan.getFamilyMap().clear();
return;
}
for (ColumnRef columnRef : columnRefs.keySet()) {
if (columnRef.getTableRef().equals(tableRef)
&& !SchemaUtil.isPKColumn(columnRef.getColumn())
&& !(columnRef instanceof LocalIndexColumnRef)) {
EncodedColumnsUtil.setColumns(columnRef.getColumn(), tableRef.getTable(), scan);
}
}
}
public PTable createProjectedTable(boolean retainPKColumns, StatementContext context) throws SQLException {
assert(!isSubselect());
List<ColumnRef> sourceColumns = new ArrayList<ColumnRef>();
PTable table = tableRef.getTable();
if (retainPKColumns) {
for (PColumn column : table.getPKColumns()) {
sourceColumns.add(new ColumnRef(tableRef, column.getPosition()));
}
}
if (isWildCardSelect()) {
for (PColumn column : table.getColumns()) {
if (!retainPKColumns || !SchemaUtil.isPKColumn(column)) {
sourceColumns.add(new ColumnRef(tableRef, column.getPosition()));
}
}
} else {
for (Map.Entry<ColumnRef, ColumnRefType> e : columnRefs.entrySet()) {
ColumnRef columnRef = e.getKey();
if (columnRef.getTableRef().equals(tableRef)
&& (!retainPKColumns || !SchemaUtil.isPKColumn(columnRef.getColumn()))) {
if (columnRef instanceof LocalIndexColumnRef) {
sourceColumns.add(new LocalIndexDataColumnRef(context, tableRef, IndexUtil.getIndexColumnName(columnRef.getColumn())));
} else {
sourceColumns.add(columnRef);
}
}
}
}
return TupleProjectionCompiler.createProjectedTable(tableRef, sourceColumns, retainPKColumns);
}
public PTable createProjectedTable(RowProjector rowProjector) throws SQLException {
assert(isSubselect());
TableRef tableRef = FromCompiler.getResolverForCompiledDerivedTable(statement.getConnection(), this.tableRef, rowProjector).getTables().get(0);
List<ColumnRef> sourceColumns = new ArrayList<ColumnRef>();
PTable table = tableRef.getTable();
for (PColumn column : table.getColumns()) {
sourceColumns.add(new ColumnRef(tableRef, column.getPosition()));
}
return TupleProjectionCompiler.createProjectedTable(tableRef, sourceColumns, false);
}
}
/**
* Push down {@link JoinTable#postFilters} of Outermost-JoinTable to
* {@link JoinTable#postFilters} of Sub-JoinTable
*/
private static class PushDownPostFilterParseNodeVisitor extends AndRewriterBooleanParseNodeVisitor {
private ColumnRefParseNodeVisitor columnRefParseNodeVisitor;
/**
* Sub-JoinTable to accept pushed down PostFilters.
*/
private JoinTable joinTable;
public PushDownPostFilterParseNodeVisitor(
ColumnResolver resolver,
JoinTable joinTablesContext,
PhoenixConnection connection) {
super(NODE_FACTORY);
this.joinTable = joinTablesContext;
this.columnRefParseNodeVisitor = new ColumnRefParseNodeVisitor(resolver, connection);
}
@Override
protected ParseNode leaveBooleanNode(
ParseNode parentParseNode, List<ParseNode> childParseNodes) throws SQLException {
columnRefParseNodeVisitor.reset();
parentParseNode.accept(columnRefParseNodeVisitor);
ColumnRefParseNodeVisitor.ColumnRefType columnRefType =
columnRefParseNodeVisitor.getContentType(
this.joinTable.getAllTableRefs());
if(columnRefType == ColumnRefParseNodeVisitor.ColumnRefType.NONE ||
columnRefType == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY){
this.joinTable.postFilters.add(parentParseNode);
return null;
}
return parentParseNode;
}
}
private static class WhereNodeVisitor extends AndBooleanParseNodeVisitor<Void> {
private ColumnRefParseNodeVisitor columnRefVisitor;
private JoinTable joinTable;
public WhereNodeVisitor(
ColumnResolver resolver,
JoinTable joinTablesContext,
PhoenixConnection connection) {
this.joinTable = joinTablesContext;
this.columnRefVisitor = new ColumnRefParseNodeVisitor(resolver, connection);
}
@Override
protected Void leaveBooleanNode(ParseNode node,
List<Void> l) throws SQLException {
columnRefVisitor.reset();
node.accept(columnRefVisitor);
ColumnRefParseNodeVisitor.ColumnRefType type =
columnRefVisitor.getContentType(this.joinTable.getLeftTableRef());
switch (type) {
case NONE:
case SELF_ONLY:
this.joinTable.addLeftTableFilter(node);
break;
case FOREIGN_ONLY:
JoinTable matched = null;
for (JoinSpec joinSpec : this.joinTable.getPrefilterAcceptedJoinSpecs()) {
if (columnRefVisitor.getContentType(
joinSpec.getRhsJoinTable().getAllTableRefs()) ==
ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY) {
matched = joinSpec.getRhsJoinTable();
break;
}
}
if (matched != null) {
matched.pushDownFilter(node);
} else {
this.joinTable.addPostJoinFilter(node);
}
break;
default:
this.joinTable.addPostJoinFilter(node);
break;
}
return null;
}
@Override
protected Void leaveNonBooleanNode(ParseNode node, List<Void> l) throws SQLException {
return null;
}
@Override
public Void visitLeave(AndParseNode node, List<Void> l) throws SQLException {
return null;
}
@Override
public Void visitLeave(ComparisonParseNode node, List<Void> l)
throws SQLException {
if (!(node instanceof EqualParseNode))
return leaveBooleanNode(node, l);
List<JoinSpec> prefilterAcceptedJoinSpecs =
this.joinTable.getPrefilterAcceptedJoinSpecs();
ListIterator<JoinSpec> iter =
prefilterAcceptedJoinSpecs.listIterator(prefilterAcceptedJoinSpecs.size());
while (iter.hasPrevious()) {
JoinSpec joinSpec = iter.previous();
if (joinSpec.getType() != JoinType.Inner || joinSpec.isSingleValueOnly()) {
continue;
}
try {
joinSpec.pushDownOnCondition(node);
return null;
} catch (SQLException e) {
}
}
return leaveBooleanNode(node, l);
}
}
private static class OnNodeVisitor extends AndBooleanParseNodeVisitor<Void> {
private final ColumnRefParseNodeVisitor columnRefVisitor;
private final JoinSpec joinSpec;
public OnNodeVisitor(
ColumnResolver resolver, JoinSpec joinSpec, PhoenixConnection connection) {
this.joinSpec = joinSpec;
this.columnRefVisitor = new ColumnRefParseNodeVisitor(resolver, connection);
}
@Override
protected Void leaveBooleanNode(ParseNode node,
List<Void> l) throws SQLException {
columnRefVisitor.reset();
node.accept(columnRefVisitor);
ColumnRefParseNodeVisitor.ColumnRefType type =
columnRefVisitor.getContentType(this.joinSpec.getRhsJoinTableRefs());
if (type == ColumnRefParseNodeVisitor.ColumnRefType.NONE
|| type == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY) {
this.joinSpec.pushDownFilterToRhsJoinTable(node);
} else {
throwAmbiguousJoinConditionException();
}
return null;
}
@Override
protected Void leaveNonBooleanNode(ParseNode node, List<Void> l) throws SQLException {
return null;
}
@Override
public Void visitLeave(AndParseNode node, List<Void> l) throws SQLException {
return null;
}
@Override
public Void visitLeave(ComparisonParseNode node, List<Void> l)
throws SQLException {
if (!(node instanceof EqualParseNode))
return leaveBooleanNode(node, l);
columnRefVisitor.reset();
node.getLHS().accept(columnRefVisitor);
ColumnRefParseNodeVisitor.ColumnRefType lhsType =
columnRefVisitor.getContentType(this.joinSpec.getRhsJoinTableRefs());
Set<TableRef> lhsTableRefSet = Sets.newHashSet(columnRefVisitor.getTableRefSet());
columnRefVisitor.reset();
node.getRHS().accept(columnRefVisitor);
ColumnRefParseNodeVisitor.ColumnRefType rhsType =
columnRefVisitor.getContentType(this.joinSpec.getRhsJoinTableRefs());
Set<TableRef> rhsTableRefSet = Sets.newHashSet(columnRefVisitor.getTableRefSet());
if ((lhsType == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY || lhsType == ColumnRefParseNodeVisitor.ColumnRefType.NONE)
&& (rhsType == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY || rhsType == ColumnRefParseNodeVisitor.ColumnRefType.NONE)) {
this.joinSpec.pushDownFilterToRhsJoinTable(node);
} else if (lhsType == ColumnRefParseNodeVisitor.ColumnRefType.FOREIGN_ONLY
&& rhsType == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY) {
this.joinSpec.addOnCondition((EqualParseNode) node);
this.joinSpec.addDependentTableRefs(lhsTableRefSet);
} else if (rhsType == ColumnRefParseNodeVisitor.ColumnRefType.FOREIGN_ONLY
&& lhsType == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY) {
this.joinSpec.addOnCondition(NODE_FACTORY.equal(node.getRHS(), node.getLHS()));
this.joinSpec.addDependentTableRefs(rhsTableRefSet);
} else {
throwAmbiguousJoinConditionException();
}
return null;
}
/*
* Conditions in the ON clause can only be:
* 1) an equal test between a self table expression and a foreign
* table expression.
* 2) a boolean condition referencing to the self table only.
* Otherwise, it can be ambiguous.
*/
public void throwAmbiguousJoinConditionException() throws SQLException {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.AMBIGUOUS_JOIN_CONDITION).build().buildException();
}
}
private static class LocalIndexColumnRef extends ColumnRef {
private final TableRef indexTableRef;
public LocalIndexColumnRef(TableRef tableRef, String familyName,
String columnName, TableRef indexTableRef) throws MetaDataEntityNotFoundException {
super(tableRef, familyName, columnName);
this.indexTableRef = indexTableRef;
}
@Override
public TableRef getTableRef() {
return indexTableRef;
}
}
private static class ColumnRefParseNodeVisitor extends StatelessTraverseAllParseNodeVisitor {
public enum ColumnRefType {NONE, SELF_ONLY, FOREIGN_ONLY, COMPLEX};
private final ColumnResolver resolver;
private final PhoenixConnection connection;
private final Set<TableRef> tableRefSet;
private final Map<ColumnRef, ColumnParseNode> columnRefMap;
public ColumnRefParseNodeVisitor(ColumnResolver resolver, PhoenixConnection connection) {
this.resolver = resolver;
this.tableRefSet = new HashSet<TableRef>();
this.columnRefMap = new HashMap<ColumnRef, ColumnParseNode>();
this.connection = connection;
}
public void reset() {
this.tableRefSet.clear();
this.columnRefMap.clear();
}
@Override
public Void visit(ColumnParseNode node) throws SQLException {
ColumnRef columnRef = null;
try {
columnRef = resolver.resolveColumn(node.getSchemaName(), node.getTableName(), node.getName());
} catch (ColumnNotFoundException e) {
// This could be a LocalIndexDataColumnRef. If so, the table name must have
// been appended by the IndexStatementRewriter, and we can convert it into.
TableRef tableRef = resolver.resolveTable(node.getSchemaName(), node.getTableName());
if (tableRef.getTable().getIndexType() == IndexType.LOCAL) {
TableRef parentTableRef = FromCompiler.getResolver(
NODE_FACTORY.namedTable(null, TableName.create(tableRef.getTable()
.getSchemaName().getString(), tableRef.getTable()
.getParentTableName().getString())), connection).resolveTable(
tableRef.getTable().getSchemaName().getString(),
tableRef.getTable().getParentTableName().getString());
columnRef = new LocalIndexColumnRef(parentTableRef,
IndexUtil.getDataColumnFamilyName(node.getName()),
IndexUtil.getDataColumnName(node.getName()), tableRef);
} else {
throw e;
}
}
columnRefMap.put(columnRef, node);
tableRefSet.add(columnRef.getTableRef());
return null;
}
public Set<TableRef> getTableRefSet() {
return tableRefSet;
}
public Map<ColumnRef, ColumnParseNode> getColumnRefMap() {
return columnRefMap;
}
public ColumnRefType getContentType(List<TableRef> selfTableRefs) {
if (tableRefSet.isEmpty())
return ColumnRefType.NONE;
ColumnRefType ret = ColumnRefType.NONE;
for (TableRef tRef : tableRefSet) {
boolean isSelf = selfTableRefs.contains(tRef);
switch (ret) {
case NONE:
ret = isSelf ? ColumnRefType.SELF_ONLY : ColumnRefType.FOREIGN_ONLY;
break;
case SELF_ONLY:
ret = isSelf ? ColumnRefType.SELF_ONLY : ColumnRefType.COMPLEX;
break;
case FOREIGN_ONLY:
ret = isSelf ? ColumnRefType.COMPLEX : ColumnRefType.FOREIGN_ONLY;
break;
default: // COMPLEX do nothing
break;
}
if (ret == ColumnRefType.COMPLEX) {
break;
}
}
return ret;
}
}
// for creation of new statements
private static final ParseNodeFactory NODE_FACTORY = new ParseNodeFactory();
private static boolean isFlat(SelectStatement select) {
return !select.isJoin()
&& !select.isAggregate()
&& !select.isDistinct()
&& !(select.getFrom() instanceof DerivedTableNode)
&& select.getLimit() == null
&& select.getOffset() == null;
}
private static ParseNode combine(List<ParseNode> nodes) {
if (nodes.isEmpty())
return null;
if (nodes.size() == 1)
return nodes.get(0);
return NODE_FACTORY.and(nodes);
}
private boolean isWildCardSelectForTable(List<AliasedNode> select, TableRef tableRef, ColumnResolver resolver) throws SQLException {
ColumnRefParseNodeVisitor visitor = new ColumnRefParseNodeVisitor(resolver, statement.getConnection());
for (AliasedNode aliasedNode : select) {
ParseNode node = aliasedNode.getNode();
if (node instanceof TableWildcardParseNode) {
TableName tableName = ((TableWildcardParseNode) node).getTableName();
if (tableRef.equals(resolver.resolveTable(tableName.getSchemaName(), tableName.getTableName()))) {
return true;
}
}
}
return false;
}
private static Expression compilePostFilterExpression(StatementContext context, List<ParseNode> postFilters) throws SQLException {
if (postFilters.isEmpty())
return null;
ExpressionCompiler expressionCompiler = new ExpressionCompiler(context);
List<Expression> expressions = new ArrayList<Expression>(postFilters.size());
for (ParseNode postFilter : postFilters) {
expressionCompiler.reset();
Expression expression = postFilter.accept(expressionCompiler);
expressions.add(expression);
}
if (expressions.size() == 1)
return expressions.get(0);
return AndExpression.create(expressions);
}
public static PTable joinProjectedTables(PTable left, PTable right, JoinType type) throws SQLException {
Preconditions.checkArgument(left.getType() == PTableType.PROJECTED);
Preconditions.checkArgument(right.getType() == PTableType.PROJECTED);
List<PColumn> merged = Lists.<PColumn> newArrayList();
if (type == JoinType.Full) {
for (PColumn c : left.getColumns()) {
merged.add(new ProjectedColumn(c.getName(), c.getFamilyName(),
c.getPosition(), true, ((ProjectedColumn) c).getSourceColumnRef(), SchemaUtil.isPKColumn(c) ? null : c.getName().getBytes()));
}
} else {
merged.addAll(left.getColumns());
}
int position = merged.size();
for (PColumn c : right.getColumns()) {
if (!SchemaUtil.isPKColumn(c)) {
PColumn column = new ProjectedColumn(c.getName(), c.getFamilyName(),
position++, type == JoinType.Inner ? c.isNullable() : true,
((ProjectedColumn) c).getSourceColumnRef(), c.getName().getBytes());
merged.add(column);
}
}
if (left.getBucketNum() != null) {
merged.remove(0);
}
return new PTableImpl.Builder()
.setType(left.getType())
.setState(left.getIndexState())
.setTimeStamp(left.getTimeStamp())
.setIndexDisableTimestamp(left.getIndexDisableTimestamp())
.setSequenceNumber(left.getSequenceNumber())
.setImmutableRows(left.isImmutableRows())
.setDisableWAL(PTable.DEFAULT_DISABLE_WAL)
.setMultiTenant(left.isMultiTenant())
.setStoreNulls(left.getStoreNulls())
.setViewType(left.getViewType())
.setViewIndexIdType(left.getviewIndexIdType())
.setViewIndexId(left.getViewIndexId())
.setIndexType(left.getIndexType())
.setTransactionProvider(left.getTransactionProvider())
.setUpdateCacheFrequency(left.getUpdateCacheFrequency())
.setNamespaceMapped(left.isNamespaceMapped())
.setAutoPartitionSeqName(left.getAutoPartitionSeqName())
.setAppendOnlySchema(left.isAppendOnlySchema())
.setImmutableStorageScheme(ONE_CELL_PER_COLUMN)
.setQualifierEncodingScheme(NON_ENCODED_QUALIFIERS)
.setBaseColumnCount(BASE_TABLE_BASE_COLUMN_COUNT)
.setEncodedCQCounter(PTable.EncodedCQCounter.NULL_COUNTER)
.setUseStatsForParallelization(left.useStatsForParallelization())
.setExcludedColumns(ImmutableList.of())
.setTenantId(left.getTenantId())
.setSchemaName(left.getSchemaName())
.setTableName(PNameFactory.newName(SchemaUtil.getTableName(left.getName().getString(),
right.getName().getString())))
.setPkName(left.getPKName())
.setRowKeyOrderOptimizable(left.rowKeyOrderOptimizable())
.setBucketNum(left.getBucketNum())
.setIndexes(left.getIndexes() == null ? Collections.emptyList() : left.getIndexes())
.setParentSchemaName(left.getParentSchemaName())
.setParentTableName(left.getParentTableName())
.setPhysicalNames(ImmutableList.of())
.setColumns(merged)
.build();
}
}