blob: 3290edd6b3ce13f9e8da44dd42fe6c00cedc5bff [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.impala.analysis;
import static org.apache.impala.analysis.ToSqlOptions.DEFAULT;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.apache.impala.authorization.Privilege;
import org.apache.impala.catalog.FeFsTable;
import org.apache.impala.catalog.FeTable;
import org.apache.impala.common.AnalysisException;
import org.apache.impala.planner.JoinNode.DistributionMode;
import org.apache.impala.rewrite.ExprRewriter;
import org.apache.impala.thrift.TReplicaPreference;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
/**
* Superclass of all table references, including references to views, base tables
* (Hdfs, HBase or DataSource tables), and nested collections. Contains the join
* specification. An instance of a TableRef (and not a subclass thereof) represents
* an unresolved table reference that must be resolved during analysis. All resolved
* table references are subclasses of TableRef.
*
* The analysis of table refs follows a two-step process:
*
* 1. Resolution: A table ref's path is resolved and then the generic TableRef is
* replaced by a concrete table ref (a BaseTableRef, CollectionTabeRef or ViewRef)
* in the originating stmt and that is given the resolved path. This step is driven by
* Analyzer.resolveTableRef().
*
* 2. Analysis/registration: After resolution, the concrete table ref is analyzed
* to register a tuple descriptor for its resolved path and register other table-ref
* specific state with the analyzer (e.g., whether it is outer/semi joined, etc.).
*
* Therefore, subclasses of TableRef should never call the analyze() of its superclass.
*
* TODO for 2.3: The current TableRef class hierarchy and the related two-phase analysis
* feels convoluted and is hard to follow. We should reorganize the TableRef class
* structure for clarity of analysis and avoid a table ref 'switching genders' in between
* resolution and registration.
*
* TODO for 2.3: Rename this class to CollectionRef and re-consider the naming and
* structure of all subclasses.
*/
public class TableRef extends StmtNode {
// Path to a collection type. Not set for inline views.
protected List<String> rawPath_;
// Legal aliases of this table ref. Contains the explicit alias as its sole element if
// there is one. Otherwise, contains the two implicit aliases. Implicit aliases are set
// in the c'tor of the corresponding resolved table ref (subclasses of TableRef) during
// analysis. By convention, for table refs with multiple implicit aliases, aliases_[0]
// contains the fully-qualified implicit alias to ensure that aliases_[0] always
// uniquely identifies this table ref regardless of whether it has an explicit alias.
protected String[] aliases_;
// Indicates whether this table ref is given an explicit alias,
protected boolean hasExplicitAlias_;
// Analysis registers privilege and/or audit requests based on this privilege.
protected final Privilege priv_;
protected final boolean requireGrantOption_;
// Optional TABLESAMPLE clause. Null if not specified.
protected TableSampleClause sampleParams_;
protected JoinOperator joinOp_;
protected List<PlanHint> joinHints_ = new ArrayList<>();
protected List<String> usingColNames_;
protected List<PlanHint> tableHints_ = new ArrayList<>();
protected TReplicaPreference replicaPreference_;
protected boolean randomReplica_;
// Hinted distribution mode for this table ref; set after analyzeJoinHints()
// TODO: Move join-specific members out of TableRef.
private DistributionMode distrMode_ = DistributionMode.NONE;
/////////////////////////////////////////
// BEGIN: Members that need to be reset()
// Resolution of rawPath_ if applicable. Result of analysis.
protected Path resolvedPath_;
protected Expr onClause_;
// the ref to the left of us, if we're part of a JOIN clause
protected TableRef leftTblRef_;
// true if this TableRef has been analyzed; implementing subclass should set it to true
// at the end of analyze() call.
protected boolean isAnalyzed_;
// Lists of table ref ids and materialized tuple ids of the full sequence of table
// refs up to and including this one. These ids are cached during analysis because
// we may alter the chain of table refs during plan generation, but we still rely
// on the original list of ids for correct predicate assignment.
// Populated in analyzeJoin().
protected List<TupleId> allTableRefIds_ = new ArrayList<>();
protected List<TupleId> allMaterializedTupleIds_ = new ArrayList<>();
// All physical tuple ids that this table ref is correlated with:
// Tuple ids of root descriptors from outer query blocks that this table ref
// (if a CollectionTableRef) or contained CollectionTableRefs (if an InlineViewRef)
// are rooted at. Populated during analysis.
protected List<TupleId> correlatedTupleIds_ = new ArrayList<>();
// analysis output
protected TupleDescriptor desc_;
// true if this table is masked by a table masking view and need to expose its nested
// columns via the view.
protected boolean exposeNestedColumnsByTableMaskView_ = false;
// END: Members that need to be reset()
/////////////////////////////////////////
public TableRef(List<String> path, String alias) {
this(path, alias, Privilege.SELECT);
}
public TableRef(List<String> path, String alias, TableSampleClause tableSample) {
this(path, alias, tableSample, Privilege.SELECT, false);
}
public TableRef(List<String> path, String alias, Privilege priv) {
this(path, alias, null, priv, false);
}
public TableRef(List<String> path, String alias, Privilege priv,
boolean requireGrantOption) {
this(path, alias, null, priv, requireGrantOption);
}
public TableRef(List<String> path, String alias, TableSampleClause sampleParams,
Privilege priv, boolean requireGrantOption) {
rawPath_ = path;
if (alias != null) {
aliases_ = new String[] { alias.toLowerCase() };
hasExplicitAlias_ = true;
} else {
hasExplicitAlias_ = false;
}
sampleParams_ = sampleParams;
priv_ = priv;
requireGrantOption_ = requireGrantOption;
isAnalyzed_ = false;
replicaPreference_ = null;
randomReplica_ = false;
}
/**
* C'tor for cloning.
*/
protected TableRef(TableRef other) {
rawPath_ = other.rawPath_;
resolvedPath_ = other.resolvedPath_;
aliases_ = other.aliases_;
hasExplicitAlias_ = other.hasExplicitAlias_;
sampleParams_ = other.sampleParams_;
priv_ = other.priv_;
requireGrantOption_ = other.requireGrantOption_;
joinOp_ = other.joinOp_;
joinHints_ = Lists.newArrayList(other.joinHints_);
onClause_ = (other.onClause_ != null) ? other.onClause_.clone() : null;
usingColNames_ =
(other.usingColNames_ != null) ? Lists.newArrayList(other.usingColNames_) : null;
tableHints_ = Lists.newArrayList(other.tableHints_);
replicaPreference_ = other.replicaPreference_;
randomReplica_ = other.randomReplica_;
distrMode_ = other.distrMode_;
// The table ref links are created at the statement level, so cloning a set of linked
// table refs is the responsibility of the statement.
leftTblRef_ = null;
isAnalyzed_ = other.isAnalyzed_;
allTableRefIds_ = Lists.newArrayList(other.allTableRefIds_);
allMaterializedTupleIds_ = Lists.newArrayList(other.allMaterializedTupleIds_);
correlatedTupleIds_ = Lists.newArrayList(other.correlatedTupleIds_);
desc_ = other.desc_;
exposeNestedColumnsByTableMaskView_ = other.exposeNestedColumnsByTableMaskView_;
}
@Override
public void analyze(Analyzer analyzer) throws AnalysisException {
throw new IllegalStateException(
"Should not call analyze() on an unresolved TableRef.");
}
/**
* Creates and returns a empty TupleDescriptor registered with the analyzer
* based on the resolvedPath_.
* This method is called from the analyzer when registering this table reference.
*/
public TupleDescriptor createTupleDescriptor(Analyzer analyzer)
throws AnalysisException {
TupleDescriptor result = analyzer.getDescTbl().createTupleDescriptor(
getClass().getSimpleName() + " " + getUniqueAlias());
result.setPath(resolvedPath_);
return result;
}
/**
* Set this table's context-dependent join attributes from the given table.
* Does not clone the attributes.
*/
protected void setJoinAttrs(TableRef other) {
this.joinOp_ = other.joinOp_;
this.joinHints_ = other.joinHints_;
this.tableHints_ = other.tableHints_;
this.onClause_ = other.onClause_;
this.usingColNames_ = other.usingColNames_;
}
public JoinOperator getJoinOp() {
// if it's not explicitly set, we're doing an inner join
return (joinOp_ == null ? JoinOperator.INNER_JOIN : joinOp_);
}
public TReplicaPreference getReplicaPreference() { return replicaPreference_; }
public boolean getRandomReplica() { return randomReplica_; }
/**
* Returns true if this table ref has a resolved path that is rooted at a registered
* tuple descriptor, false otherwise.
*/
public boolean isRelative() { return false; }
/**
* Indicates if this TableRef directly or indirectly references another TableRef from
* an outer query block.
*/
public boolean isCorrelated() { return !correlatedTupleIds_.isEmpty(); }
public List<String> getPath() { return rawPath_; }
public Path getResolvedPath() { return resolvedPath_; }
/**
* Returns all legal aliases of this table ref.
*/
public String[] getAliases() { return aliases_; }
/**
* Returns the explicit alias or the fully-qualified implicit alias. The returned alias
* is guaranteed to be unique (i.e., column/field references against the alias cannot
* be ambiguous).
*/
public String getUniqueAlias() { return aliases_[0]; }
/**
* Returns true if this table ref has an explicit alias.
* Note that getAliases().length() == 1 does not imply an explicit alias because
* nested collection refs have only a single implicit alias.
*/
public boolean hasExplicitAlias() { return hasExplicitAlias_; }
/**
* Returns the explicit alias if this table ref has one, null otherwise.
*/
public String getExplicitAlias() {
if (hasExplicitAlias()) return getUniqueAlias();
return null;
}
public FeTable getTable() {
Preconditions.checkNotNull(resolvedPath_);
return resolvedPath_.getRootTable();
}
public TableSampleClause getSampleParams() { return sampleParams_; }
public Privilege getPrivilege() { return priv_; }
public boolean requireGrantOption() { return requireGrantOption_; }
public List<PlanHint> getJoinHints() { return joinHints_; }
public List<PlanHint> getTableHints() { return tableHints_; }
public Expr getOnClause() { return onClause_; }
public void setJoinOp(JoinOperator op) { this.joinOp_ = op; }
public void setOnClause(Expr e) { this.onClause_ = e; }
public void setUsingClause(List<String> colNames) { this.usingColNames_ = colNames; }
public TableRef getLeftTblRef() { return leftTblRef_; }
public void setLeftTblRef(TableRef leftTblRef) { this.leftTblRef_ = leftTblRef; }
public void setExposeNestedColumnsByTableMaskView() {
exposeNestedColumnsByTableMaskView_ = true;
}
public boolean exposeNestedColumnsByTableMaskView() {
return exposeNestedColumnsByTableMaskView_;
}
public void setJoinHints(List<PlanHint> hints) {
Preconditions.checkNotNull(hints);
joinHints_ = hints;
}
public void setTableHints(List<PlanHint> hints) {
Preconditions.checkNotNull(hints);
tableHints_ = hints;
}
public boolean isBroadcastJoin() { return distrMode_ == DistributionMode.BROADCAST; }
public boolean isPartitionedJoin() {
return distrMode_ == DistributionMode.PARTITIONED;
}
public DistributionMode getDistributionMode() { return distrMode_; }
public List<TupleId> getCorrelatedTupleIds() { return correlatedTupleIds_; }
public boolean isAnalyzed() { return isAnalyzed_; }
public boolean isResolved() { return !getClass().equals(TableRef.class); }
/**
* This method should only be called after the TableRef has been analyzed.
*/
public TupleDescriptor getDesc() {
Preconditions.checkState(isAnalyzed_);
// after analyze(), desc should be set.
Preconditions.checkState(desc_ != null);
return desc_;
}
/**
* This method should only be called after the TableRef has been analyzed.
*/
public TupleId getId() {
Preconditions.checkState(isAnalyzed_);
// after analyze(), desc should be set.
Preconditions.checkNotNull(desc_);
return desc_.getId();
}
public List<TupleId> getMaterializedTupleIds() {
// This function should only be called after analyze().
Preconditions.checkState(isAnalyzed_);
Preconditions.checkNotNull(desc_);
return desc_.getId().asList();
}
/**
* Returns the list of tuple ids materialized by the full sequence of
* table refs up to and including this one.
*/
public List<TupleId> getAllMaterializedTupleIds() {
Preconditions.checkState(isAnalyzed_);
return allMaterializedTupleIds_;
}
/**
* Return the list of table ref ids of the full sequence of table refs up to
* and including this one.
*/
public List<TupleId> getAllTableRefIds() {
Preconditions.checkState(isAnalyzed_);
return allTableRefIds_;
}
protected void analyzeTableSample(Analyzer analyzer) throws AnalysisException {
if (sampleParams_ == null) return;
sampleParams_.analyze(analyzer);
if (!(this instanceof BaseTableRef)
|| !(resolvedPath_.destTable() instanceof FeFsTable)) {
throw new AnalysisException(
"TABLESAMPLE is only supported on HDFS tables: " + getUniqueAlias());
}
}
protected void analyzeHints(Analyzer analyzer) throws AnalysisException {
// We prefer adding warnings over throwing exceptions here to maintain view
// compatibility with Hive.
Preconditions.checkState(isResolved());
analyzeTableHints(analyzer);
analyzeJoinHints(analyzer);
}
private void analyzeTableHints(Analyzer analyzer) {
if (tableHints_.isEmpty()) return;
if (!(this instanceof BaseTableRef)) {
analyzer.addWarning("Table hints not supported for inline view and collections");
return;
}
// BaseTableRef will always have their path resolved at this point.
Preconditions.checkState(getResolvedPath() != null);
if (getResolvedPath().destTable() != null &&
!(getResolvedPath().destTable() instanceof FeFsTable)) {
analyzer.addWarning("Table hints only supported for Hdfs tables");
}
for (PlanHint hint: tableHints_) {
if (hint.is("SCHEDULE_CACHE_LOCAL")) {
analyzer.setHasPlanHints();
replicaPreference_ = TReplicaPreference.CACHE_LOCAL;
} else if (hint.is("SCHEDULE_DISK_LOCAL")) {
analyzer.setHasPlanHints();
replicaPreference_ = TReplicaPreference.DISK_LOCAL;
} else if (hint.is("SCHEDULE_REMOTE")) {
analyzer.setHasPlanHints();
replicaPreference_ = TReplicaPreference.REMOTE;
} else if (hint.is("SCHEDULE_RANDOM_REPLICA")) {
analyzer.setHasPlanHints();
randomReplica_ = true;
} else {
Preconditions.checkState(getAliases() != null && getAliases().length > 0);
analyzer.addWarning("Table hint not recognized for table " + getUniqueAlias() +
": " + hint);
}
}
}
private void analyzeJoinHints(Analyzer analyzer) throws AnalysisException {
if (joinHints_.isEmpty()) return;
for (PlanHint hint: joinHints_) {
if (hint.is("BROADCAST")) {
if (joinOp_ == JoinOperator.RIGHT_OUTER_JOIN
|| joinOp_ == JoinOperator.FULL_OUTER_JOIN
|| joinOp_ == JoinOperator.RIGHT_SEMI_JOIN
|| joinOp_ == JoinOperator.RIGHT_ANTI_JOIN) {
throw new AnalysisException(
joinOp_.toString() + " does not support BROADCAST.");
}
if (isPartitionedJoin()) {
throw new AnalysisException("Conflicting JOIN hint: " + hint);
}
distrMode_ = DistributionMode.BROADCAST;
analyzer.setHasPlanHints();
} else if (hint.is("SHUFFLE")) {
if (joinOp_ == JoinOperator.CROSS_JOIN) {
throw new AnalysisException("CROSS JOIN does not support SHUFFLE.");
}
if (isBroadcastJoin()) {
throw new AnalysisException("Conflicting JOIN hint: " + hint);
}
distrMode_ = DistributionMode.PARTITIONED;
analyzer.setHasPlanHints();
} else {
analyzer.addWarning("JOIN hint not recognized: " + hint);
}
}
}
/**
* Analyzes the join clause. Populates allTableRefIds_ and allMaterializedTupleIds_.
* The join clause can only be analyzed after the left table has been analyzed
* and the TupleDescriptor (desc) of this table has been created.
*/
public void analyzeJoin(Analyzer analyzer) throws AnalysisException {
Preconditions.checkState(leftTblRef_ == null || leftTblRef_.isAnalyzed_);
Preconditions.checkState(desc_ != null);
// Populate the lists of all table ref and materialized tuple ids.
allTableRefIds_.clear();
allMaterializedTupleIds_.clear();
if (leftTblRef_ != null) {
allTableRefIds_.addAll(leftTblRef_.getAllTableRefIds());
allMaterializedTupleIds_.addAll(leftTblRef_.getAllMaterializedTupleIds());
}
allTableRefIds_.add(getId());
allMaterializedTupleIds_.addAll(getMaterializedTupleIds());
if (joinOp_ == JoinOperator.CROSS_JOIN) {
// A CROSS JOIN is always a broadcast join, regardless of the join hints
distrMode_ = DistributionMode.BROADCAST;
}
if (usingColNames_ != null) {
Preconditions.checkState(joinOp_ != JoinOperator.CROSS_JOIN);
// Turn USING clause into equivalent ON clause.
onClause_ = null;
for (String colName: usingColNames_) {
// check whether colName exists both for our table and the one
// to the left of us
Path leftColPath = new Path(leftTblRef_.getDesc(),
Lists.newArrayList(colName.toLowerCase()));
if (!leftColPath.resolve()) {
throw new AnalysisException(
"unknown column " + colName + " for alias "
+ leftTblRef_.getUniqueAlias() + " (in \"" + this.toSql() + "\")");
}
Path rightColPath = new Path(desc_,
Lists.newArrayList(colName.toLowerCase()));
if (!rightColPath.resolve()) {
throw new AnalysisException(
"unknown column " + colName + " for alias "
+ getUniqueAlias() + " (in \"" + this.toSql() + "\")");
}
// create predicate "<left>.colName = <right>.colName"
BinaryPredicate eqPred =
new BinaryPredicate(BinaryPredicate.Operator.EQ,
new SlotRef(Path.createRawPath(leftTblRef_.getUniqueAlias(), colName)),
new SlotRef(Path.createRawPath(getUniqueAlias(), colName)));
onClause_ = CompoundPredicate.createConjunction(eqPred, onClause_);
}
}
// at this point, both 'this' and leftTblRef have been analyzed and registered;
// register the tuple ids of the TableRefs on the nullable side of an outer join
if (joinOp_ == JoinOperator.LEFT_OUTER_JOIN
|| joinOp_ == JoinOperator.FULL_OUTER_JOIN) {
analyzer.registerOuterJoinedTids(getId().asList(), this);
}
if (joinOp_ == JoinOperator.RIGHT_OUTER_JOIN
|| joinOp_ == JoinOperator.FULL_OUTER_JOIN) {
analyzer.registerOuterJoinedTids(leftTblRef_.getAllTableRefIds(), this);
}
// register the tuple ids of a full outer join
if (joinOp_ == JoinOperator.FULL_OUTER_JOIN) {
analyzer.registerFullOuterJoinedTids(leftTblRef_.getAllTableRefIds(), this);
analyzer.registerFullOuterJoinedTids(getId().asList(), this);
}
// register the tuple id of the rhs of a left semi join
TupleId semiJoinedTupleId = null;
if (joinOp_ == JoinOperator.LEFT_SEMI_JOIN
|| joinOp_ == JoinOperator.LEFT_ANTI_JOIN
|| joinOp_ == JoinOperator.NULL_AWARE_LEFT_ANTI_JOIN) {
analyzer.registerSemiJoinedTid(getId(), this);
semiJoinedTupleId = getId();
}
// register the tuple id of the lhs of a right semi join
if (joinOp_ == JoinOperator.RIGHT_SEMI_JOIN
|| joinOp_ == JoinOperator.RIGHT_ANTI_JOIN) {
analyzer.registerSemiJoinedTid(leftTblRef_.getId(), this);
semiJoinedTupleId = leftTblRef_.getId();
}
if (onClause_ != null) {
Preconditions.checkState(joinOp_ != JoinOperator.CROSS_JOIN);
analyzer.setVisibleSemiJoinedTuple(semiJoinedTupleId);
onClause_.analyze(analyzer);
analyzer.setVisibleSemiJoinedTuple(null);
onClause_.checkReturnsBool("ON clause", true);
if (onClause_.contains(Expr.IS_AGGREGATE)) {
throw new AnalysisException(
"aggregate function not allowed in ON clause: " + toSql());
}
if (onClause_.contains(AnalyticExpr.class)) {
throw new AnalysisException(
"analytic expression not allowed in ON clause: " + toSql());
}
if (onClause_.contains(Subquery.class)) {
throw new AnalysisException(
"Subquery is not allowed in ON clause: " + toSql());
}
Set<TupleId> onClauseTupleIds = new HashSet<>();
List<Expr> conjuncts = onClause_.getConjuncts();
// Outer join clause conjuncts are registered for this particular table ref
// (ie, can only be evaluated by the plan node that implements this join).
// The exception are conjuncts that only pertain to the nullable side
// of the outer join; those can be evaluated directly when materializing tuples
// without violating outer join semantics.
analyzer.registerOnClauseConjuncts(conjuncts, this);
for (Expr e: conjuncts) {
List<TupleId> tupleIds = new ArrayList<>();
e.getIds(tupleIds, null);
onClauseTupleIds.addAll(tupleIds);
}
} else if (!isRelative() && !isCorrelated()
&& (getJoinOp().isOuterJoin() || getJoinOp().isSemiJoin())) {
throw new AnalysisException(
joinOp_.toString() + " requires an ON or USING clause.");
} else {
// Indicate that this table ref has an empty ON-clause.
analyzer.registerOnClauseConjuncts(Collections.<Expr>emptyList(), this);
}
}
public void rewriteExprs(ExprRewriter rewriter, Analyzer analyzer)
throws AnalysisException {
Preconditions.checkState(isAnalyzed_);
if (onClause_ != null) onClause_ = rewriter.rewrite(onClause_, analyzer);
}
public String debugString() { return tableRefToSql(); }
protected String tableRefToSql() { return tableRefToSql(DEFAULT); }
protected String tableRefToSql(ToSqlOptions options) {
String aliasSql = null;
String alias = getExplicitAlias();
if (alias != null) aliasSql = ToSqlUtils.getIdentSql(alias);
List<String> path = rawPath_;
if (resolvedPath_ != null) path = resolvedPath_.getFullyQualifiedRawPath();
return ToSqlUtils.getPathSql(path) + ((aliasSql != null) ? " " + aliasSql : "");
}
@Override
public final String toSql() {
return toSql(DEFAULT);
}
@Override
public String toSql(ToSqlOptions options) {
if (joinOp_ == null) {
// prepend "," if we're part of a sequence of table refs w/o an
// explicit JOIN clause
return (leftTblRef_ != null ? ", " : "") + tableRefToSql(options);
}
StringBuilder output = new StringBuilder(" " + joinOp_.toString() + " ");
if (!joinHints_.isEmpty())
output.append(ToSqlUtils.getPlanHintsSql(options, joinHints_)).append(" ");
output.append(tableRefToSql(options));
if (usingColNames_ != null) {
output.append(" USING (").append(Joiner.on(", ").join(usingColNames_)).append(")");
} else if (onClause_ != null) {
output.append(" ON ").append(onClause_.toSql(options));
}
return output.toString();
}
/**
* Returns a deep clone of this table ref without also cloning the chain of table refs.
* Sets leftTblRef_ in the returned clone to null.
*/
@Override
protected TableRef clone() { return new TableRef(this); }
public void reset() {
isAnalyzed_ = false;
resolvedPath_ = null;
if (usingColNames_ != null) {
// The using col names are converted into an on-clause predicate during analysis,
// so unset the on-clause here.
onClause_ = null;
} else if (onClause_ != null) {
onClause_.reset();
}
leftTblRef_ = null;
allTableRefIds_.clear();
allMaterializedTupleIds_.clear();
correlatedTupleIds_.clear();
desc_ = null;
}
public boolean isTableMaskingView() { return false; }
void migratePropertiesTo(TableRef other) {
other.aliases_ = aliases_;
other.onClause_ = onClause_;
other.usingColNames_ = usingColNames_;
other.joinOp_ = joinOp_;
other.joinHints_ = joinHints_;
other.tableHints_ = tableHints_;
// Clear properties. Don't clear aliases_ since it's still used in resolving slots
// in the query block of 'other'.
onClause_ = null;
usingColNames_ = null;
joinOp_ = null;
joinHints_ = new ArrayList<>();
tableHints_ = new ArrayList<>();
}
}