blob: cf59011abd625a35e7ea8b3c1c5b60d2ee98b913 [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.sql.compile.FromBaseTable
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.derby.impl.sql.compile;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.apache.derby.catalog.IndexDescriptor;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.ClassName;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.services.classfile.VMOpcode;
import org.apache.derby.iapi.services.compiler.MethodBuilder;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.iapi.services.io.FormatableArrayHolder;
import org.apache.derby.iapi.services.io.FormatableBitSet;
import org.apache.derby.iapi.services.io.FormatableIntHolder;
import org.apache.derby.iapi.services.property.PropertyUtil;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.LanguageProperties;
import org.apache.derby.iapi.sql.compile.AccessPath;
import org.apache.derby.iapi.sql.compile.CompilerContext;
import org.apache.derby.iapi.sql.compile.CostEstimate;
import org.apache.derby.iapi.sql.compile.JoinStrategy;
import org.apache.derby.iapi.sql.compile.OptimizablePredicate;
import org.apache.derby.iapi.sql.compile.OptimizablePredicateList;
import org.apache.derby.iapi.sql.compile.Optimizer;
import org.apache.derby.iapi.sql.compile.RequiredRowOrdering;
import org.apache.derby.iapi.sql.compile.RowOrdering;
import org.apache.derby.iapi.sql.compile.TagFilter;
import org.apache.derby.iapi.sql.compile.Visitor;
import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList;
import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptor;
import org.apache.derby.iapi.sql.dictionary.DataDictionary;
import org.apache.derby.iapi.sql.dictionary.IndexRowGenerator;
import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
import org.apache.derby.iapi.sql.dictionary.ViewDescriptor;
import org.apache.derby.iapi.sql.execute.ExecRow;
import org.apache.derby.iapi.store.access.ScanController;
import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;
import org.apache.derby.iapi.store.access.StoreCostController;
import org.apache.derby.iapi.store.access.TransactionController;
import org.apache.derby.iapi.transaction.TransactionControl;
import org.apache.derby.iapi.types.DataValueDescriptor;
import org.apache.derby.iapi.util.JBitSet;
import org.apache.derby.iapi.util.StringUtil;
// Temporary until user override for disposable stats has been removed.
import org.apache.derby.impl.services.daemon.IndexStatisticsDaemonImpl;
import org.apache.derby.impl.sql.catalog.SYSUSERSRowFactory;
/**
* A FromBaseTable represents a table in the FROM list of a DML statement,
* as distinguished from a FromSubquery, which represents a subquery in the
* FROM list. A FromBaseTable may actually represent a view. During parsing,
* we can't distinguish views from base tables. During binding, when we
* find FromBaseTables that represent views, we replace them with FromSubqueries.
* By the time we get to code generation, all FromSubqueries have been eliminated,
* and all FromBaseTables will represent only true base tables.
* <p>
* <B>Positioned Update</B>: Currently, all columns of an updatable cursor
* are selected to deal with a positioned update. This is because we don't
* know what columns will ultimately be needed from the UpdateNode above
* us. For example, consider:<pre><i>
*
* get c as 'select cint from t for update of ctinyint'
* update t set ctinyint = csmallint
*
* </pre></i> Ideally, the cursor only selects cint. Then,
* something akin to an IndexRowToBaseRow is generated to
* take the CursorResultSet and get the appropriate columns
* out of the base table from the RowLocation returned by the
* cursor. Then the update node can generate the appropriate
* NormalizeResultSet (or whatever else it might need) to
* get things into the correct format for the UpdateResultSet.
* See CurrentOfNode for more information.
*
*/
class FromBaseTable extends FromTable
{
static final int UNSET = -1;
/**
* Whether or not we have checked the index statistics for staleness.
* Used to avoid performing the check multiple times per compilation.
*/
private boolean hasCheckedIndexStats;
TableName tableName;
TableDescriptor tableDescriptor;
ConglomerateDescriptor baseConglomerateDescriptor;
ConglomerateDescriptor[] conglomDescs;
int updateOrDelete;
/*
** The number of rows to bulkFetch.
** Initially it is unset. If the user
** uses the bulkFetch table property,
** it is set to that. Otherwise, it
** may be turned on if it isn't an updatable
** cursor and it is the right type of
** result set (more than 1 row expected to
** be returned, and not hash, which does its
** own bulk fetch, and subquery).
*/
int bulkFetch = UNSET;
/*
** Used to validate deferred check constraints.
** It is the uuid of the target table inserted into or updated
** when a violation was detected but deferred.
*/
private String targetTableUUIDString;
private boolean validatingCheckConstraint = false;
/* We may turn off bulk fetch for a variety of reasons,
* including because of the min optimization.
* bulkFetchTurnedOff is set to true in those cases.
*/
boolean bulkFetchTurnedOff;
/* Whether or not we are going to do execution time "multi-probing"
* on the table scan for this FromBaseTable.
*/
boolean multiProbing = false;
private double singleScanRowCount;
private FormatableBitSet referencedCols;
private ResultColumnList templateColumns;
/* A 0-based array of column names for this table used
* for optimizer trace.
*/
private String[] columnNames;
// true if we are to do a special scan to retrieve the last value
// in the index
private boolean specialMaxScan;
// true if we are to do a distinct scan
private boolean distinctScan;
/**
*Information for dependent table scan for Referential Actions
*/
private boolean raDependentScan;
private String raParentResultSetId;
private long fkIndexConglomId;
private int[] fkColArray;
/**
* Restriction as a PredicateList
*/
PredicateList baseTableRestrictionList;
PredicateList nonBaseTableRestrictionList;
PredicateList restrictionList;
PredicateList storeRestrictionList;
PredicateList nonStoreRestrictionList;
PredicateList requalificationRestrictionList;
static final int UPDATE = 1;
static final int DELETE = 2;
/* Variables for EXISTS FBTs */
private boolean existsBaseTable;
private boolean isNotExists; //is a NOT EXISTS base table
private JBitSet dependencyMap;
private boolean getUpdateLocks;
// true if we are running with sql authorization and this is the SYSUSERS table
private boolean authorizeSYSUSERS;
// non-null if we need to return a row location column
private String rowLocationColumnName;
/**
* Constructor for a table in a FROM list. Parameters are as follows:
*
* @param tableName The name of the table
* @param correlationName The correlation name
* @param derivedRCL The derived column list
* @param tableProperties The Properties list associated with the table.
* @param cm The context manager
*/
FromBaseTable(TableName tableName,
String correlationName,
ResultColumnList derivedRCL,
Properties tableProperties,
ContextManager cm)
{
super(correlationName, tableProperties, cm);
this.tableName = tableName;
setResultColumns( derivedRCL );
setOrigTableName(this.tableName);
templateColumns = getResultColumns();
}
/**
* Initializer for a table in a FROM list. Parameters are as follows:
*
* @param tableName The name of the table
* @param correlationName The correlation name
* @param updateOrDelete Table is being updated/deleted from.
* @param derivedRCL The derived column list
* @param cm The context manager
*/
FromBaseTable(TableName tableName,
String correlationName,
int updateOrDelete,
ResultColumnList derivedRCL,
ContextManager cm)
{
super(correlationName, null, cm);
this.tableName = tableName;
this.updateOrDelete = updateOrDelete;
setResultColumns( derivedRCL );
setOrigTableName(this.tableName);
templateColumns = getResultColumns();
}
/** Set the name of the row location column */
void setRowLocationColumnName( String rowLocationColumnName )
{
this.rowLocationColumnName = rowLocationColumnName;
}
/**
* no LOJ reordering for base table.
*/
@Override
boolean LOJ_reorderable(int numTables)
throws StandardException
{
return false;
}
@Override
JBitSet LOJgetReferencedTables(int numTables)
throws StandardException
{
JBitSet map = new JBitSet(numTables);
fillInReferencedTableMap(map);
return map;
}
/*
* Optimizable interface.
*/
/**
* @see org.apache.derby.iapi.sql.compile.Optimizable#nextAccessPath
*
* @exception StandardException Thrown on error
*/
@Override
public boolean nextAccessPath(Optimizer optimizer,
OptimizablePredicateList predList,
RowOrdering rowOrdering)
throws StandardException
{
String userSpecifiedIndexName = getUserSpecifiedIndexName();
AccessPath ap = getCurrentAccessPath();
ConglomerateDescriptor currentConglomerateDescriptor =
ap.getConglomerateDescriptor();
if ( optimizerTracingIsOn() )
{ getOptimizerTracer().traceNextAccessPath( getExposedName(), ((predList == null) ? 0 : predList.size()) ); }
/*
** Remove the ordering of the current conglomerate descriptor,
** if any.
*/
rowOrdering.removeOptimizable(getTableNumber());
// RESOLVE: This will have to be modified to step through the
// join strategies as well as the conglomerates.
if (userSpecifiedIndexName != null)
{
/*
** User specified an index name, so we should look at only one
** index. If there is a current conglomerate descriptor, and there
** are no more join strategies, we've already looked at the index,
** so go back to null.
*/
if (currentConglomerateDescriptor != null)
{
if ( ! super.nextAccessPath(optimizer,
predList,
rowOrdering) )
{
currentConglomerateDescriptor = null;
}
}
else
{
if ( optimizerTracingIsOn() )
{ getOptimizerTracer().traceLookingForSpecifiedIndex( userSpecifiedIndexName, tableNumber ); }
if (StringUtil.SQLToUpperCase(userSpecifiedIndexName).equals("NULL"))
{
/* Special case - user-specified table scan */
currentConglomerateDescriptor =
tableDescriptor.getConglomerateDescriptor(
tableDescriptor.getHeapConglomerateId()
);
}
else
{
/* User-specified index name */
getConglomDescs();
for (int index = 0; index < conglomDescs.length; index++)
{
currentConglomerateDescriptor = conglomDescs[index];
String conglomerateName =
currentConglomerateDescriptor.getConglomerateName();
if (conglomerateName != null)
{
/* Have we found the desired index? */
if (conglomerateName.equals(userSpecifiedIndexName))
{
break;
}
}
}
/* We should always find a match */
if (SanityManager.DEBUG)
{
if (currentConglomerateDescriptor == null)
{
SanityManager.THROWASSERT(
"Expected to find match for forced index " +
userSpecifiedIndexName);
}
}
}
if ( ! super.nextAccessPath(optimizer,
predList,
rowOrdering))
{
if (SanityManager.DEBUG)
{
SanityManager.THROWASSERT("No join strategy found");
}
}
}
}
else
{
if (currentConglomerateDescriptor != null)
{
/*
** Once we have a conglomerate descriptor, cycle through
** the join strategies (done in parent).
*/
if ( ! super.nextAccessPath(optimizer,
predList,
rowOrdering))
{
/*
** When we're out of join strategies, go to the next
** conglomerate descriptor.
*/
currentConglomerateDescriptor = getNextConglom(currentConglomerateDescriptor);
/*
** New conglomerate, so step through join strategies
** again.
*/
resetJoinStrategies(optimizer);
if ( ! super.nextAccessPath(optimizer,
predList,
rowOrdering))
{
if (SanityManager.DEBUG)
{
SanityManager.THROWASSERT("No join strategy found");
}
}
}
}
else
{
/* Get the first conglomerate descriptor */
currentConglomerateDescriptor = getFirstConglom();
if ( ! super.nextAccessPath(optimizer,
predList,
rowOrdering))
{
if (SanityManager.DEBUG)
{
SanityManager.THROWASSERT("No join strategy found");
}
}
}
}
if (currentConglomerateDescriptor == null)
{
if ( optimizerTracingIsOn() ) { getOptimizerTracer().traceNoMoreConglomerates( tableNumber ); }
}
else
{
currentConglomerateDescriptor.setColumnNames(columnNames);
if ( optimizerTracingIsOn() ) { getOptimizerTracer().traceConsideringConglomerate( currentConglomerateDescriptor, tableNumber ); }
}
/*
** Tell the rowOrdering that what the ordering of this conglomerate is
*/
if (currentConglomerateDescriptor != null)
{
if ( ! currentConglomerateDescriptor.isIndex())
{
/* If we are scanning the heap, but there
* is a full match on a unique key, then
* we can say that the table IS NOT unordered.
* (We can't currently say what the ordering is
* though.)
*/
if (! isOneRowResultSet(predList))
{
if ( optimizerTracingIsOn() )
{ getOptimizerTracer().traceAddingUnorderedOptimizable( ((predList == null) ? 0 : predList.size()) ); }
rowOrdering.addUnorderedOptimizable(this);
}
else
{
if ( optimizerTracingIsOn() ) { getOptimizerTracer().traceScanningHeapWithUniqueKey(); }
}
}
else
{
IndexRowGenerator irg =
currentConglomerateDescriptor.getIndexDescriptor();
int[] baseColumnPositions = irg.baseColumnPositions();
boolean[] isAscending = irg.isAscending();
for (int i = 0; i < baseColumnPositions.length; i++)
{
/*
** Don't add the column to the ordering if it's already
** an ordered column. This can happen in the following
** case:
**
** create index ti on t(x, y);
** select * from t where x = 1 order by y;
**
** Column x is always ordered, so we want to avoid the
** sort when using index ti. This is accomplished by
** making column y appear as the first ordered column
** in the list.
*/
if ( ! rowOrdering.orderedOnColumn(isAscending[i] ?
RowOrdering.ASCENDING :
RowOrdering.DESCENDING,
getTableNumber(),
baseColumnPositions[i]))
{
rowOrdering.nextOrderPosition(isAscending[i] ?
RowOrdering.ASCENDING :
RowOrdering.DESCENDING);
rowOrdering.addOrderedColumn(isAscending[i] ?
RowOrdering.ASCENDING :
RowOrdering.DESCENDING,
getTableNumber(),
baseColumnPositions[i]);
}
}
}
}
ap.setConglomerateDescriptor(currentConglomerateDescriptor);
return currentConglomerateDescriptor != null;
}
/** Tell super-class that this Optimizable can be ordered */
@Override
protected boolean canBeOrdered()
{
return true;
}
/**
* @see org.apache.derby.iapi.sql.compile.Optimizable#optimizeIt
*
* @exception StandardException Thrown on error
*/
@Override
public CostEstimate optimizeIt(
Optimizer optimizer,
OptimizablePredicateList predList,
CostEstimate outerCost,
RowOrdering rowOrdering)
throws StandardException
{
optimizer.costOptimizable(
this,
tableDescriptor,
getCurrentAccessPath().getConglomerateDescriptor(),
predList,
outerCost);
// The cost that we found from the above call is now stored in the
// cost field of this FBT's current access path. So that's the
// cost we want to return here.
return getCurrentAccessPath().getCostEstimate();
}
/** @see org.apache.derby.iapi.sql.compile.Optimizable#getTableDescriptor */
@Override
public TableDescriptor getTableDescriptor()
{
return tableDescriptor;
}
/** @see org.apache.derby.iapi.sql.compile.Optimizable#isMaterializable
*
* @exception StandardException Thrown on error
*/
@Override
public boolean isMaterializable()
throws StandardException
{
/* base tables are always materializable */
return true;
}
/**
* @see org.apache.derby.iapi.sql.compile.Optimizable#pushOptPredicate
*
* @exception StandardException Thrown on error
*/
@Override
public boolean pushOptPredicate(OptimizablePredicate optimizablePredicate)
throws StandardException
{
if (SanityManager.DEBUG)
{
SanityManager.ASSERT(optimizablePredicate instanceof Predicate,
"optimizablePredicate expected to be instanceof Predicate");
}
/* Add the matching predicate to the restrictionList */
restrictionList.addPredicate((Predicate) optimizablePredicate);
return true;
}
/**
* @see org.apache.derby.iapi.sql.compile.Optimizable#pullOptPredicates
*
* @exception StandardException Thrown on error
*/
@Override
public void pullOptPredicates(
OptimizablePredicateList optimizablePredicates)
throws StandardException
{
for (int i = restrictionList.size() - 1; i >= 0; i--) {
optimizablePredicates.addOptPredicate(
restrictionList.getOptPredicate(i));
restrictionList.removeOptPredicate(i);
}
}
/**
* @see org.apache.derby.iapi.sql.compile.Optimizable#isCoveringIndex
* @exception StandardException Thrown on error
*/
@Override
public boolean isCoveringIndex(ConglomerateDescriptor cd) throws StandardException
{
boolean coveringIndex = true;
IndexRowGenerator irg;
int[] baseCols;
int colPos;
/* You can only be a covering index if you're an index */
if ( ! cd.isIndex())
return false;
irg = cd.getIndexDescriptor();
baseCols = irg.baseColumnPositions();
/* First we check to see if this is a covering index */
for (ResultColumn rc : getResultColumns())
{
/* Ignore unreferenced columns */
if (! rc.isReferenced())
{
continue;
}
/* Ignore constants - this can happen if all of the columns
* were projected out and we ended up just generating
* a "1" in RCL.doProject().
*/
if (rc.getExpression() instanceof ConstantNode)
{
continue;
}
coveringIndex = false;
colPos = rc.getColumnPosition();
/* Is this column in the index? */
for (int i = 0; i < baseCols.length; i++)
{
if (colPos == baseCols[i])
{
coveringIndex = true;
break;
}
}
/* No need to continue if the column was not in the index */
if (! coveringIndex)
{
break;
}
}
return coveringIndex;
}
/** @see org.apache.derby.iapi.sql.compile.Optimizable#verifyProperties
* @exception StandardException Thrown on error
*/
@Override
public void verifyProperties(DataDictionary dDictionary)
throws StandardException
{
if (tableProperties == null)
{
return;
}
/* Check here for:
* invalid properties key
* index and constraint properties
* non-existent index
* non-existent constraint
* invalid joinStrategy
* invalid value for hashInitialCapacity
* invalid value for hashLoadFactor
* invalid value for hashMaxCapacity
*/
boolean indexSpecified = false;
boolean constraintSpecified = false;
ConstraintDescriptor consDesc = null;
Enumeration<?> e = tableProperties.keys();
StringUtil.SQLEqualsIgnoreCase(tableDescriptor.getSchemaName(), "SYS");
while (e.hasMoreElements())
{
String key = (String) e.nextElement();
String value = (String) tableProperties.get(key);
if (key.equals("index"))
{
// User only allowed to specify 1 of index and constraint, not both
if (constraintSpecified)
{
throw StandardException.newException(SQLState.LANG_BOTH_FORCE_INDEX_AND_CONSTRAINT_SPECIFIED,
getBaseTableName());
}
indexSpecified = true;
/* Validate index name - NULL means table scan */
if (! StringUtil.SQLToUpperCase(value).equals("NULL"))
{
ConglomerateDescriptor cd = null;
ConglomerateDescriptor[] cds = tableDescriptor.getConglomerateDescriptors();
for (int index = 0; index < cds.length; index++)
{
cd = cds[index];
String conglomerateName = cd.getConglomerateName();
if (conglomerateName != null)
{
if (conglomerateName.equals(value))
{
break;
}
}
// Not a match, clear cd
cd = null;
}
// Throw exception if user specified index not found
if (cd == null)
{
throw StandardException.newException(SQLState.LANG_INVALID_FORCED_INDEX1,
value, getBaseTableName());
}
/* Query is dependent on the ConglomerateDescriptor */
getCompilerContext().createDependency(cd);
}
}
else if (key.equals("constraint"))
{
// User only allowed to specify 1 of index and constraint, not both
if (indexSpecified)
{
throw StandardException.newException(SQLState.LANG_BOTH_FORCE_INDEX_AND_CONSTRAINT_SPECIFIED,
getBaseTableName());
}
constraintSpecified = true;
if (! StringUtil.SQLToUpperCase(value).equals("NULL"))
{
consDesc =
dDictionary.getConstraintDescriptorByName(
tableDescriptor, (SchemaDescriptor)null, value,
false);
/* Throw exception if user specified constraint not found
* or if it does not have a backing index.
*/
if ((consDesc == null) || ! consDesc.hasBackingIndex())
{
throw StandardException.newException(SQLState.LANG_INVALID_FORCED_INDEX2,
value, getBaseTableName());
}
/* Query is dependent on the ConstraintDescriptor */
getCompilerContext().createDependency(consDesc);
}
}
else if (key.equals("joinStrategy"))
{
userSpecifiedJoinStrategy = StringUtil.SQLToUpperCase(value);
}
else if (key.equals("hashInitialCapacity"))
{
initialCapacity = getIntProperty(value, key);
// verify that the specified value is valid
if (initialCapacity <= 0)
{
throw StandardException.newException(SQLState.LANG_INVALID_HASH_INITIAL_CAPACITY,
String.valueOf(initialCapacity));
}
}
else if (key.equals("hashLoadFactor"))
{
try
{
loadFactor = Float.parseFloat(value);
}
catch (NumberFormatException nfe)
{
throw StandardException.newException(SQLState.LANG_INVALID_NUMBER_FORMAT_FOR_OVERRIDE,
value, key);
}
// verify that the specified value is valid
if (loadFactor <= 0.0 || loadFactor > 1.0)
{
throw StandardException.newException(SQLState.LANG_INVALID_HASH_LOAD_FACTOR,
value);
}
}
else if (key.equals("hashMaxCapacity"))
{
maxCapacity = getIntProperty(value, key);
// verify that the specified value is valid
if (maxCapacity <= 0)
{
throw StandardException.newException(SQLState.LANG_INVALID_HASH_MAX_CAPACITY,
String.valueOf(maxCapacity));
}
}
else if (key.equals("bulkFetch"))
{
bulkFetch = getIntProperty(value, key);
// verify that the specified value is valid
if (bulkFetch <= 0)
{
throw StandardException.newException(SQLState.LANG_INVALID_BULK_FETCH_VALUE,
String.valueOf(bulkFetch));
}
// no bulk fetch on updatable scans
if (forUpdate())
{
throw StandardException.newException(SQLState.LANG_INVALID_BULK_FETCH_UPDATEABLE);
}
}
else if (key.equals("validateCheckConstraint")) {
// the property "validateCheckConstraint" is read earlier
// cf. isValidatingCheckConstraint
}
else
{
// No other "legal" values at this time
throw StandardException.newException(SQLState.LANG_INVALID_FROM_TABLE_PROPERTY, key,
"index, constraint, joinStrategy");
}
}
/* If user specified a non-null constraint name(DERBY-1707), then
* replace it in the properties list with the underlying index name to
* simplify the code in the optimizer.
* NOTE: The code to get from the constraint name, for a constraint
* with a backing index, to the index name is convoluted. Given
* the constraint name, we can get the conglomerate id from the
* ConstraintDescriptor. We then use the conglomerate id to get
* the ConglomerateDescriptor from the DataDictionary and, finally,
* we get the index name (conglomerate name) from the ConglomerateDescriptor.
*/
if (constraintSpecified && consDesc != null)
{
ConglomerateDescriptor cd =
dDictionary.getConglomerateDescriptor(
consDesc.getConglomerateId());
String indexName = cd.getConglomerateName();
tableProperties.remove("constraint");
tableProperties.put("index", indexName);
}
}
private boolean isValidatingCheckConstraint() throws StandardException {
if (tableProperties == null) {
return false;
}
for (Enumeration<?> e = tableProperties.keys(); e.hasMoreElements();) {
String key = (String)e.nextElement();
String value = (String) tableProperties.get(key);
if (key.equals("validateCheckConstraint")) {
targetTableUUIDString = value;
validatingCheckConstraint = true;
return true;
}
}
return false;
}
/** @see org.apache.derby.iapi.sql.compile.Optimizable#getBaseTableName */
@Override
public String getBaseTableName()
{
return tableName.getTableName();
}
/** @see org.apache.derby.iapi.sql.compile.Optimizable#startOptimizing */
@Override
public void startOptimizing(Optimizer optimizer, RowOrdering rowOrdering)
{
AccessPath ap = getCurrentAccessPath();
AccessPath bestAp = getBestAccessPath();
AccessPath bestSortAp = getBestSortAvoidancePath();
ap.setConglomerateDescriptor((ConglomerateDescriptor) null);
bestAp.setConglomerateDescriptor((ConglomerateDescriptor) null);
bestSortAp.setConglomerateDescriptor((ConglomerateDescriptor) null);
ap.setCoveringIndexScan(false);
bestAp.setCoveringIndexScan(false);
bestSortAp.setCoveringIndexScan(false);
ap.setLockMode(0);
bestAp.setLockMode(0);
bestSortAp.setLockMode(0);
/*
** Only need to do this for current access path, because the
** costEstimate will be copied to the best access paths as
** necessary.
*/
CostEstimate costEst = getCostEstimate(optimizer);
ap.setCostEstimate(costEst);
/*
** This is the initial cost of this optimizable. Initialize it
** to the maximum cost so that the optimizer will think that
** any access path is better than none.
*/
costEst.setCost(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
super.startOptimizing(optimizer, rowOrdering);
}
/** @see org.apache.derby.iapi.sql.compile.Optimizable#convertAbsoluteToRelativeColumnPosition */
@Override
public int convertAbsoluteToRelativeColumnPosition(int absolutePosition)
{
return mapAbsoluteToRelativeColumnPosition(absolutePosition);
}
/**
* <p>
* Estimate the cost of scanning this {@code FromBaseTable} using the
* given predicate list with the given conglomerate.
* </p>
*
* <p>
* If the table contains little data, the cost estimate might be adjusted
* to make it more likely that an index scan will be preferred to a table
* scan, and a unique index will be preferred to a non-unique index. Even
* though such a plan may be slightly suboptimal when seen in isolation,
* using indexes, unique indexes in particular, needs fewer locks and
* allows more concurrency.
* </p>
*
* @see org.apache.derby.iapi.sql.compile.Optimizable#estimateCost
*
* @exception StandardException Thrown on error
*/
@Override
public CostEstimate estimateCost(OptimizablePredicateList predList,
ConglomerateDescriptor cd,
CostEstimate outerCost,
Optimizer optimizer,
RowOrdering rowOrdering)
throws StandardException
{
double cost;
boolean statisticsForTable = false;
boolean statisticsForConglomerate = false;
/* unknownPredicateList contains all predicates whose effect on
* cost/selectivity can't be calculated by the store.
*/
PredicateList unknownPredicateList = null;
if (optimizer.useStatistics() && predList != null)
{
/* if user has specified that we don't use statistics,
pretend that statistics don't exist.
*/
statisticsForConglomerate = tableDescriptor.statisticsExist(cd);
statisticsForTable = tableDescriptor.statisticsExist(null);
unknownPredicateList = new PredicateList(getContextManager());
predList.copyPredicatesToOtherList(unknownPredicateList);
// If not already done, check if this table has indexes and if
// their statistics need to get updated.
if (!hasCheckedIndexStats) {
hasCheckedIndexStats = true;
// Only mark if a base table and there are indexes. Skip VTIs,
// system tables, subqueries etc.
// The case where we have a table with a single-column unique
// index is pretty common, so avoid engaging the istat
// daemon if that's the only index on the table.
if (qualifiesForStatisticsUpdateCheck(tableDescriptor)) {
tableDescriptor.markForIndexStatsUpdate(baseRowCount());
}
}
}
AccessPath currAccessPath = getCurrentAccessPath();
JoinStrategy currentJoinStrategy =
currAccessPath.getJoinStrategy();
if ( optimizerTracingIsOn() ) { getOptimizerTracer().traceEstimatingCostOfConglomerate( cd, tableNumber ); }
/* Get the uniqueness factory for later use (see below) */
double tableUniquenessFactor =
optimizer.uniqueJoinWithOuterTable(predList);
boolean oneRowResultSetForSomeConglom = isOneRowResultSet(predList);
/* Get the predicates that can be used for scanning the base table */
baseTableRestrictionList.removeAllElements();
currentJoinStrategy.getBasePredicates(predList,
baseTableRestrictionList,
this);
/* RESOLVE: Need to figure out how to cache the StoreCostController */
StoreCostController scc = getStoreCostController(cd);
CostEstimate costEst = getScratchCostEstimate(optimizer);
/* First, get the cost for one scan */
/* Does the conglomerate match at most one row? */
if (isOneRowResultSet(cd, baseTableRestrictionList))
{
/*
** Tell the RowOrdering that this optimizable is always ordered.
** It will figure out whether it is really always ordered in the
** context of the outer tables and their orderings.
*/
rowOrdering.optimizableAlwaysOrdered(this);
singleScanRowCount = 1.0;
/* Yes, the cost is to fetch exactly one row */
// RESOLVE: NEED TO FIGURE OUT HOW TO GET REFERENCED COLUMN LIST,
// FIELD STATES, AND ACCESS TYPE
cost = scc.getFetchFromFullKeyCost(
(FormatableBitSet) null,
0);
if ( optimizerTracingIsOn() ) { getOptimizerTracer().traceSingleMatchedRowCost( cost, tableNumber ); }
costEst.setCost(cost, 1.0d, 1.0d);
/*
** Let the join strategy decide whether the cost of the base
** scan is a single scan, or a scan per outer row.
** NOTE: The multiplication should only be done against the
** total row count, not the singleScanRowCount.
*/
double newCost = costEst.getEstimatedCost();
if (currentJoinStrategy.multiplyBaseCostByOuterRows())
{
newCost *= outerCost.rowCount();
}
costEst.setCost(
newCost,
costEst.rowCount() * outerCost.rowCount(),
costEst.singleScanRowCount());
/*
** Choose the lock mode. If the start/stop conditions are
** constant, choose row locking, because we will always match
** the same row. If they are not constant (i.e. they include
** a join), we decide whether to do row locking based on
** the total number of rows for the life of the query.
*/
boolean constantStartStop = true;
for (int i = 0; i < predList.size(); i++)
{
OptimizablePredicate pred = predList.getOptPredicate(i);
/*
** The predicates are in index order, so the start and
** stop keys should be first.
*/
if ( ! (pred.isStartKey() || pred.isStopKey()))
{
break;
}
/* Stop when we've found a join */
if ( ! pred.getReferencedMap().hasSingleBitSet())
{
constantStartStop = false;
break;
}
}
if (constantStartStop)
{
currAccessPath.setLockMode(
TransactionController.MODE_RECORD);
if ( optimizerTracingIsOn() ) { getOptimizerTracer().traceConstantStartStopPositions(); }
}
else
{
setLockingBasedOnThreshold(optimizer, costEst.rowCount());
}
if (optimizerTracingIsOn()) {
getOptimizerTracer().traceCostOfNScans(
tableNumber,
outerCost.rowCount(),
costEst );
}
/* Add in cost of fetching base row for non-covering index */
if (cd.isIndex() && ( ! isCoveringIndex(cd) ) )
{
double singleFetchCost =
getBaseCostController().getFetchFromRowLocationCost(
(FormatableBitSet) null,
0);
// The estimated row count is always 1 here, although the
// index scan may actually return 0 rows, depending on whether
// or not the predicates match a key. It is assumed that a
// match is more likely than a miss, hence the row count is 1.
// Note (DERBY-6011): Alternative (non-unique) indexes may come
// up with row counts lower than 1 because they multiply with
// the selectivity, especially if the table is almost empty.
// This makes the optimizer prefer non-unique indexes if there
// are not so many rows in the table. We still want to use the
// unique index in that case, as the performance difference
// between the different scans on a small table is small, and
// the unique index is likely to lock fewer rows and reduce
// the chance of deadlocks. Therefore, we compensate by
// making the row count at least 1 for the non-unique index.
// See reference to DERBY-6011 further down in this method.
cost = singleFetchCost * costEst.rowCount();
costEst.setEstimatedCost(
costEst.getEstimatedCost() + cost);
if ( optimizerTracingIsOn() ) { getOptimizerTracer().traceNonCoveringIndexCost( cost, tableNumber ); }
}
}
else
{
/* Conglomerate might match more than one row */
/*
** Some predicates are good for start/stop, but we don't know
** the values they are being compared to at this time, so we
** estimate their selectivity in language rather than ask the
** store about them . The predicates on the first column of
** the conglomerate reduce the number of pages and rows scanned.
** The predicates on columns after the first reduce the number
** of rows scanned, but have a much smaller effect on the number
** of pages scanned, so we keep track of these selectivities in
** two separate variables: extraFirstColumnSelectivity and
** extraStartStopSelectivity. (Theoretically, we could try to
** figure out the effect of predicates after the first column
** on the number of pages scanned, but it's too hard, so we
** use these predicates only to reduce the estimated number of
** rows. For comparisons with known values, though, the store
** can figure out exactly how many rows and pages are scanned.)
**
** Other predicates are not good for start/stop. We keep track
** of their selectvities separately, because these limit the
** number of rows, but not the number of pages, and so need to
** be factored into the row count but not into the cost.
** These selectivities are factored into extraQualifierSelectivity.
**
** statStartStopSelectivity (using statistics) represents the
** selectivity of start/stop predicates that can be used to scan
** the index. If no statistics exist for the conglomerate then
** the value of this variable remains at 1.0
**
** statCompositeSelectivity (using statistics) represents the
** selectivity of all the predicates (including NonBaseTable
** predicates). This represents the most educated guess [among
** all the wild surmises in this routine] as to the number
** of rows that will be returned from this joinNode.
** If no statistics exist on the table or no statistics at all
** can be found to satisfy the predicates at this join opertor,
** then statCompositeSelectivity is left initialized at 1.0
*/
double extraFirstColumnSelectivity = 1.0d;
double extraStartStopSelectivity = 1.0d;
double extraQualifierSelectivity = 1.0d;
double extraNonQualifierSelectivity = 1.0d;
double statStartStopSelectivity = 1.0d;
double statCompositeSelectivity = 1.0d;
int numExtraFirstColumnPreds = 0;
int numExtraStartStopPreds = 0;
int numExtraQualifiers = 0;
int numExtraNonQualifiers = 0;
/*
** It is possible for something to be a start or stop predicate
** without it being possible to use it as a key for cost estimation.
** For example, with an index on (c1, c2), and the predicate
** c1 = othertable.c3 and c2 = 1, the comparison on c1 is with
** an unknown value, so we can't pass it to the store. This means
** we can't pass the comparison on c2 to the store, either.
**
** The following booleans keep track of whether we have seen
** gaps in the keys we can pass to the store.
*/
boolean startGap = false;
boolean stopGap = false;
boolean seenFirstColumn = false;
/*
** We need to figure out the number of rows touched to decide
** whether to use row locking or table locking. If the start/stop
** conditions are constant (i.e. no joins), the number of rows
** touched is the number of rows per scan. But if the start/stop
** conditions contain a join, the number of rows touched must
** take the number of outer rows into account.
*/
boolean constantStartStop = true;
boolean startStopFound = false;
/* Count the number of start and stop keys */
int startKeyNum = 0;
int stopKeyNum = 0;
OptimizablePredicate pred;
int predListSize;
if (predList != null)
predListSize = baseTableRestrictionList.size();
else
predListSize = 0;
int startStopPredCount = 0;
ColumnReference firstColumn = null;
for (int i = 0; i < predListSize; i++)
{
pred = baseTableRestrictionList.getOptPredicate(i);
boolean startKey = pred.isStartKey();
boolean stopKey = pred.isStopKey();
if (startKey || stopKey)
{
startStopFound = true;
if ( ! pred.getReferencedMap().hasSingleBitSet())
{
constantStartStop = false;
}
boolean knownConstant =
pred.compareWithKnownConstant(this, true);
if (startKey)
{
if (knownConstant && ( ! startGap ) )
{
startKeyNum++;
if (unknownPredicateList != null)
unknownPredicateList.removeOptPredicate(pred);
}
else
{
startGap = true;
}
}
if (stopKey)
{
if (knownConstant && ( ! stopGap ) )
{
stopKeyNum++;
if (unknownPredicateList != null)
unknownPredicateList.removeOptPredicate(pred);
}
else
{
stopGap = true;
}
}
/* If either we are seeing startGap or stopGap because start/stop key is
* comparison with non-constant, we should multiply the selectivity to
* extraFirstColumnSelectivity. Beetle 4787.
*/
if (startGap || stopGap)
{
// Don't include redundant join predicates in selectivity calculations
if (baseTableRestrictionList.isRedundantPredicate(i))
continue;
if (startKey && stopKey)
startStopPredCount++;
if (pred.getIndexPosition() == 0)
{
extraFirstColumnSelectivity *=
pred.selectivity(this);
if (! seenFirstColumn)
{
ValueNode relNode = ((Predicate) pred).getAndNode().getLeftOperand();
if (relNode instanceof BinaryRelationalOperatorNode)
firstColumn = ((BinaryRelationalOperatorNode) relNode).getColumnOperand(this);
seenFirstColumn = true;
}
}
else
{
extraStartStopSelectivity *= pred.selectivity(this);
numExtraStartStopPreds++;
}
}
}
else
{
// Don't include redundant join predicates in selectivity calculations
if (baseTableRestrictionList.isRedundantPredicate(i))
{
continue;
}
/* If we have "like" predicate on the first index column, it is more likely
* to have a smaller range than "between", so we apply extra selectivity 0.2
* here. beetle 4387, 4787.
*/
if (pred instanceof Predicate)
{
ValueNode leftOpnd = ((Predicate) pred).getAndNode().getLeftOperand();
if (firstColumn != null && leftOpnd instanceof LikeEscapeOperatorNode)
{
LikeEscapeOperatorNode likeNode = (LikeEscapeOperatorNode) leftOpnd;
if (likeNode.getLeftOperand().requiresTypeFromContext())
{
ValueNode receiver = ((TernaryOperatorNode) likeNode).getReceiver();
if (receiver instanceof ColumnReference)
{
ColumnReference cr = (ColumnReference) receiver;
if (cr.getTableNumber() == firstColumn.getTableNumber() &&
cr.getColumnNumber() == firstColumn.getColumnNumber())
extraFirstColumnSelectivity *= 0.2;
}
}
}
}
if (pred.isQualifier())
{
extraQualifierSelectivity *= pred.selectivity(this);
numExtraQualifiers++;
}
else
{
extraNonQualifierSelectivity *= pred.selectivity(this);
numExtraNonQualifiers++;
}
/*
** Strictly speaking, it shouldn't be necessary to
** indicate a gap here, since there should be no more
** start/stop predicates, but let's do it, anyway.
*/
startGap = true;
stopGap = true;
}
}
if (unknownPredicateList != null)
{
statCompositeSelectivity = unknownPredicateList.selectivity(this);
if (statCompositeSelectivity == -1.0d)
statCompositeSelectivity = 1.0d;
}
if (seenFirstColumn && (startStopPredCount > 0))
{
if (statisticsForConglomerate) {
statStartStopSelectivity =
tableDescriptor.selectivityForConglomerate(cd,
startStopPredCount);
} else if (cd.isIndex()) {
//DERBY-3790 (Investigate if request for update
// statistics can be skipped for certain kind of
// indexes, one instance may be unique indexes based
// on one column.) But as found in DERBY-6045 (in list
// multi-probe by primary key not chosen on tables with
// >256 rows), even though we do not keep the
// statistics for single-column unique indexes, we
// should improve the selectivity of such an index
// when the index is being considered by the optimizer.
IndexRowGenerator irg = cd.getIndexDescriptor();
if (irg.isUnique()
&& irg.numberOfOrderedColumns() == 1
&& startStopPredCount == 1) {
statStartStopSelectivity = (1/(double)baseRowCount());
}
}
}
/*
** Factor the non-base-table predicates into the extra
** non-qualifier selectivity, since these will restrict the
** number of rows, but not the cost.
*/
extraNonQualifierSelectivity *=
currentJoinStrategy.nonBasePredicateSelectivity(this, predList);
/* Create the start and stop key arrays, and fill them in */
DataValueDescriptor[] startKeys;
DataValueDescriptor[] stopKeys;
if (startKeyNum > 0)
startKeys = new DataValueDescriptor[startKeyNum];
else
startKeys = null;
if (stopKeyNum > 0)
stopKeys = new DataValueDescriptor[stopKeyNum];
else
stopKeys = null;
startKeyNum = 0;
stopKeyNum = 0;
startGap = false;
stopGap = false;
/* If we have a probe predicate that is being used as a start/stop
* key then ssKeySourceInList will hold the InListOperatorNode
* from which the probe predicate was built.
*/
InListOperatorNode ssKeySourceInList = null;
for (int i = 0; i < predListSize; i++)
{
pred = baseTableRestrictionList.getOptPredicate(i);
boolean startKey = pred.isStartKey();
boolean stopKey = pred.isStopKey();
if (startKey || stopKey)
{
/* A probe predicate is only useful if it can be used as
* as a start/stop key for _first_ column in an index
* (i.e. if the column position is 0). That said, we only
* allow a single start/stop key per column position in
* the index (see PredicateList.orderUsefulPredicates()).
* Those two facts combined mean that we should never have
* more than one probe predicate start/stop key for a given
* conglomerate.
*/
if (SanityManager.DEBUG)
{
if ((ssKeySourceInList != null) &&
((Predicate)pred).isInListProbePredicate())
{
SanityManager.THROWASSERT(
"Found multiple probe predicate start/stop keys" +
" for conglomerate '" + cd.getConglomerateName() +
"' when at most one was expected.");
}
}
/* By passing "true" in the next line we indicate that we
* should only retrieve the underlying InListOpNode *if*
* the predicate is a "probe predicate".
*/
ssKeySourceInList = ((Predicate)pred).getSourceInList(true);
boolean knownConstant = pred.compareWithKnownConstant(this, true);
if (startKey)
{
if (knownConstant && ( ! startGap ) )
{
startKeys[startKeyNum] = pred.getCompareValue(this);
startKeyNum++;
}
else
{
startGap = true;
}
}
if (stopKey)
{
if (knownConstant && ( ! stopGap ) )
{
stopKeys[stopKeyNum] = pred.getCompareValue(this);
stopKeyNum++;
}
else
{
stopGap = true;
}
}
}
else
{
startGap = true;
stopGap = true;
}
}
int startOperator;
int stopOperator;
if (baseTableRestrictionList != null)
{
startOperator = baseTableRestrictionList.startOperator(this);
stopOperator = baseTableRestrictionList.stopOperator(this);
}
else
{
/*
** If we're doing a full scan, it doesn't matter what the
** start and stop operators are.
*/
startOperator = ScanController.NA;
stopOperator = ScanController.NA;
}
/*
** Get a row template for this conglomerate. For now, just tell
** it we are using all the columns in the row.
*/
DataValueDescriptor[] rowTemplate =
getRowTemplate(cd, getBaseCostController());
/* we prefer index than table scan for concurrency reason, by a small
* adjustment on estimated row count. This affects optimizer's decision
* especially when few rows are in table. beetle 5006. This makes sense
* since the plan may stay long before we actually check and invalidate it.
* And new rows may be inserted before we check and invalidate the plan.
* Here we only prefer index that has start/stop key from predicates. Non-
* constant start/stop key case is taken care of by selectivity later.
*/
long baseRC = (startKeys != null || stopKeys != null) ? baseRowCount() : baseRowCount() + 5;
scc.getScanCost(
currentJoinStrategy.scanCostType(),
baseRC,
1,
forUpdate(),
(FormatableBitSet) null,
rowTemplate,
startKeys,
startOperator,
stopKeys,
stopOperator,
false,
0,
costEst);
/* initialPositionCost is the first part of the index scan cost we get above.
* It's the cost of initial positioning/fetch of key. So it's unrelated to
* row count of how many rows we fetch from index. We extract it here so that
* we only multiply selectivity to the other part of index scan cost, which is
* nearly linear, to make cost calculation more accurate and fair, especially
* compared to the plan of "one row result set" (unique index). beetle 4787.
*/
double initialPositionCost = 0.0;
if (cd.isIndex())
{
initialPositionCost = scc.getFetchFromFullKeyCost((FormatableBitSet) null, 0);
/* oneRowResultSetForSomeConglom means there's a unique index, but certainly
* not this one since we are here. If store knows this non-unique index
* won't return any row or just returns one row (eg., the predicate is a
* comparison with constant or almost empty table), we do minor adjustment
* on cost (affecting decision for covering index) and rc (decision for
* non-covering). The purpose is favoring unique index. beetle 5006.
*/
if (oneRowResultSetForSomeConglom && costEst.rowCount() <= 1)
{
costEst.setCost(costEst.getEstimatedCost() * 2,
costEst.rowCount() + 2,
costEst.singleScanRowCount() + 2);
}
}
if ( optimizerTracingIsOn() )
{
getOptimizerTracer().traceCostOfConglomerateScan
(
tableNumber,
cd,
costEst,
numExtraFirstColumnPreds,
extraFirstColumnSelectivity,
numExtraStartStopPreds,
extraStartStopSelectivity,
startStopPredCount,
statStartStopSelectivity,
numExtraQualifiers,
extraQualifierSelectivity,
numExtraNonQualifiers,
extraNonQualifierSelectivity
);
}
/* initial row count is the row count without applying
any predicates-- we use this at the end of the routine
when we use statistics to recompute the row count.
*/
double initialRowCount = costEst.rowCount();
if (statStartStopSelectivity != 1.0d)
{
/*
** If statistics exist use the selectivity computed
** from the statistics to calculate the cost.
** NOTE: we apply this selectivity to the cost as well
** as both the row counts. In the absence of statistics
** we only applied the FirstColumnSelectivity to the
** cost.
*/
costEst.setCost(
scanCostAfterSelectivity(costEst.getEstimatedCost(),
initialPositionCost,
statStartStopSelectivity,
oneRowResultSetForSomeConglom),
costEst.rowCount() * statStartStopSelectivity,
costEst.singleScanRowCount() *
statStartStopSelectivity);
if (optimizerTracingIsOn()) {
getOptimizerTracer().
traceCostIncludingStatsForIndex(costEst, tableNumber);
}
}
else
{
/*
** Factor in the extra selectivity on the first column
** of the conglomerate (see comment above).
** NOTE: In this case we want to apply the selectivity to both
** the total row count and singleScanRowCount.
*/
if (extraFirstColumnSelectivity != 1.0d)
{
costEst.setCost(
scanCostAfterSelectivity(costEst.getEstimatedCost(),
initialPositionCost,
extraFirstColumnSelectivity,
oneRowResultSetForSomeConglom),
costEst.rowCount() * extraFirstColumnSelectivity,
costEst.singleScanRowCount() *
extraFirstColumnSelectivity);
if (optimizerTracingIsOn()) {
getOptimizerTracer().
traceCostIncludingExtra1stColumnSelectivity(
costEst, tableNumber);
}
}
/* Factor in the extra start/stop selectivity (see comment above).
* NOTE: In this case we want to apply the selectivity to both
* the row count and singleScanRowCount.
*/
if (extraStartStopSelectivity != 1.0d)
{
costEst.setCost(
costEst.getEstimatedCost(),
costEst.rowCount() * extraStartStopSelectivity,
costEst.singleScanRowCount() *
extraStartStopSelectivity);
if (optimizerTracingIsOn()) {
getOptimizerTracer().traceCostIncludingExtraStartStop(
costEst, tableNumber);
}
}
}
/* If the start and stop key came from an IN-list "probe predicate"
* then we need to adjust the cost estimate. The probe predicate
* is of the form "col = ?" and we currently have the estimated
* cost of probing the index a single time for "?". But with an
* IN-list we don't just probe the index once; we're going to
* probe it once for every value in the IN-list. And we are going
* to potentially return an additional row (or set of rows) for
* each probe. To account for this "multi-probing" we take the
* costEstimate and multiply each of its fields by the size of
* the IN-list.
*
* Note: If the IN-list has duplicate values then this simple
* multiplication could give us an elevated cost (because we
* only probe the index for each *non-duplicate* value in the
* IN-list). But for now, we're saying that's okay.
*/
if (ssKeySourceInList != null)
{
int listSize = ssKeySourceInList.getRightOperandList().size();
double rc = costEst.rowCount() * listSize;
double ssrc = costEst.singleScanRowCount() * listSize;
/* If multiplication by listSize returns more rows than are
* in the scan then just use the number of rows in the scan.
*/
costEst.setCost(
costEst.getEstimatedCost() * listSize,
rc > initialRowCount ? initialRowCount : rc,
ssrc > initialRowCount ? initialRowCount : ssrc);
}
/*
** Figure out whether to do row locking or table locking.
**
** If there are no start/stop predicates, we're doing full
** conglomerate scans, so do table locking.
*/
if (! startStopFound)
{
currAccessPath.setLockMode(
TransactionController.MODE_TABLE);
if ( optimizerTracingIsOn() ) { getOptimizerTracer().traceNoStartStopPosition(); }
}
else
{
/*
** Figure out the number of rows touched. If all the
** start/stop predicates are constant, the number of
** rows touched is the number of rows per scan.
** This is also true for join strategies that scan the
** inner table only once (like hash join) - we can
** tell if we have one of those, because
** multiplyBaseCostByOuterRows() will return false.
*/
double rowsTouched = costEst.rowCount();
if ( (! constantStartStop) &&
currentJoinStrategy.multiplyBaseCostByOuterRows())
{
/*
** This is a join where the inner table is scanned
** more than once, so we have to take the number
** of outer rows into account. The formula for this
** works out as follows:
**
** total rows in table = r
** number of rows touched per scan = s
** number of outer rows = o
** proportion of rows touched per scan = s / r
** proportion of rows not touched per scan =
** 1 - (s / r)
** proportion of rows not touched for all scans =
** (1 - (s / r)) ** o
** proportion of rows touched for all scans =
** 1 - ((1 - (s / r)) ** o)
** total rows touched for all scans =
** r * (1 - ((1 - (s / r)) ** o))
**
** In doing these calculations, we must be careful not
** to divide by zero. This could happen if there are
** no rows in the table. In this case, let's do table
** locking.
*/
double r = baseRowCount();
if (r > 0.0)
{
double s = costEst.rowCount();
double o = outerCost.rowCount();
double pRowsNotTouchedPerScan = 1.0 - (s / r);
double pRowsNotTouchedAllScans =
Math.pow(pRowsNotTouchedPerScan, o);
double pRowsTouchedAllScans =
1.0 - pRowsNotTouchedAllScans;
double rowsTouchedAllScans =
r * pRowsTouchedAllScans;
rowsTouched = rowsTouchedAllScans;
}
else
{
/* See comments in setLockingBasedOnThreshold */
rowsTouched = optimizer.tableLockThreshold() + 1;
}
}
setLockingBasedOnThreshold(optimizer, rowsTouched);
}
/*
** If the index isn't covering, add the cost of getting the
** base row. Only apply extraFirstColumnSelectivity and extraStartStopSelectivity
** before we do this, don't apply extraQualifierSelectivity etc. The
** reason is that the row count here should be the number of index rows
** (and hence heap rows) we get, and we need to fetch all those rows, even
** though later on some of them may be filtered out by other predicates.
** beetle 4787.
*/
if (cd.isIndex() && ( ! isCoveringIndex(cd) ) )
{
double singleFetchCost =
getBaseCostController().getFetchFromRowLocationCost(
(FormatableBitSet) null,
0);
// The number of rows we expect to fetch from the base table.
double rowsToFetch = costEst.rowCount();
if (oneRowResultSetForSomeConglom) {
// DERBY-6011: We know that there is a unique index, and
// that there are predicates that guarantee that at most
// one row will be fetched from the unique index. The
// unique alternative always has 1 as estimated row count
// (see reference to DERBY-6011 further up in this method),
// even though it could actually return 0 rows.
//
// If the alternative that's being considered here has
// expected row count less than 1, it is going to have
// lower estimated cost for fetching base rows. We prefer
// unique indexes, as they lock fewer rows and allow more
// concurrency. Therefore, make sure the cost estimate for
// this alternative includes at least fetching one row from
// the base table.
rowsToFetch = Math.max(1.0d, rowsToFetch);
}
cost = singleFetchCost * rowsToFetch;
costEst.setEstimatedCost(
costEst.getEstimatedCost() + cost);
if (optimizerTracingIsOn()) {
getOptimizerTracer().traceCostOfNoncoveringIndex(
costEst, tableNumber);
}
}
/* Factor in the extra qualifier selectivity (see comment above).
* NOTE: In this case we want to apply the selectivity to both
* the row count and singleScanRowCount.
*/
if (extraQualifierSelectivity != 1.0d)
{
costEst.setCost(
costEst.getEstimatedCost(),
costEst.rowCount() * extraQualifierSelectivity,
costEst.singleScanRowCount() *
extraQualifierSelectivity);
if (optimizerTracingIsOn()) {
getOptimizerTracer().
traceCostIncludingExtraQualifierSelectivity(
costEst, tableNumber);
}
}
singleScanRowCount = costEst.singleScanRowCount();
/*
** Let the join strategy decide whether the cost of the base
** scan is a single scan, or a scan per outer row.
** NOTE: In this case we only want to multiply against the
** total row count, not the singleScanRowCount.
** NOTE: Do not multiply row count if we determined that
** conglomerate is a 1 row result set when costing nested
** loop. (eg, we will find at most 1 match when probing
** the hash table.)
*/
double newCost = costEst.getEstimatedCost();
double rowCnt = costEst.rowCount();
/*
** RESOLVE - If there is a unique index on the joining
** columns, the number of matching rows will equal the
** number of outer rows, even if we're not considering the
** unique index for this access path. To figure that out,
** however, would require an analysis phase at the beginning
** of optimization. So, we'll always multiply the number
** of outer rows by the number of rows per scan. This will
** give us a higher than actual row count when there is
** such a unique index, which will bias the optimizer toward
** using the unique index. This is probably OK most of the
** time, since the optimizer would probably choose the
** unique index, anyway. But it would be better if the
** optimizer set the row count properly in this case.
*/
if (currentJoinStrategy.multiplyBaseCostByOuterRows())
{
newCost *= outerCost.rowCount();
}
rowCnt *= outerCost.rowCount();
initialRowCount *= outerCost.rowCount();
/*
** If this table can generate at most one row per scan,
** the maximum row count is the number of outer rows.
** NOTE: This does not completely take care of the RESOLVE
** in the above comment, since it will only notice
** one-row result sets for the current join order.
*/
if (oneRowResultSetForSomeConglom)
{
if (outerCost.rowCount() < rowCnt)
{
rowCnt = outerCost.rowCount();
}
}
/*
** The estimated cost may be too high for indexes, if the
** estimated row count exceeds the maximum. Only do this
** if we're not doing a full scan, and the start/stop position
** is not constant (i.e. we're doing a join on the first column
** of the index) - the reason being that this is when the
** cost may be inaccurate.
*/
if (cd.isIndex() && startStopFound && ( ! constantStartStop ) )
{
/*
** Does any table outer to this one have a unique key on
** a subset of the joining columns? If so, the maximum number
** of rows that this table can return is the number of rows
** in this table times the number of times the maximum number
** of times each key can be repeated.
*/
double scanUniquenessFactor =
optimizer.uniqueJoinWithOuterTable(baseTableRestrictionList);
if (scanUniquenessFactor > 0.0)
{
/*
** A positive uniqueness factor means there is a unique
** outer join key. The value is the reciprocal of the
** maximum number of duplicates for each unique key
** (the duplicates can be caused by other joining tables).
*/
double maxRows =
((double) baseRowCount()) / scanUniquenessFactor;
if (rowCnt > maxRows)
{
/*
** The estimated row count is too high. Adjust the
** estimated cost downwards proportionately to
** match the maximum number of rows.
*/
newCost *= (maxRows / rowCnt);
}
}
}
/* The estimated total row count may be too high */
if (tableUniquenessFactor > 0.0)
{
/*
** A positive uniqueness factor means there is a unique outer
** join key. The value is the reciprocal of the maximum number
** of duplicates for each unique key (the duplicates can be
** caused by other joining tables).
*/
double maxRows =
((double) baseRowCount()) / tableUniquenessFactor;
if (rowCnt > maxRows)
{
/*
** The estimated row count is too high. Set it to the
** maximum row count.
*/
rowCnt = maxRows;
}
}
costEst.setCost(
newCost,
rowCnt,
costEst.singleScanRowCount());
if (optimizerTracingIsOn()) {
getOptimizerTracer().traceCostOfNScans(
tableNumber, outerCost.rowCount(), costEst);
}
/*
** Now figure in the cost of the non-qualifier predicates.
** existsBaseTables have a row count of 1
*/
double rc = -1, src = -1;
if (existsBaseTable)
rc = src = 1;
// don't factor in extraNonQualifierSelectivity in case of oneRowResultSetForSomeConglom
// because "1" is the final result and the effect of other predicates already considered
// beetle 4787
else if (extraNonQualifierSelectivity != 1.0d)
{
rc = oneRowResultSetForSomeConglom ?
costEst.rowCount() :
costEst.rowCount() * extraNonQualifierSelectivity;
src = costEst.singleScanRowCount() *
extraNonQualifierSelectivity;
}
if (rc != -1) // changed
{
costEst.setCost(costEst.getEstimatedCost(), rc, src);
if (optimizerTracingIsOn()) {
getOptimizerTracer().
traceCostIncludingExtraNonQualifierSelectivity(
costEst, tableNumber);
}
}
recomputeRowCount:
if (statisticsForTable && !oneRowResultSetForSomeConglom &&
(statCompositeSelectivity != 1.0d))
{
/* if we have statistics we should use statistics to calculate
row count-- if it has been determined that this table
returns one row for some conglomerate then there is no need
to do this recalculation
*/
double compositeStatRC = initialRowCount * statCompositeSelectivity;
if ( optimizerTracingIsOn() )
{ getOptimizerTracer().traceCompositeSelectivityFromStatistics( statCompositeSelectivity ); }
if (tableUniquenessFactor > 0.0)
{
/* If the row count from the composite statistics
comes up more than what the table uniqueness
factor indicates then lets stick with the current
row count.
*/
if (compositeStatRC > (baseRowCount() *
tableUniquenessFactor))
{
break recomputeRowCount;
}
}
/* set the row count and the single scan row count
to the initialRowCount. initialRowCount is the product
of the RC from store * RC of the outerCost.
Thus RC = initialRowCount * the selectivity from stats.
SingleRC = RC / outerCost.rowCount().
*/
costEst.setCost(costEst.getEstimatedCost(),
compositeStatRC,
(existsBaseTable) ?
1 :
compositeStatRC / outerCost.rowCount());
if (optimizerTracingIsOn()) {
getOptimizerTracer().
traceCostIncludingCompositeSelectivityFromStats(
costEst, tableNumber);
}
}
}
/* Put the base predicates back in the predicate list */
currentJoinStrategy.putBasePredicates(predList,
baseTableRestrictionList);
return costEst;
}
private double scanCostAfterSelectivity(double originalScanCost,
double initialPositionCost,
double selectivity,
boolean anotherIndexUnique)
throws StandardException
{
/* If there's another paln using unique index, its selectivity is 1/r
* because we use row count 1. This plan is not unique index, so we make
* selectivity at least 2/r, which is more fair, because for unique index
* we don't use our selectivity estimates. Unique index also more likely
* locks less rows, hence better concurrency. beetle 4787.
*/
if (anotherIndexUnique)
{
double r = baseRowCount();
if (r > 0.0)
{
double minSelectivity = 2.0 / r;
if (minSelectivity > selectivity)
selectivity = minSelectivity;
}
}
/* initialPositionCost is the first part of the index scan cost we get above.
* It's the cost of initial positioning/fetch of key. So it's unrelated to
* row count of how many rows we fetch from index. We extract it here so that
* we only multiply selectivity to the other part of index scan cost, which is
* nearly linear, to make cost calculation more accurate and fair, especially
* compared to the plan of "one row result set" (unique index). beetle 4787.
*/
double afterInitialCost = (originalScanCost - initialPositionCost) *
selectivity;
if (afterInitialCost < 0)
afterInitialCost = 0;
return initialPositionCost + afterInitialCost;
}
private void setLockingBasedOnThreshold(
Optimizer optimizer, double rowsTouched)
{
/* In optimizer we always set it to row lock (unless there's no
* start/stop key found to utilize an index, in which case we do table
* lock), it's up to store to upgrade it to table lock. This makes
* sense for the default read committed isolation level and update
* lock. For more detail, see Beetle 4133.
*/
getCurrentAccessPath().setLockMode(
TransactionController.MODE_RECORD);
}
/** @see org.apache.derby.iapi.sql.compile.Optimizable#isBaseTable */
@Override
public boolean isBaseTable()
{
return true;
}
/** @see org.apache.derby.iapi.sql.compile.Optimizable#forUpdate */
@Override
public boolean forUpdate()
{
/* This table is updatable if it is the
* target table of an update or delete,
* or it is (or was) the target table of an
* updatable cursor.
*/
return (updateOrDelete != 0) || isCursorTargetTable() || getUpdateLocks;
}
/** @see org.apache.derby.iapi.sql.compile.Optimizable#initialCapacity */
@Override
public int initialCapacity()
{
return initialCapacity;
}
/** @see org.apache.derby.iapi.sql.compile.Optimizable#loadFactor */
@Override
public float loadFactor()
{
return loadFactor;
}
/**
* @see org.apache.derby.iapi.sql.compile.Optimizable#memoryUsageOK
*/
@Override
public boolean memoryUsageOK(double rowCount, int maxMemoryPerTable)
throws StandardException
{
return super.memoryUsageOK(singleScanRowCount, maxMemoryPerTable);
}
/**
* @see org.apache.derby.iapi.sql.compile.Optimizable#isTargetTable
*/
@Override
public boolean isTargetTable()
{
return (updateOrDelete != 0);
}
/**
* @see org.apache.derby.iapi.sql.compile.Optimizable#uniqueJoin
*/
@Override
public double uniqueJoin(OptimizablePredicateList predList)
throws StandardException
{
double retval = -1.0;
PredicateList pl = (PredicateList) predList;
int numColumns = getTableDescriptor().getNumberOfColumns();
int tableNo = getTableNumber();
// This is supposed to be an array of table numbers for the current
// query block. It is used to determine whether a join is with a
// correlation column, to fill in eqOuterCols properly. We don't care
// about eqOuterCols, so just create a zero-length array, pretending
// that all columns are correlation columns.
int[] tableNumbers = new int[0];
JBitSet[] tableColMap = new JBitSet[1];
tableColMap[0] = new JBitSet(numColumns + 1);
pl.checkTopPredicatesForEqualsConditions(tableNo,
null,
tableNumbers,
tableColMap,
false);
if (supersetOfUniqueIndex(tableColMap))
{
retval =
getBestAccessPath().getCostEstimate().singleScanRowCount();
}
return retval;
}
/**
* @see org.apache.derby.iapi.sql.compile.Optimizable#isOneRowScan
*
* @exception StandardException Thrown on error
*/
@Override
public boolean isOneRowScan()
throws StandardException
{
/* EXISTS FBT will never be a 1 row scan.
* Otherwise call method in super class.
*/
if (existsBaseTable)
{
return false;
}
return super.isOneRowScan();
}
/**
* @see org.apache.derby.iapi.sql.compile.Optimizable#legalJoinOrder
*/
@Override
public boolean legalJoinOrder(JBitSet assignedTableMap)
{
// Only an issue for EXISTS FBTs
if (existsBaseTable)
{
/* Have all of our dependencies been satisfied? */
return assignedTableMap.contains(dependencyMap);
}
return true;
}
/**
* Convert this object to a String. See comments in QueryTreeNode.java
* for how this should be done for tree printing.
*
* @return This object as a String
*/
@Override
public String toString()
{
if (SanityManager.DEBUG)
{
return "tableName: " +
(tableName != null ? tableName.toString() : "null") + "\n" +
"tableDescriptor: " + tableDescriptor + "\n" +
"updateOrDelete: " + updateOrDelete + "\n" +
(tableProperties != null ?
tableProperties.toString() : "null") + "\n" +
"existsBaseTable: " + existsBaseTable + "\n" +
"dependencyMap: " +
(dependencyMap != null
? dependencyMap.toString()
: "null") + "\n" +
super.toString();
}
else
{
return "";
}
}
/**
* Does this FBT represent an EXISTS FBT.
*
* @return Whether or not this FBT represents
* an EXISTS FBT.
*/
boolean getExistsBaseTable()
{
return existsBaseTable;
}
/**
* Set whether or not this FBT represents an
* EXISTS FBT.
*
* @param existsBaseTable Whether or not an EXISTS FBT.
* @param dependencyMap The dependency map for the EXISTS FBT.
* @param isNotExists Whether or not for NOT EXISTS, more specifically.
*/
void setExistsBaseTable(boolean existsBaseTable, JBitSet dependencyMap, boolean isNotExists)
{
this.existsBaseTable = existsBaseTable;
this.isNotExists = isNotExists;
/* Set/clear the dependency map as needed */
if (existsBaseTable)
{
this.dependencyMap = dependencyMap;
}
else
{
this.dependencyMap = null;
}
}
/**
* Clear the bits from the dependency map when join nodes are flattened
*
* @param locations list of bit numbers to be cleared
*/
void clearDependency(List<Integer> locations)
{
if (this.dependencyMap != null)
{
for (int i = 0; i < locations.size() ; i++)
this.dependencyMap.clear(locations.get(i).intValue());
}
}
/**
* Set the table properties for this table.
*
* @param tableProperties The new table properties.
*/
void setTableProperties(Properties tableProperties)
{
this.tableProperties = tableProperties;
}
/**
* Bind the table in this FromBaseTable.
* This is where view resolution occurs
*
* @param dataDictionary The DataDictionary to use for binding
* @param fromListParam FromList to use/append to.
*
* @return ResultSetNode The FromTable for the table or resolved view.
*
* @exception StandardException Thrown on error
*/
@Override
ResultSetNode bindNonVTITables(DataDictionary dataDictionary,
FromList fromListParam)
throws StandardException
{
tableName.bind();
TableDescriptor tabDescr = bindTableDescriptor();
if (tabDescr.getTableType() == TableDescriptor.VTI_TYPE) {
ResultSetNode vtiNode = mapTableAsVTI(
tabDescr,
getCorrelationName(),
getResultColumns(),
getProperties(),
getContextManager());
return vtiNode.bindNonVTITables(dataDictionary, fromListParam);
}
ResultColumnList derivedRCL = getResultColumns();
// make sure there's a restriction list
restrictionList = new PredicateList(getContextManager());
baseTableRestrictionList = new PredicateList(getContextManager());
CompilerContext compilerContext = getCompilerContext();
/* Generate the ResultColumnList */
setResultColumns( genResultColList() );
templateColumns = getResultColumns();
/* Resolve the view, if this is a view */
if (tabDescr.getTableType() == TableDescriptor.VIEW_TYPE)
{
FromSubquery fsq;
ResultSetNode rsn;
ViewDescriptor vd;
CreateViewNode cvn;
SchemaDescriptor compSchema;
/* Get the associated ViewDescriptor so that we can get
* the view definition text.
*/
vd = dataDictionary.getViewDescriptor(tabDescr);
/*
** Set the default compilation schema to be whatever
** this schema this view was originally compiled against.
** That way we pick up the same tables no matter what
** schema we are running against.
*/
compSchema = dataDictionary.getSchemaDescriptor(vd.getCompSchemaId(), null);
compilerContext.pushCompilationSchema(compSchema);
try
{
/* This represents a view - query is dependent on the ViewDescriptor */
compilerContext.createDependency(vd);
cvn = (CreateViewNode)
parseStatement(vd.getViewText(), false);
rsn = cvn.getParsedQueryExpression();
/* If the view contains a '*' then we mark the views derived column list
* so that the view will still work, and return the expected results,
* if any of the tables referenced in the view have columns added to
* them via ALTER TABLE. The expected results means that the view
* will always return the same # of columns.
*/
if (rsn.getResultColumns().containsAllResultColumn())
{
getResultColumns().setCountMismatchAllowed(true);
}
//Views execute with definer's privileges and if any one of
//those privileges' are revoked from the definer, the view gets
//dropped. So, a view can exist in Derby only if it's owner has
//all the privileges needed to create one. In order to do a
//select from a view, a user only needs select privilege on the
//view and doesn't need any privilege for objects accessed by
//the view. Hence, when collecting privilege requirement for a
//sql accessing a view, we only need to look for select privilege
//on the actual view and that is what the following code is
//checking.
for (ResultColumn rc : getResultColumns()) {
if (isPrivilegeCollectionRequired()) {
compilerContext.addRequiredColumnPriv( rc.getTableColumnDescriptor());
}
}
fsq = new FromSubquery(
rsn,
cvn.getOrderByList(),
cvn.getOffset(),
cvn.getFetchFirst(),
cvn.hasJDBClimitClause(),
(correlationName != null) ?
correlationName : getOrigTableName().getTableName(),
getResultColumns(),
tableProperties,
getContextManager());
// Transfer the nesting level to the new FromSubquery
fsq.setLevel(level);
//We are getting ready to bind the query underneath the view. Since
//that query is going to run with definer's privileges, we do not
//need to collect any privilege requirement for that query.
//Following call is marking the query to run with definer
//privileges. This marking will make sure that we do not collect
//any privilege requirement for it.
CollectNodesVisitor<QueryTreeNode> cnv =
new CollectNodesVisitor<QueryTreeNode>(QueryTreeNode.class);
fsq.accept(cnv);
for (QueryTreeNode node : cnv.getList()) {
node.disablePrivilegeCollection();
}
fsq.setOrigTableName(this.getOrigTableName());
// since we reset the compilation schema when we return, we
// need to save it for use when we bind expressions:
fsq.setOrigCompilationSchema(compSchema);
ResultSetNode fsqBound =
fsq.bindNonVTITables(dataDictionary, fromListParam);
/* Do error checking on derived column list and update "exposed"
* column names if valid.
*/
if (derivedRCL != null) {
fsqBound.getResultColumns().propagateDCLInfo(
derivedRCL,
origTableName.getFullTableName());
}
return fsqBound;
}
finally
{
compilerContext.popCompilationSchema();
}
}
else
{
/* This represents a table - query is dependent on the TableDescriptor */
compilerContext.createDependency(tabDescr);
/* Get the base conglomerate descriptor */
baseConglomerateDescriptor =
tabDescr.getConglomerateDescriptor(
tabDescr.getHeapConglomerateId()
);
// Bail out if the descriptor couldn't be found. The conglomerate
// probably doesn't exist anymore.
if (baseConglomerateDescriptor == null) {
throw StandardException.newException(
SQLState.STORE_CONGLOMERATE_DOES_NOT_EXIST,
Long.valueOf(tabDescr.getHeapConglomerateId()));
}
/* Build the 0-based array of base column names. */
columnNames = getResultColumns().getColumnNames();
/* Do error checking on derived column list and update "exposed"
* column names if valid.
*/
if (derivedRCL != null)
{
getResultColumns().propagateDCLInfo(derivedRCL,
origTableName.getFullTableName());
}
/* Assign the tableNumber */
if (tableNumber == -1) // allow re-bind, in which case use old number
tableNumber = compilerContext.getNextTableNumber();
}
//
// Only the DBO can select from SYS.SYSUSERS.
//
authorizeSYSUSERS =
dataDictionary.usesSqlAuthorization() &&
tabDescr.getUUID().toString().equals(
SYSUSERSRowFactory.SYSUSERS_UUID );
if ( authorizeSYSUSERS )
{
String databaseOwner = dataDictionary.getAuthorizationDatabaseOwner();
String currentUser = getLanguageConnectionContext().getStatementContext().getSQLSessionContext().getCurrentUser();
if ( !databaseOwner.equals( currentUser ) )
{
throw StandardException.newException( SQLState.DBO_ONLY );
}
}
return this;
}
/**
* Return a node that represents invocation of the virtual table for the
* given table descriptor. The mapping of the table descriptor to a specific
* VTI class name will occur as part of the "init" phase for the
* NewInvocationNode that we create here.
*
* Currently only handles no argument VTIs corresponding to a subset of the
* diagnostic tables. (e.g. lock_table). The node returned is a FROM_VTI
* node with a passed in NEW_INVOCATION_NODE representing the class, with no
* arguments. Other attributes of the original FROM_TABLE node (such as
* resultColumns) are passed into the FROM_VTI node.
*/
private ResultSetNode mapTableAsVTI(
TableDescriptor td,
String correlationName,
ResultColumnList resultColumns,
Properties tableProperties,
ContextManager cm)
throws StandardException {
// The fact that we pass a non-null table descriptor to the following
// call is an indication that we are mapping to a no-argument VTI. Since
// we have the table descriptor we do not need to pass in a TableName.
// See NewInvocationNode for more.
final List<ValueNode> emptyList = Collections.emptyList();
MethodCallNode newNode = new NewInvocationNode(
null, // TableName
td, // TableDescriptor
emptyList,
false,
cm);
QueryTreeNode vtiNode;
if (correlationName != null) {
vtiNode = new FromVTI(
newNode,
correlationName,
resultColumns,
tableProperties,
cm);
} else {
TableName exposedName = newNode.makeTableName(td.getSchemaName(),
td.getDescriptorName());
vtiNode = new FromVTI(
newNode,
null, /* correlationName */
resultColumns,
tableProperties,
exposedName,
cm);
}
return (ResultSetNode) vtiNode;
}
/**
* Determine whether or not the specified name is an exposed name in
* the current query block.
*
* @param name The specified name to search for as an exposed name.
* @param schemaName Schema name, if non-null.
* @param exactMatch Whether or not we need an exact match on specified schema and table
* names or match on table id.
*
* @return The FromTable, if any, with the exposed name.
*
* @exception StandardException Thrown on error
*/
@Override
FromTable getFromTableByName(String name, String schemaName, boolean exactMatch)
throws StandardException
{
// ourSchemaName can be null if correlation name is specified.
String ourSchemaName = getOrigTableName().getSchemaName();
String fullName = (schemaName != null) ? (schemaName + '.' + name) : name;
/* If an exact string match is required then:
* o If schema name specified on 1 but not both then no match.
* o If schema name not specified on either, compare exposed names.
* o If schema name specified on both, compare schema and exposed names.
*/
if (exactMatch)
{
if ((schemaName != null && ourSchemaName == null) ||
(schemaName == null && ourSchemaName != null))
{
return null;
}
if (getExposedName().equals(fullName))
{
return this;
}
return null;
}
/* If an exact string match is not required then:
* o If schema name specified on both, compare schema and exposed names.
* o If schema name not specified on either, compare exposed names.
* o If schema name specified on column but not table, then compare
* the column's schema name against the schema name from the TableDescriptor.
* If they agree, then the column's table name must match the exposed name
* from the table, which must also be the base table name, since a correlation
* name does not belong to a schema.
* o If schema name not specified on column then just match the exposed names.
*/
// Both or neither schema name specified
if (getExposedName().equals(fullName))
{
return this;
}
else if ((schemaName != null && ourSchemaName != null) ||
(schemaName == null && ourSchemaName == null))
{
return null;
}
// Schema name only on column
// e.g.: select w1.i from t1 w1 order by test2.w1.i; (incorrect)
if (schemaName != null && ourSchemaName == null)
{
// Compare column's schema name with table descriptor's if it is
// not a synonym since a synonym can be declared in a different
// schema.
if (tableName.equals(origTableName) &&
! schemaName.equals(tableDescriptor.getSchemaDescriptor().getSchemaName()))
{
return null;
}
// Compare exposed name with column's table name
if (! getExposedName().equals(name))
{
return null;
}
// Make sure exposed name is not a correlation name
if (! getExposedName().equals(getOrigTableName().getTableName()))
{
return null;
}
return this;
}
/* Schema name only specified on table. Compare full exposed name
* against table's schema name || "." || column's table name.
*/
if (! getExposedName().equals(getOrigTableName().getSchemaName() + "." + name))
{
return null;
}
return this;
}
/**
* Bind the table descriptor for this table.
*
* If the tableName is a synonym, it will be resolved here.
* The original table name is retained in origTableName.
*
* @exception StandardException Thrown on error
*/
private TableDescriptor bindTableDescriptor()
throws StandardException
{
String schemaName = tableName.getSchemaName();
SchemaDescriptor sd = getSchemaDescriptor(schemaName);
tableDescriptor = getTableDescriptor(tableName.getTableName(), sd);
if (tableDescriptor == null)
{
// Check if the reference is for a synonym.
TableName synonymTab = resolveTableToSynonym(tableName);
if (synonymTab == null)
throw StandardException.newException(SQLState.LANG_TABLE_NOT_FOUND, tableName);
tableName = synonymTab;
sd = getSchemaDescriptor(tableName.getSchemaName());
tableDescriptor = getTableDescriptor(synonymTab.getTableName(), sd);
if (tableDescriptor == null)
throw StandardException.newException(SQLState.LANG_TABLE_NOT_FOUND, tableName);
}
return tableDescriptor;
}
/**
* Bind the expressions in this FromBaseTable. This means binding the
* sub-expressions, as well as figuring out what the return type is for
* each expression.
*
* @param fromListParam FromList to use/append to.
*
* @exception StandardException Thrown on error
*/
@Override
void bindExpressions(FromList fromListParam)
throws StandardException
{
/* No expressions to bind for a FromBaseTable.
* NOTE - too involved to optimize so that this method
* doesn't get called, so just do nothing.
*/
}
/**
* Bind the result columns of this ResultSetNode when there is no
* base table to bind them to. This is useful for SELECT statements,
* where the result columns get their types from the expressions that
* live under them.
*
* @param fromListParam FromList to use/append to.
*
* @exception StandardException Thrown on error
*/
@Override
void bindResultColumns(FromList fromListParam)
throws StandardException
{
/* Nothing to do, since RCL bound in bindNonVTITables() */
}
/**
* Try to find a ResultColumn in the table represented by this FromBaseTable
* that matches the name in the given ColumnReference.
*
* @param columnReference The columnReference whose name we're looking
* for in the given table.
*
* @return A ResultColumn whose expression is the ColumnNode
* that matches the ColumnReference.
* Returns null if there is no match.
*
* @exception StandardException Thrown on error
*/
@Override
ResultColumn getMatchingColumn(ColumnReference columnReference)
throws StandardException
{
ResultColumn resultColumn = null;
TableName columnsTableName;
TableName exposedTableName;
columnsTableName = columnReference.getQualifiedTableName();
if(columnsTableName != null) {
if(columnsTableName.getSchemaName() == null && correlationName == null)
{
columnsTableName.bind();
}
}
/*
** If there is a correlation name, use that instead of the
** table name.
*/
exposedTableName = getExposedTableName();
if (exposedTableName.getSchemaName() == null
&& correlationName == null) {
exposedTableName.bind();
}
/*
** If the column did not specify a name, or the specified name
** matches the table we're looking at, see whether the column
** is in this table.
*/
if (columnsTableName == null || columnsTableName.equals(exposedTableName))
{
//
// The only way that we can be looking up a column reference BEFORE
// the base table is bound is if we are binding a reference inside an argument
// to a VTI/tableFunction. See DERBY-5779. This can happen in the following
// query:
//
// select tt.*
// from
// ( select tablename from table (syscs_diag.space_table( systabs.tablename )) as t2 ) tt,
// sys.systables systabs
// where systabs.tabletype = 'T' and systabs.tableid = tt.tableid;
//
if ( getResultColumns() == null )
{
throw StandardException.newException
( SQLState.LANG_BAD_TABLE_FUNCTION_PARAM_REF, columnReference.getColumnName() );
}
resultColumn = getResultColumns().getResultColumn(columnReference.getColumnName());
/* Did we find a match? */
if (resultColumn != null)
{
columnReference.setTableNumber(tableNumber);
columnReference.setColumnNumber(
resultColumn.getColumnPosition());
// set the column-referenced bit if this is not the row location column
if (
(tableDescriptor != null) &&
( (rowLocationColumnName == null) || !(rowLocationColumnName.equals( columnReference.getColumnName() )) )
)
{
//
// Add a privilege for this column if the bind() phase of an UPDATE
// statement marked it as a selected column. see DERBY-6429.
//
if ( columnReference.isPrivilegeCollectionRequired() )
{
if ( columnReference.taggedWith( TagFilter.NEED_PRIVS_FOR_UPDATE_STMT ) )
{
getCompilerContext().addRequiredColumnPriv
( tableDescriptor.getColumnDescriptor( columnReference.getColumnName() ) );
}
}
FormatableBitSet referencedColumnMap = tableDescriptor.getReferencedColumnMap();
if (referencedColumnMap == null)
referencedColumnMap = new FormatableBitSet(
tableDescriptor.getNumberOfColumns() + 1);
referencedColumnMap.set(resultColumn.getColumnPosition());
tableDescriptor.setReferencedColumnMap(referencedColumnMap);
}
}
}
return resultColumn;
}
/**
* Preprocess a ResultSetNode - this currently means:
* o Generating a referenced table map for each ResultSetNode.
* o Putting the WHERE and HAVING clauses in conjunctive normal form (CNF).
* o Converting the WHERE and HAVING clauses into PredicateLists and
* classifying them.
* o Ensuring that a ProjectRestrictNode is generated on top of every
* FromBaseTable and generated in place of every FromSubquery.
* o Pushing single table predicates down to the new ProjectRestrictNodes.
*
* @param numTables The number of tables in the DML Statement
* @param gbl The group by list, if any
* @param fromList The from list, if any
*
* @return ResultSetNode at top of preprocessed tree.
*
* @exception StandardException Thrown on error
*/
@Override
ResultSetNode preprocess(int numTables,
GroupByList gbl,
FromList fromList)
throws StandardException
{
//
// We're done with binding, so we should know which columns
// are referenced. We check to see if SYSUSERS.PASSWORD is referenced.
// Even the DBO is not allowed to SELECT that column.
// This is to prevent us from instantiating the password as a
// String. See DERBY-866.
// We do this check before optimization because the optimizer may
// change the result column list as it experiments with different access paths.
// At preprocess() time, the result column list should be the columns in the base
// table.
//
if ( authorizeSYSUSERS )
{
int passwordColNum = SYSUSERSRowFactory.PASSWORD_COL_NUM;
FormatableBitSet refCols = getResultColumns().getReferencedFormatableBitSet( false, true, false );
if (
(refCols.getLength() >= passwordColNum ) && refCols.isSet( passwordColNum - 1 )
)
{
throw StandardException.newException
( SQLState.HIDDEN_COLUMN, SYSUSERSRowFactory.TABLE_NAME, SYSUSERSRowFactory.PASSWORD_COL_NAME );
}
}
/* Generate the referenced table map */
setReferencedTableMap( new JBitSet(numTables) );
getReferencedTableMap().set(tableNumber);
return genProjectRestrict(numTables);
}
/**
* Put a ProjectRestrictNode on top of each FromTable in the FromList.
* ColumnReferences must continue to point to the same ResultColumn, so
* that ResultColumn must percolate up to the new PRN. However,
* that ResultColumn will point to a new expression, a VirtualColumnNode,
* which points to the FromTable and the ResultColumn that is the source for
* the ColumnReference.
* (The new PRN will have the original of the ResultColumnList and
* the ResultColumns from that list. The FromTable will get shallow copies
* of the ResultColumnList and its ResultColumns. ResultColumn.expression
* will remain at the FromTable, with the PRN getting a new
* VirtualColumnNode for each ResultColumn.expression.)
* We then project out the non-referenced columns. If there are no referenced
* columns, then the PRN's ResultColumnList will consist of a single ResultColumn
* whose expression is 1.
*
* @param numTables Number of tables in the DML Statement
*
* @return The generated ProjectRestrictNode atop the original FromTable.
*
* @exception StandardException Thrown on error
*/
@Override
protected ResultSetNode genProjectRestrict(int numTables)
throws StandardException
{
/* We get a shallow copy of the ResultColumnList and its
* ResultColumns. (Copy maintains ResultColumn.expression for now.)
*/
ResultColumnList prRCList = getResultColumns();
setResultColumns( getResultColumns().copyListAndObjects() );
getResultColumns().setIndexRow( baseConglomerateDescriptor.getConglomerateNumber(), forUpdate() );
/* Replace ResultColumn.expression with new VirtualColumnNodes
* in the ProjectRestrictNode's ResultColumnList. (VirtualColumnNodes include
* pointers to source ResultSetNode, this, and source ResultColumn.)
* NOTE: We don't want to mark the underlying RCs as referenced, otherwise
* we won't be able to project out any of them.
*/
prRCList.genVirtualColumnNodes(this, getResultColumns(), false);
/* Project out any unreferenced columns. If there are no referenced
* columns, generate and bind a single ResultColumn whose expression is 1.
*/
prRCList.doProjection();
/* Finally, we create the new ProjectRestrictNode */
ProjectRestrictNode result =
new ProjectRestrictNode(
this,
prRCList,
null, /* Restriction */
null, /* Restriction as PredicateList */
null, /* Project subquery list */
null, /* Restrict subquery list */
null,
getContextManager());
if (isValidatingCheckConstraint()) {
CompilerContext cc = getCompilerContext();
if ((cc.getReliability() &
// Internal feature: throw if used on app level
CompilerContext.INTERNAL_SQL_ILLEGAL) != 0) {
throw StandardException.newException(
SQLState.LANG_SYNTAX_ERROR, "validateCheckConstraint");
}
result.setValidatingCheckConstraints(targetTableUUIDString);
}
return result;
}
/**
* @see ResultSetNode#changeAccessPath
*
* @exception StandardException Thrown on error
*/
@Override
ResultSetNode changeAccessPath() throws StandardException
{
ResultSetNode retval;
AccessPath ap = getTrulyTheBestAccessPath();
ConglomerateDescriptor trulyTheBestConglomerateDescriptor =
ap.getConglomerateDescriptor();
JoinStrategy trulyTheBestJoinStrategy = ap.getJoinStrategy();
Optimizer opt = ap.getOptimizer();
if (optimizerTracingIsOn()) {
getOptimizerTracer().traceChangingAccessPathForTable(tableNumber);
}
if (SanityManager.DEBUG)
{
SanityManager.ASSERT(
trulyTheBestConglomerateDescriptor != null,
"Should only modify access path after conglomerate has been chosen.");
}
/*
** Make sure user-specified bulk fetch is OK with the chosen join
** strategy.
*/
if (bulkFetch != UNSET)
{
if ( ! trulyTheBestJoinStrategy.bulkFetchOK())
{
throw StandardException.newException(SQLState.LANG_INVALID_BULK_FETCH_WITH_JOIN_TYPE,
trulyTheBestJoinStrategy.getName());
}
// bulkFetch has no meaning for hash join, just ignore it
else if (trulyTheBestJoinStrategy.ignoreBulkFetch())
{
disableBulkFetch();
}
// bug 4431 - ignore bulkfetch property if it's 1 row resultset
else if (isOneRowResultSet())
{
disableBulkFetch();
}
}
// bulkFetch = 1 is the same as no bulk fetch
if (bulkFetch == 1)
{
disableBulkFetch();
}
/* Remove any redundant join clauses. A redundant join clause is one
* where there are other join clauses in the same equivalence class
* after it in the PredicateList.
*/
restrictionList.removeRedundantPredicates();
/*
** Divide up the predicates for different processing phases of the
** best join strategy.
*/
storeRestrictionList = new PredicateList(getContextManager());
nonStoreRestrictionList = new PredicateList(getContextManager());
requalificationRestrictionList = new PredicateList(getContextManager());
trulyTheBestJoinStrategy.divideUpPredicateLists(
this,
restrictionList,
storeRestrictionList,
nonStoreRestrictionList,
requalificationRestrictionList,
getDataDictionary());
/* Check to see if we are going to do execution-time probing
* of an index using IN-list values. We can tell by looking
* at the restriction list: if there is an IN-list probe
* predicate that is also a start/stop key then we know that
* we're going to do execution-time probing. In that case
* we disable bulk fetching to minimize the number of non-
* matching rows that we read from disk. RESOLVE: Do we
* really need to completely disable bulk fetching here,
* or can we do something else?
*/
for (Predicate pred : restrictionList)
{
if (pred.isInListProbePredicate() && pred.isStartKey())
{
disableBulkFetch();
multiProbing = true;
break;
}
}
/*
** Consider turning on bulkFetch if it is turned
** off. Only turn it on if it is a not an updatable
** scan and if it isn't a oneRowResultSet, and
** not a subquery, and it is OK to use bulk fetch
** with the chosen join strategy. NOTE: the subquery logic
** could be more sophisticated -- we are taking
** the safe route in avoiding reading extra
** data for something like:
**
** select x from t where x in (select y from t)
**
** In this case we want to stop the subquery
** evaluation as soon as something matches.
*/
if (trulyTheBestJoinStrategy.bulkFetchOK() &&
!(trulyTheBestJoinStrategy.ignoreBulkFetch()) &&
! bulkFetchTurnedOff &&
(bulkFetch == UNSET) &&
!forUpdate() &&
!isOneRowResultSet() &&
getLevel() == 0 &&
!validatingCheckConstraint)
{
bulkFetch = getDefaultBulkFetch();
}
/* Statement is dependent on the chosen conglomerate. */
getCompilerContext().createDependency(
trulyTheBestConglomerateDescriptor);
/* No need to modify access path if conglomerate is the heap */
if ( ! trulyTheBestConglomerateDescriptor.isIndex())
{
/*
** We need a little special logic for SYSSTATEMENTS
** here. SYSSTATEMENTS has a hidden column at the
** end. When someone does a select * we don't want
** to get that column from the store. So we'll always
** generate a partial read bitSet if we are scanning
** SYSSTATEMENTS to ensure we don't get the hidden
** column.
*/
boolean isSysstatements = tableName.equals("SYS","SYSSTATEMENTS");
/* Template must reflect full row.
* Compact RCL down to partial row.
*/
templateColumns = getResultColumns();
referencedCols = getResultColumns().getReferencedFormatableBitSet(isCursorTargetTable(), isSysstatements, false);
setResultColumns( getResultColumns().compactColumns(isCursorTargetTable(), isSysstatements) );
return this;
}
/* No need to go to the data page if this is a covering index */
/* Derby-1087: use data page when returning an updatable resultset */
if (ap.getCoveringIndexScan() && (!isCursorTargetTable()))
{
/* Massage resultColumns so that it matches the index. */
setResultColumns
(
newResultColumns
(
getResultColumns(),
trulyTheBestConglomerateDescriptor,
baseConglomerateDescriptor,
false
)
);
/* We are going against the index. The template row must be the full index row.
* The template row will have the RID but the result row will not
* since there is no need to go to the data page.
*/
templateColumns = newResultColumns(getResultColumns(),
trulyTheBestConglomerateDescriptor,
baseConglomerateDescriptor,
false);
templateColumns.addRCForRID();
// If this is for update then we need to get the RID in the result row
if (forUpdate())
{
getResultColumns().addRCForRID();
}
/* Compact RCL down to the partial row. We always want a new
* RCL and FormatableBitSet because this is a covering index. (This is
* because we don't want the RID in the partial row returned
* by the store.)
*/
referencedCols = getResultColumns().getReferencedFormatableBitSet(isCursorTargetTable(),true, false);
setResultColumns( getResultColumns().compactColumns(isCursorTargetTable(),true) );
getResultColumns().setIndexRow(
baseConglomerateDescriptor.getConglomerateNumber(),
forUpdate());
return this;
}
/* Statement is dependent on the base conglomerate if this is
* a non-covering index.
*/
getCompilerContext().createDependency(baseConglomerateDescriptor);
/*
** On bulkFetch, we need to add the restrictions from
** the TableScan and reapply them here.
*/
if (bulkFetch != UNSET)
{
restrictionList.copyPredicatesToOtherList(
requalificationRestrictionList);
}
/*
** We know the chosen conglomerate is an index. We need to allocate
** an IndexToBaseRowNode above us, and to change the result column
** list for this FromBaseTable to reflect the columns in the index.
** We also need to shift "cursor target table" status from this
** FromBaseTable to the new IndexToBaseRowNow (because that's where
** a cursor can fetch the current row).
*/
ResultColumnList newResultColumns =
newResultColumns(getResultColumns(),
trulyTheBestConglomerateDescriptor,
baseConglomerateDescriptor,
true
);
/* Compact the RCL for the IndexToBaseRowNode down to
* the partial row for the heap. The referenced BitSet
* will reflect only those columns coming from the heap.
* (ie, it won't reflect columns coming from the index.)
* NOTE: We need to re-get all of the columns from the heap
* when doing a bulk fetch because we will be requalifying
* the row in the IndexRowToBaseRow.
*/
// Get the BitSet for all of the referenced columns
FormatableBitSet indexReferencedCols = null;
FormatableBitSet heapReferencedCols;
if ((bulkFetch == UNSET) &&
(requalificationRestrictionList == null ||
requalificationRestrictionList.size() == 0))
{
/* No BULK FETCH or requalification, XOR off the columns coming from the heap
* to get the columns coming from the index.
*/
indexReferencedCols = getResultColumns().getReferencedFormatableBitSet(isCursorTargetTable(), true, false);
heapReferencedCols = getResultColumns().getReferencedFormatableBitSet(isCursorTargetTable(), true, true);
if (heapReferencedCols != null)
{
indexReferencedCols.xor(heapReferencedCols);
}
}
else
{
// BULK FETCH or requalification - re-get all referenced columns from the heap
heapReferencedCols = getResultColumns().getReferencedFormatableBitSet(isCursorTargetTable(), true, false) ;
}
ResultColumnList heapRCL = getResultColumns().compactColumns(isCursorTargetTable(), false);
heapRCL.setIndexRow
(
baseConglomerateDescriptor.getConglomerateNumber(),
forUpdate()
);
retval = new IndexToBaseRowNode(this,
baseConglomerateDescriptor,
heapRCL,
isCursorTargetTable(),
heapReferencedCols,
indexReferencedCols,
requalificationRestrictionList,
forUpdate(),
tableProperties,
getContextManager());
/*
** The template row is all the columns. The
** result set is the compacted column list.
*/
setResultColumns( newResultColumns );
templateColumns = newResultColumns(getResultColumns(),
trulyTheBestConglomerateDescriptor,
baseConglomerateDescriptor,
false);
/* Since we are doing a non-covered index scan, if bulkFetch is on, then
* the only columns that we need to get are those columns referenced in the start and stop positions
* and the qualifiers (and the RID) because we will need to re-get all of the other
* columns from the heap anyway.
* At this point in time, columns referenced anywhere in the column tree are
* marked as being referenced. So, we clear all of the references, walk the
* predicate list and remark the columns referenced from there and then add
* the RID before compacting the columns.
*/
if (bulkFetch != UNSET)
{
getResultColumns().markAllUnreferenced();
storeRestrictionList.markReferencedColumns();
if (nonStoreRestrictionList != null)
{
nonStoreRestrictionList.markReferencedColumns();
}
}
getResultColumns().addRCForRID();
templateColumns.addRCForRID();
// Compact the RCL for the index scan down to the partial row.
referencedCols = getResultColumns().getReferencedFormatableBitSet(isCursorTargetTable(), false, false);
setResultColumns( getResultColumns().compactColumns(isCursorTargetTable(), false) );
getResultColumns().setIndexRow(
baseConglomerateDescriptor.getConglomerateNumber(),
forUpdate());
/* We must remember if this was the cursorTargetTable
* in order to get the right locking on the scan.
*/
getUpdateLocks = isCursorTargetTable();
setCursorTargetTable( false );
return retval;
}
/**
* Create a new ResultColumnList to reflect the columns in the
* index described by the given ConglomerateDescriptor. The columns
* in the new ResultColumnList are based on the columns in the given
* ResultColumnList, which reflects the columns in the base table.
*
* @param oldColumns The original list of columns, which reflects
* the columns in the base table.
* @param idxCD The ConglomerateDescriptor, which describes
* the index that the new ResultColumnList will
* reflect.
* @param heapCD The ConglomerateDescriptor for the base heap
* @param cloneRCs Whether or not to clone the RCs
*
* @return A new ResultColumnList that reflects the columns in the index.
*
* @exception StandardException Thrown on error
*/
private ResultColumnList newResultColumns(
ResultColumnList oldColumns,
ConglomerateDescriptor idxCD,
ConglomerateDescriptor heapCD,
boolean cloneRCs)
throws StandardException
{
IndexRowGenerator irg = idxCD.getIndexDescriptor();
int[] baseCols = irg.baseColumnPositions();
ResultColumnList newCols = new ResultColumnList((getContextManager()));
for (int i = 0; i < baseCols.length; i++)
{
int basePosition = baseCols[i];
ResultColumn oldCol = oldColumns.getResultColumn(basePosition);
ResultColumn newCol;
if (SanityManager.DEBUG)
{
SanityManager.ASSERT(oldCol != null,
"Couldn't find base column "+basePosition+
"\n. RCL is\n"+oldColumns);
}
/* If we're cloning the RCs its because we are
* building an RCL for the index when doing
* a non-covering index scan. Set the expression
* for the old RC to be a VCN pointing to the
* new RC.
*/
if (cloneRCs)
{
newCol = oldCol.cloneMe();
oldCol.setExpression(new VirtualColumnNode(
this,
newCol,
oldCol.getVirtualColumnId(),
getContextManager()));
}
else
{
newCol = oldCol;
}
newCols.addResultColumn(newCol);
}
/*
** The conglomerate is an index, so we need to generate a RowLocation
** as the last column of the result set. Notify the ResultColumnList
** that it needs to do this. Also tell the RCL whether this is
** the target of an update, so it can tell the conglomerate controller
** when it is getting the RowLocation template.
*/
newCols.setIndexRow(heapCD.getConglomerateNumber(), forUpdate());
return newCols;
}
/**
* Generation on a FromBaseTable creates a scan on the
* optimizer-selected conglomerate.
*
* @param acb The ActivationClassBuilder for the class being built
* @param mb the execute() method to be built
*
* @exception StandardException Thrown on error
*/
@Override
void generate(ActivationClassBuilder acb, MethodBuilder mb)
throws StandardException
{
if ( rowLocationColumnName != null )
{
getResultColumns().conglomerateId = tableDescriptor.getHeapConglomerateId();
}
generateResultSet( acb, mb );
/*
** Remember if this base table is the cursor target table, so we can
** know which table to use when doing positioned update and delete
*/
if (isCursorTargetTable())
{
acb.rememberCursorTarget(mb);
}
}
/**
* Generation on a FromBaseTable for a SELECT. This logic was separated
* out so that it could be shared with PREPARE SELECT FILTER.
*
* @param acb The ExpressionClassBuilder for the class being built
* @param mb The execute() method to be built
*
* @exception StandardException Thrown on error
*/
@Override
void generateResultSet(ExpressionClassBuilder acb, MethodBuilder mb)
throws StandardException
{
/* We must have been a best conglomerate descriptor here */
if (SanityManager.DEBUG)
SanityManager.ASSERT(
getTrulyTheBestAccessPath().getConglomerateDescriptor() != null);
/* Get the next ResultSet #, so that we can number this ResultSetNode, its
* ResultColumnList and ResultSet.
*/
assignResultSetNumber();
/*
** If we are doing a special scan to get the last row
** of an index, generate it separately.
*/
if (specialMaxScan)
{
generateMaxSpecialResultSet(acb, mb);
return;
}
/*
** If we are doing a special distinct scan, generate
** it separately.
*/
if (distinctScan)
{
generateDistinctScan(acb, mb);
return;
}
/*
* Referential action dependent table scan, generate it
* seperately.
*/
if(raDependentScan)
{
generateRefActionDependentTableScan(acb, mb);
return;
}
JoinStrategy trulyTheBestJoinStrategy =
getTrulyTheBestAccessPath().getJoinStrategy();
// the table scan generator is what we return
acb.pushGetResultSetFactoryExpression(mb);
int nargs = getScanArguments(acb, mb);
mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null,
trulyTheBestJoinStrategy.resultSetMethodName(
(bulkFetch != UNSET), multiProbing, validatingCheckConstraint),
ClassName.NoPutResultSet, nargs);
/* If this table is the target of an update or a delete, then we must
* wrap the Expression up in an assignment expression before
* returning.
* NOTE - scanExpress is a ResultSet. We will need to cast it to the
* appropriate subclass.
* For example, for a DELETE, instead of returning a call to the
* ResultSetFactory, we will generate and return:
* this.SCANRESULTSET = (cast to appropriate ResultSet type)
* The outer cast back to ResultSet is needed so that
* we invoke the appropriate method.
* (call to the ResultSetFactory)
*/
if ((updateOrDelete == UPDATE) || (updateOrDelete == DELETE))
{
mb.cast(ClassName.CursorResultSet);
mb.putField(acb.getRowLocationScanResultSetName(), ClassName.CursorResultSet);
mb.cast(ClassName.NoPutResultSet);
}
}
/**
* Get the final CostEstimate for this ResultSetNode.
*
* @return The final CostEstimate for this ResultSetNode.
*/
@Override
CostEstimate getFinalCostEstimate()
{
return getTrulyTheBestAccessPath().getCostEstimate();
}
/* helper method used by generateMaxSpecialResultSet and
* generateDistinctScan to return the name of the index if the
* conglomerate is an index.
* @param cd Conglomerate for which we need to push the index name
* @param mb Associated MethodBuilder
* @throws StandardException
*/
private void pushIndexName(ConglomerateDescriptor cd, MethodBuilder mb)
throws StandardException
{
if (cd.isConstraint()) {
DataDictionary dd = getDataDictionary();
ConstraintDescriptor constraintDesc =
dd.getConstraintDescriptor(tableDescriptor, cd.getUUID());
mb.push(constraintDesc.getConstraintName());
} else if (cd.isIndex()) {
mb.push(cd.getConglomerateName());
} else {
// If the conglomerate is the base table itself, make sure we push
// null. Before the fix for DERBY-578, we would push the base table
// name and this was just plain wrong and would cause statistics
// information to be incorrect.
mb.pushNull("java.lang.String");
}
}
private void generateMaxSpecialResultSet(
ExpressionClassBuilder acb,
MethodBuilder mb) throws StandardException
{
ConglomerateDescriptor cd = getTrulyTheBestAccessPath().getConglomerateDescriptor();
CostEstimate costEst = getFinalCostEstimate();
int colRefItem = (referencedCols == null) ?
-1 :
acb.addItem(referencedCols);
boolean tableLockGranularity = tableDescriptor.getLockGranularity() == TableDescriptor.TABLE_LOCK_GRANULARITY;
/*
** getLastIndexKeyResultSet
** (
** activation,
** resultSetNumber,
** resultRowAllocator,
** conglomereNumber,
** tableName,
** optimizeroverride
** indexName,
** colRefItem,
** lockMode,
** tableLocked,
** isolationLevel,
** optimizerEstimatedRowCount,
** optimizerEstimatedRowCost,
** );
*/
acb.pushGetResultSetFactoryExpression(mb);
acb.pushThisAsActivation(mb);
mb.push(getResultSetNumber());
mb.push(acb.addItem(
getResultColumns().buildRowTemplate(referencedCols, false)));
mb.push(cd.getConglomerateNumber());
mb.push(tableDescriptor.getName());
//User may have supplied optimizer overrides in the sql
//Pass them onto execute phase so it can be shown in
//run time statistics.
if (tableProperties != null)
mb.push(org.apache.derby.iapi.util.PropertyUtil.sortProperties(tableProperties));
else
mb.pushNull("java.lang.String");
pushIndexName(cd, mb);
mb.push(colRefItem);
mb.push(getTrulyTheBestAccessPath().getLockMode());
mb.push(tableLockGranularity);
mb.push(getCompilerContext().getScanIsolationLevel());
mb.push(costEst.singleScanRowCount());
mb.push(costEst.getEstimatedCost());
mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, "getLastIndexKeyResultSet",
ClassName.NoPutResultSet, 13);
}
private void generateDistinctScan
(
ExpressionClassBuilder acb,
MethodBuilder mb
) throws StandardException
{
ConglomerateDescriptor cd = getTrulyTheBestAccessPath().getConglomerateDescriptor();
CostEstimate costEst = getFinalCostEstimate();
int colRefItem = (referencedCols == null) ?
-1 :
acb.addItem(referencedCols);
boolean tableLockGranularity = tableDescriptor.getLockGranularity() == TableDescriptor.TABLE_LOCK_GRANULARITY;
/*
** getDistinctScanResultSet
** (
** activation,
** resultSetNumber,
** resultRowAllocator,
** conglomereNumber,
** tableName,
** optimizeroverride
** indexName,
** colRefItem,
** lockMode,
** tableLocked,
** isolationLevel,
** optimizerEstimatedRowCount,
** optimizerEstimatedRowCost,
** closeCleanupMethod
** );
*/
/* Get the hash key columns and wrap them in a formattable */
int[] hashKeyCols;
hashKeyCols = new int[getResultColumns().size()];
if (referencedCols == null)
{
for (int index = 0; index < hashKeyCols.length; index++)
{
hashKeyCols[index] = index;
}
}
else
{
int index = 0;
for (int colNum = referencedCols.anySetBit();
colNum != -1;
colNum = referencedCols.anySetBit(colNum))
{
hashKeyCols[index++] = colNum;
}
}
FormatableIntHolder[] fihArray =
FormatableIntHolder.getFormatableIntHolders(hashKeyCols);
FormatableArrayHolder hashKeyHolder = new FormatableArrayHolder(fihArray);
int hashKeyItem = acb.addItem(hashKeyHolder);
long conglomNumber = cd.getConglomerateNumber();
StaticCompiledOpenConglomInfo scoci = getLanguageConnectionContext().
getTransactionCompile().
getStaticCompiledConglomInfo(conglomNumber);
acb.pushGetResultSetFactoryExpression(mb);
acb.pushThisAsActivation(mb);
mb.push(conglomNumber);
mb.push(acb.addItem(scoci));
mb.push(acb.addItem(
getResultColumns().buildRowTemplate(referencedCols, false)));
mb.push(getResultSetNumber());
mb.push(hashKeyItem);
mb.push(tableDescriptor.getName());
//User may have supplied optimizer overrides in the sql
//Pass them onto execute phase so it can be shown in
//run time statistics.
if (tableProperties != null)
mb.push(org.apache.derby.iapi.util.PropertyUtil.sortProperties(tableProperties));
else
mb.pushNull("java.lang.String");
pushIndexName(cd, mb);
mb.push(cd.isConstraint());
mb.push(colRefItem);
mb.push(getTrulyTheBestAccessPath().getLockMode());
mb.push(tableLockGranularity);
mb.push(getCompilerContext().getScanIsolationLevel());
mb.push(costEst.singleScanRowCount());
mb.push(costEst.getEstimatedCost());
mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, "getDistinctScanResultSet",
ClassName.NoPutResultSet, 16);
}
/**
* Generation on a FromBaseTable for a referential action dependent table.
*
* @param acb The ExpressionClassBuilder for the class being built
* @param mb The execute() method to be built
*
* @exception StandardException Thrown on error
*/
private void generateRefActionDependentTableScan
(
ExpressionClassBuilder acb,
MethodBuilder mb
) throws StandardException
{
acb.pushGetResultSetFactoryExpression(mb);
//get the parameters required to do a table scan
int nargs = getScanArguments(acb, mb);
//extra parameters required to create an dependent table result set.
mb.push(raParentResultSetId); //id for the parent result set.
mb.push(fkIndexConglomId);
mb.push(acb.addItem(fkColArray));
mb.push(acb.addItem(getDataDictionary().getRowLocationTemplate(
getLanguageConnectionContext(), tableDescriptor)));
int argCount = nargs + 4;
mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, "getRaDependentTableScanResultSet",
ClassName.NoPutResultSet, argCount);
if ((updateOrDelete == UPDATE) || (updateOrDelete == DELETE))
{
mb.cast(ClassName.CursorResultSet);
mb.putField(acb.getRowLocationScanResultSetName(), ClassName.CursorResultSet);
mb.cast(ClassName.NoPutResultSet);
}
}
private int getScanArguments(ExpressionClassBuilder acb,
MethodBuilder mb)
throws StandardException
{
// Put the result row template in the saved objects.
int resultRowTemplate =
acb.addItem(getResultColumns().buildRowTemplate(referencedCols, false));
// pass in the referenced columns on the saved objects
// chain
int colRefItem = -1;
if (referencedCols != null)
{
colRefItem = acb.addItem(referencedCols);
}
// beetle entry 3865: updateable cursor using index
int indexColItem = -1;
if (isCursorTargetTable() || getUpdateLocks)
{
ConglomerateDescriptor cd = getTrulyTheBestAccessPath().getConglomerateDescriptor();
if (cd.isIndex())
{
int[] baseColPos = cd.getIndexDescriptor().baseColumnPositions();
boolean[] isAscending = cd.getIndexDescriptor().isAscending();
int[] indexCols = new int[baseColPos.length];
for (int i = 0; i < indexCols.length; i++)
indexCols[i] = isAscending[i] ? baseColPos[i] : -baseColPos[i];
indexColItem = acb.addItem(indexCols);
}
}
AccessPath ap = getTrulyTheBestAccessPath();
JoinStrategy trulyTheBestJoinStrategy = ap.getJoinStrategy();
/*
** We can only do bulkFetch on NESTEDLOOP
*/
if (SanityManager.DEBUG)
{
if ( ( ! trulyTheBestJoinStrategy.bulkFetchOK()) &&
(bulkFetch != UNSET))
{
SanityManager.THROWASSERT("bulkFetch should not be set "+
"for the join strategy " +
trulyTheBestJoinStrategy.getName());
}
}
int nargs = trulyTheBestJoinStrategy.getScanArgs(
getLanguageConnectionContext().getTransactionCompile(),
mb,
this,
storeRestrictionList,
nonStoreRestrictionList,
acb,
bulkFetch,
resultRowTemplate,
colRefItem,
indexColItem,
getTrulyTheBestAccessPath().
getLockMode(),
(tableDescriptor.getLockGranularity() == TableDescriptor.TABLE_LOCK_GRANULARITY),
getCompilerContext().getScanIsolationLevel(),
ap.getOptimizer().getMaxMemoryPerTable(),
multiProbing
);
return nargs;
}
/**
* Convert an absolute to a relative 0-based column position.
*
* @param absolutePosition The absolute 0-based column position.
*
* @return The relative 0-based column position.
*/
private int mapAbsoluteToRelativeColumnPosition(int absolutePosition)
{
if (referencedCols == null)
{
return absolutePosition;
}
/* setBitCtr counts the # of columns in the row,
* from the leftmost to the absolutePosition, that will be
* in the partial row returned by the store. This becomes
* the new value for column position.
*/
int setBitCtr = 0;
int bitCtr = 0;
for ( ;
bitCtr < referencedCols.size() && bitCtr < absolutePosition;
bitCtr++)
{
if (referencedCols.get(bitCtr))
{
setBitCtr++;
}
}
return setBitCtr;
}
/**
* Get the exposed name for this table, which is the name that can
* be used to refer to it in the rest of the query.
*
* @return The exposed name of this table.
*
*/
@Override
String getExposedName()
{
if (correlationName != null)
return correlationName;
else
return getOrigTableName().getFullTableName();
}
/**
* Get the exposed table name for this table, which is the name that can
* be used to refer to it in the rest of the query.
*
* @return TableName The exposed name of this table.
*
* @exception StandardException Thrown on error
*/
TableName getExposedTableName() throws StandardException
{
if (correlationName != null)
return makeTableName(null, correlationName);
else
return getOrigTableName();
}
/**
* Return the table name for this table.
*
* @return The table name for this table.
*/
TableName getTableNameField()
{
return tableName;
}
/**
* Return a ResultColumnList with all of the columns in this table.
* (Used in expanding '*'s.)
* NOTE: Since this method is for expanding a "*" in the SELECT list,
* ResultColumn.expression will be a ColumnReference.
*
* @param allTableName The qualifier on the "*"
*
* @return ResultColumnList List of result columns from this table.
*
* @exception StandardException Thrown on error
*/
@Override
ResultColumnList getAllResultColumns(TableName allTableName)
throws StandardException
{
return getResultColumnsForList(allTableName, getResultColumns(),
getOrigTableName());
}
/**
* Build a ResultColumnList based on all of the columns in this FromBaseTable.
* NOTE - Since the ResultColumnList generated is for the FromBaseTable,
* ResultColumn.expression will be a BaseColumnNode.
*
* @return ResultColumnList representing all referenced columns
*
* @exception StandardException Thrown on error
*/
ResultColumnList genResultColList()
throws StandardException
{
ResultColumn resultColumn;
ValueNode valueNode;
/* Cache exposed name for this table.
* The exposed name becomes the qualifier for each column
* in the expanded list.
*/
TableName exposedName = getExposedTableName();
/* Add all of the columns in the table */
ResultColumnList rcList = new ResultColumnList((getContextManager()));
ColumnDescriptorList cdl = tableDescriptor.getColumnDescriptorList();
int cdlSize = cdl.size();
for (int index = 0; index < cdlSize; index++)
{
/* Build a ResultColumn/BaseColumnNode pair for the column */
ColumnDescriptor colDesc = cdl.elementAt(index);
//A ColumnDescriptor instantiated through SYSCOLUMNSRowFactory only has
//the uuid set on it and no table descriptor set on it. Since we know here
//that this columnDescriptor is tied to tableDescriptor, set it so using
//setTableDescriptor method. ColumnDescriptor's table descriptor is used
//to get ResultSetMetaData.getTableName & ResultSetMetaData.getSchemaName
colDesc.setTableDescriptor(tableDescriptor);
valueNode = new BaseColumnNode(colDesc.getColumnName(),
exposedName,
colDesc.getType(),
getContextManager());
resultColumn =
new ResultColumn(colDesc, valueNode, getContextManager());
/* Build the ResultColumnList to return */
rcList.addResultColumn(resultColumn);
}
// add a row location column as necessary
if ( rowLocationColumnName != null )
{
CurrentRowLocationNode rowLocationNode = new CurrentRowLocationNode( getContextManager() );
ResultColumn rowLocationColumn = new ResultColumn
( rowLocationColumnName, rowLocationNode, getContextManager() );
rowLocationColumn.markGenerated();
rowLocationNode.bindExpression( null, null, null );
rowLocationColumn.bindResultColumnToExpression();
rcList.addResultColumn( rowLocationColumn );
}
return rcList;
}
/**
* Augment the RCL to include the columns in the FormatableBitSet.
* If the column is already there, don't add it twice.
* Column is added as a ResultColumn pointing to a
* ColumnReference.
*
* @param inputRcl The original list
* @param colsWeWant bit set of cols we want
*
* @return ResultColumnList the rcl
*
* @exception StandardException Thrown on error
*/
ResultColumnList addColsToList
(
ResultColumnList inputRcl,
FormatableBitSet colsWeWant
)
throws StandardException
{
ResultColumn resultColumn;
TableName exposedName;
/* Cache exposed name for this table.
* The exposed name becomes the qualifier for each column
* in the expanded list.
*/
exposedName = getExposedTableName();
/* Add all of the columns in the table */
ResultColumnList newRcl = new ResultColumnList((getContextManager()));
ColumnDescriptorList cdl = tableDescriptor.getColumnDescriptorList();
int cdlSize = cdl.size();
for (int index = 0; index < cdlSize; index++)
{
/* Build a ResultColumn/BaseColumnNode pair for the column */
ColumnDescriptor cd = cdl.elementAt(index);
int position = cd.getPosition();
if (!colsWeWant.get(position))
{
continue;
}
if ((resultColumn = inputRcl.getResultColumn(position)) == null)
{
ColumnReference cr = new ColumnReference(cd.getColumnName(),
exposedName,
getContextManager());
if ( (getMergeTableID() != ColumnReference.MERGE_UNKNOWN ) )
{
cr.setMergeTableID( getMergeTableID() );
}
resultColumn =
new ResultColumn(cd, cr, getContextManager());
}
/* Build the ResultColumnList to return */
newRcl.addResultColumn(resultColumn);
}
return newRcl;
}
/**
* Return a TableName node representing this FromTable.
* @return a TableName node representing this FromTable.
* @exception StandardException Thrown on error
*/
@Override
TableName getTableName()
throws StandardException
{
TableName tn = super.getTableName();
if (tn != null && tn.getSchemaName() == null
&& correlationName == null) {
tn.bind();
}
return (tn != null ? tn : tableName);
}
/**
Mark this ResultSetNode as the target table of an updatable
cursor.
*/
@Override
boolean markAsCursorTargetTable()
{
setCursorTargetTable( true );
return true;
}
/**
* Is this a table that has a FOR UPDATE
* clause?
*
* @return true/false
*/
@Override
protected boolean cursorTargetTable()
{
return isCursorTargetTable();
}
/**
* Mark as updatable all the columns in the result column list of this
* FromBaseTable that match the columns in the given update column list.
*
* @param updateColumns A ResultColumnList representing the columns
* to be updated.
*/
void markUpdated(ResultColumnList updateColumns)
{
getResultColumns().markUpdated(updateColumns);
}
/**
* Search to see if a query references the specifed table name.
*
* @param name Table name (String) to search for.
* @param baseTable Whether or not name is for a base table
*
* @return true if found, else false
*
* @exception StandardException Thrown on error
*/
@Override
boolean referencesTarget(String name, boolean baseTable)
throws StandardException
{
return baseTable && name.equals(getBaseTableName());
}
/**
* Return true if the node references SESSION schema tables (temporary or permanent)
*
* @return true if references SESSION schema tables, else false
*
* @exception StandardException Thrown on error
*/
@Override
public boolean referencesSessionSchema()
throws StandardException
{
//If base table is a SESSION schema table, then return true.
return isSessionSchema(tableDescriptor.getSchemaDescriptor());
}
/**
* Return whether or not the underlying ResultSet tree will return
* a single row, at most. This method is intended to be used during
* generation, after the "truly" best conglomerate has been chosen.
* This is important for join nodes where we can save the extra next
* on the right side if we know that it will return at most 1 row.
*
* @return Whether or not the underlying ResultSet tree will return a single row.
* @exception StandardException Thrown on error
*/
@Override
boolean isOneRowResultSet() throws StandardException
{
// EXISTS FBT will only return a single row
if (existsBaseTable)
{
return true;
}
/* For hash join, we need to consider both the qualification
* and hash join predicates and we consider them against all
* conglomerates since we are looking for any uniqueness
* condition that holds on the columns in the hash table,
* otherwise we just consider the predicates in the
* restriction list and the conglomerate being scanned.
*/
AccessPath ap = getTrulyTheBestAccessPath();
JoinStrategy trulyTheBestJoinStrategy = ap.getJoinStrategy();
PredicateList pl;
if (trulyTheBestJoinStrategy.isHashJoin())
{
pl = new PredicateList(getContextManager());
if (storeRestrictionList != null)
{
pl.nondestructiveAppend(storeRestrictionList);
}
if (nonStoreRestrictionList != null)
{
pl.nondestructiveAppend(nonStoreRestrictionList);
}
return isOneRowResultSet(pl);
}
else
{
return isOneRowResultSet(getTrulyTheBestAccessPath().
getConglomerateDescriptor(),
restrictionList);
}
}
/**
* Return whether or not this is actually a EBT for NOT EXISTS.
*/
@Override
boolean isNotExists()
{
return isNotExists;
}
boolean isOneRowResultSet(OptimizablePredicateList predList)
throws StandardException
{
ConglomerateDescriptor[] cds = tableDescriptor.getConglomerateDescriptors();
for (int index = 0; index < cds.length; index++)
{
if (isOneRowResultSet(cds[index], predList))
{
return true;
}
}
return false;
}
/**
* Determine whether or not the columns marked as true in
* the passed in array are a superset of any unique index
* on this table.
* This is useful for subquery flattening and distinct elimination
* based on a uniqueness condition.
*
* @param eqCols The columns to consider
*
* @return Whether or not the columns marked as true are a superset
*/
protected boolean supersetOfUniqueIndex(boolean[] eqCols)
throws StandardException
{
ConglomerateDescriptor[] cds = tableDescriptor.getConglomerateDescriptors();
/* Cycle through the ConglomerateDescriptors */
for (int index = 0; index < cds.length; index++)
{
ConglomerateDescriptor cd = cds[index];
if (! cd.isIndex())
{
continue;
}
IndexDescriptor id = cd.getIndexDescriptor();
if (! id.isUnique())
{
continue;
}
int[] keyColumns = id.baseColumnPositions();
int inner = 0;
for ( ; inner < keyColumns.length; inner++)
{
if (! eqCols[keyColumns[inner]])
{
break;
}
}
/* Did we get a full match? */
if (inner == keyColumns.length)
{
return true;
}
}
return false;
}
/**
* Determine whether or not the columns marked as true in
* the passed in join table matrix are a superset of any single column unique index
* on this table.
* This is useful for distinct elimination
* based on a uniqueness condition.
*
* @param tableColMap The columns to consider
*
* @return Whether or not the columns marked as true for one at least
* one table are a superset
*/
protected boolean supersetOfUniqueIndex(JBitSet[] tableColMap)
throws StandardException
{
ConglomerateDescriptor[] cds = tableDescriptor.getConglomerateDescriptors();
/* Cycle through the ConglomerateDescriptors */
for (int index = 0; index < cds.length; index++)
{
ConglomerateDescriptor cd = cds[index];
if (! cd.isIndex())
{
continue;
}
IndexDescriptor id = cd.getIndexDescriptor();
if (! id.isUnique())
{
continue;
}
int[] keyColumns = id.baseColumnPositions();
int numBits = tableColMap[0].size();
JBitSet keyMap = new JBitSet(numBits);
JBitSet resMap = new JBitSet(numBits);
int inner = 0;
for ( ; inner < keyColumns.length; inner++)
{
keyMap.set(keyColumns[inner]);
}
int table = 0;
for ( ; table < tableColMap.length; table++)
{
resMap.setTo(tableColMap[table]);
resMap.and(keyMap);
if (keyMap.equals(resMap))
{
tableColMap[table].set(0);
return true;
}
}
}
return false;
}
/**
* Get the lock mode for the target table heap of an update or delete
* statement. It is not always MODE_RECORD. We want the lock on the
* heap to be consistent with optimizer and eventually system's decision.
* This is to avoid deadlock (beetle 4318). During update/delete's
* execution, it will first use this lock mode we return to lock heap to
* open a RowChanger, then use the lock mode that is the optimizer and
* system's combined decision to open the actual source conglomerate.
* We've got to make sure they are consistent. This is the lock chart (for
* detail reason, see comments below):
* BEST ACCESS PATH LOCK MODE ON HEAP
* ---------------------- -----------------------------------------
* index row lock
*
* heap row lock if READ_COMMITTED,
* REPEATBLE_READ, or READ_UNCOMMITTED and
* not specified table lock otherwise,
* use optimizer decided best acess
* path's lock mode
*
* @return The lock mode
*/
@Override
int updateTargetLockMode()
{
/* if best access path is index scan, we always use row lock on heap,
* consistent with IndexRowToBaseRowResultSet's openCore(). We don't
* need to worry about the correctness of serializable isolation level
* because index will have previous key locking if it uses row locking
* as well.
*/
if (getTrulyTheBestAccessPath().getConglomerateDescriptor().isIndex())
return TransactionController.MODE_RECORD;
/* we override optimizer's decision of the lock mode on heap, and
* always use row lock if we are read committed/uncommitted or
* repeatable read isolation level, and no forced table lock.
*
* This is also reflected in TableScanResultSet's constructor,
* KEEP THEM CONSISTENT!
*
* This is to improve concurrency, while maintaining correctness with
* serializable level. Since the isolation level can change between
* compilation and execution if the statement is cached or stored, we
* encode both the SERIALIZABLE lock mode and the non-SERIALIZABLE
* lock mode in the returned lock mode if they are different.
*/
int isolationLevel =
getLanguageConnectionContext().getCurrentIsolationLevel();
if ((isolationLevel != TransactionControl.SERIALIZABLE_ISOLATION_LEVEL) &&
(tableDescriptor.getLockGranularity() !=
TableDescriptor.TABLE_LOCK_GRANULARITY))
{
int lockMode = getTrulyTheBestAccessPath().getLockMode();
if (lockMode != TransactionController.MODE_RECORD)
lockMode = (lockMode & 0xff) << 16;
else
lockMode = 0;
lockMode += TransactionController.MODE_RECORD;
return lockMode;
}
/* if above don't apply, use optimizer's decision on heap's lock
*/
return getTrulyTheBestAccessPath().getLockMode();
}
/**
* Return whether or not the underlying ResultSet tree
* is ordered on the specified columns.
* RESOLVE - This method currently only considers the outermost table
* of the query block.
* RESOLVE - We do not currently push method calls down, so we don't
* worry about whether the equals comparisons can be against a variant method.
*
* @param crs The specified ColumnReference[]
* @param permuteOrdering Whether or not the order of the CRs in the array can be permuted
* @param fbtHolder List that is to be filled with the FromBaseTable
*
* @return Whether the underlying ResultSet tree
* is ordered on the specified column.
*
* @exception StandardException Thrown on error
*/
@Override
boolean isOrderedOn(ColumnReference[] crs, boolean permuteOrdering, List<FromBaseTable> fbtHolder)
throws StandardException
{
/* The following conditions must be met, regardless of the value of permuteOrdering,
* in order for the table to be ordered on the specified columns:
* o Each column is from this table. (RESOLVE - handle joins later)
* o The access path for this table is an index.
*/
// Verify that all CRs are from this table
for (int index = 0; index < crs.length; index++)
{
if (crs[index].getTableNumber() != tableNumber)
{
return false;
}
}
// Verify access path is an index
ConglomerateDescriptor cd = getTrulyTheBestAccessPath().getConglomerateDescriptor();
if (! cd.isIndex())
{
return false;
}
// Now consider whether or not the CRs can be permuted
boolean isOrdered;
if (permuteOrdering)
{
isOrdered = isOrdered(crs, cd);
}
else
{
isOrdered = isStrictlyOrdered(crs, cd);
}
if (fbtHolder != null)
{
fbtHolder.add(this);
}
return isOrdered;
}
/**
* Turn off bulk fetch
*/
void disableBulkFetch()
{
bulkFetchTurnedOff = true;
bulkFetch = UNSET;
}
/**
* Do a special scan for max.
*/
void doSpecialMaxScan()
{
if (SanityManager.DEBUG)
{
if ((restrictionList.size() != 0) ||
(storeRestrictionList.size() != 0) ||
(nonStoreRestrictionList.size() != 0))
{
SanityManager.THROWASSERT("shouldn't be setting max special scan because there is a restriction");
}
}
specialMaxScan = true;
}
/**
* Is it possible to do a distinct scan on this ResultSet tree.
* (See SelectNode for the criteria.)
*
* @param distinctColumns the set of distinct columns
* @return Whether or not it is possible to do a distinct scan on this ResultSet tree.
*/
@Override
boolean isPossibleDistinctScan(Set<BaseColumnNode> distinctColumns)
{
if ((restrictionList != null && restrictionList.size() != 0)) {
return false;
}
HashSet<ValueNode> columns = new HashSet<ValueNode>();
for (ResultColumn rc : getResultColumns()) {
columns.add(rc.getExpression());
}
return columns.equals(distinctColumns);
}
/**
* Mark the underlying scan as a distinct scan.
*/
@Override
void markForDistinctScan()
{
distinctScan = true;
}
/**
* @see ResultSetNode#adjustForSortElimination
*/
@Override
void adjustForSortElimination()
{
/* NOTE: IRTBR will use a different method to tell us that
* it cannot do a bulk fetch as the ordering issues are
* specific to a FBT being under an IRTBR as opposed to a
* FBT being under a PRN, etc.
* So, we just ignore this call for now.
*/
}
/**
* @see ResultSetNode#adjustForSortElimination
*/
@Override
void adjustForSortElimination(RequiredRowOrdering rowOrdering)
throws StandardException
{
/* We may have eliminated a sort with the assumption that
* the rows from this base table will naturally come back
* in the correct ORDER BY order. But in the case of IN
* list probing predicates (see DERBY-47) the predicate
* itself may affect the order of the rows. In that case
* we need to notify the predicate so that it does the
* right thing--i.e. so that it preserves the natural
* ordering of the rows as expected from this base table.
* DERBY-3279.
*/
if (restrictionList != null)
restrictionList.adjustForSortElimination(rowOrdering);
}
/**
* Return whether or not this index is ordered on a permutation of the specified columns.
*
* @param crs The specified ColumnReference[]
* @param cd The ConglomerateDescriptor for the chosen index.
*
* @return Whether or not this index is ordered exactly on the specified columns.
*
* @exception StandardException Thrown on error
*/
private boolean isOrdered(ColumnReference[] crs, ConglomerateDescriptor cd)
throws StandardException
{
/* This table is ordered on a permutation of the specified columns if:
* o For each key column, until a match has been found for all of the
* ColumnReferences, it is either in the array of ColumnReferences
* or there is an equality predicate on it.
* (NOTE: It is okay to exhaust the key columns before the ColumnReferences
* if the index is unique. In other words if we have CRs left over after
* matching all of the columns in the key then the table is considered ordered
* iff the index is unique. For example:
* i1 on (c1, c2), unique
* select distinct c3 from t1 where c1 = 1 and c2 = ?;
* is ordered on c3 since there will be at most 1 qualifying row.)
*/
boolean[] matchedCRs = new boolean[crs.length];
int nextKeyColumn = 0;
int[] keyColumns = cd.getIndexDescriptor().baseColumnPositions();
// Walk through the key columns
for ( ; nextKeyColumn < keyColumns.length; nextKeyColumn++)
{
boolean currMatch = false;
// See if the key column is in crs
for (int nextCR = 0; nextCR < crs.length; nextCR++)
{
if (crs[nextCR].getColumnNumber() == keyColumns[nextKeyColumn])
{
matchedCRs[nextCR] = true;
currMatch = true;
break;
}
}
// Advance to next key column if we found a match on this one
if (currMatch)
{
continue;
}
// Stop search if there is no equality predicate on this key column
if (! storeRestrictionList.hasOptimizableEqualityPredicate(this, keyColumns[nextKeyColumn], true))
{
break;
}
}
/* Count the number of matched CRs. The table is ordered if we matched all of them. */
int numCRsMatched = 0;
for (int nextCR = 0; nextCR < matchedCRs.length; nextCR++)
{
if (matchedCRs[nextCR])
{
numCRsMatched++;
}
}
if (numCRsMatched == matchedCRs.length)
{
return true;
}
/* We didn't match all of the CRs, but if
* we matched all of the key columns then
* we need to check if the index is unique.
*/
if (nextKeyColumn == keyColumns.length)
{
if (cd.getIndexDescriptor().isUnique())
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
/**
* Return whether or not this index is ordered on a permutation of the specified columns.
*
* @param crs The specified ColumnReference[]
* @param cd The ConglomerateDescriptor for the chosen index.
*
* @return Whether or not this index is ordered exactly on the specified columns.
*
* @exception StandardException Thrown on error
*/
private boolean isStrictlyOrdered(ColumnReference[] crs, ConglomerateDescriptor cd)
throws StandardException
{
/* This table is ordered on the specified columns in the specified order if:
* o For each ColumnReference, it is either the next key column or there
* is an equality predicate on all key columns prior to the ColumnReference.
* (NOTE: If the index is unique, then it is okay to have a suffix of
* unmatched ColumnReferences because the set is known to be ordered. For example:
* i1 on (c1, c2), unique
* select distinct c3 from t1 where c1 = 1 and c2 = ?;
* is ordered on c3 since there will be at most 1 qualifying row.)
*/
int nextCR = 0;
int nextKeyColumn = 0;
int[] keyColumns = cd.getIndexDescriptor().baseColumnPositions();
// Walk through the CRs
for ( ; nextCR < crs.length; nextCR++)
{
/* If we've walked through all of the key columns then
* we need to check if the index is unique.
* Beetle 4402
*/
if (nextKeyColumn == keyColumns.length)
{
if (cd.getIndexDescriptor().isUnique())
{
break;
}
else
{
return false;
}
}
if (crs[nextCR].getColumnNumber() == keyColumns[nextKeyColumn])
{
nextKeyColumn++;
continue;
}
else
{
while (crs[nextCR].getColumnNumber() != keyColumns[nextKeyColumn])
{
// Stop if there is no equality predicate on this key column
if (! storeRestrictionList.hasOptimizableEqualityPredicate(this, keyColumns[nextKeyColumn], true))
{
return false;
}
// Advance to the next key column
nextKeyColumn++;
/* If we've walked through all of the key columns then
* we need to check if the index is unique.
*/
if (nextKeyColumn == keyColumns.length)
{
if (cd.getIndexDescriptor().isUnique())
{
break;
}
else
{
return false;
}
}
}
}
}
return true;
}
/**
* Is this a one-row result set with the given conglomerate descriptor?
*/
private boolean isOneRowResultSet(ConglomerateDescriptor cd,
OptimizablePredicateList predList)
throws StandardException
{
if (predList == null)
{
return false;
}
if (SanityManager.DEBUG)
{
if (! (predList instanceof PredicateList))
{
SanityManager.THROWASSERT(
"predList should be a PredicateList, but is a " +
predList.getClass().getName()
);
}
}
PredicateList restrictList = (PredicateList) predList;
if (! cd.isIndex())
{
return false;
}
IndexRowGenerator irg =
cd.getIndexDescriptor();
// is this a unique index
if (! irg.isUnique())
{
return false;
}
int[] baseColumnPositions = irg.baseColumnPositions();
// Do we have an exact match on the full key
for (int index = 0; index < baseColumnPositions.length; index++)
{
// get the column number at this position
int curCol = baseColumnPositions[index];
/* Is there a pushable equality predicate on this key column?
* (IS NULL is also acceptable)
*/
if (! restrictList.hasOptimizableEqualityPredicate(
this, curCol, true))
{
return false;
}
}
return true;
}
private int getDefaultBulkFetch()
throws StandardException
{
int valInt;
String valStr = PropertyUtil.getServiceProperty(
getLanguageConnectionContext().getTransactionCompile(),
LanguageProperties.BULK_FETCH_PROP,
LanguageProperties.BULK_FETCH_DEFAULT);
valInt = getIntProperty(valStr, LanguageProperties.BULK_FETCH_PROP);
// verify that the specified value is valid
if (valInt <= 0)
{
throw StandardException.newException(SQLState.LANG_INVALID_BULK_FETCH_VALUE,
String.valueOf(valInt));
}
/*
** If the value is <= 1, then reset it
** to UNSET -- this is how customers can
** override the bulkFetch default to turn
** it off.
*/
return (valInt <= 1) ?
UNSET : valInt;
}
private String getUserSpecifiedIndexName()
{
String retval = null;
if (tableProperties != null)
{
retval = tableProperties.getProperty("index");
}
return retval;
}
/*
** RESOLVE: This whole thing should probably be moved somewhere else,
** like the optimizer or the data dictionary.
*/
private StoreCostController getStoreCostController(
ConglomerateDescriptor cd)
throws StandardException
{
return getCompilerContext().getStoreCostController(cd.getConglomerateNumber());
}
private StoreCostController getBaseCostController()
throws StandardException
{
return getStoreCostController(baseConglomerateDescriptor);
}
private boolean gotRowCount = false;
private long rowCount = 0;
private long baseRowCount() throws StandardException
{
if (! gotRowCount)
{
StoreCostController scc = getBaseCostController();
rowCount = scc.getEstimatedRowCount();
gotRowCount = true;
}
return rowCount;
}
private DataValueDescriptor[] getRowTemplate(
ConglomerateDescriptor cd,
StoreCostController scc)
throws StandardException
{
/*
** If it's for a heap scan, just get all the columns in the
** table.
*/
if (! cd.isIndex())
return templateColumns.buildEmptyRow().getRowArray();
/* It's an index scan, so get all the columns in the index */
ExecRow emptyIndexRow = templateColumns.buildEmptyIndexRow(
tableDescriptor,
cd,
scc,
getDataDictionary());
return emptyIndexRow.getRowArray();
}
private ConglomerateDescriptor getFirstConglom()
throws StandardException
{
getConglomDescs();
return conglomDescs[0];
}
private ConglomerateDescriptor getNextConglom(ConglomerateDescriptor currCD)
{
int index = 0;
for ( ; index < conglomDescs.length; index++)
{
if (currCD == conglomDescs[index])
{
break;
}
}
if (index < conglomDescs.length - 1)
{
return conglomDescs[index + 1];
}
else
{
return null;
}
}
private void getConglomDescs()
throws StandardException
{
if (conglomDescs == null)
{
conglomDescs = tableDescriptor.getConglomerateDescriptors();
}
}
/**
* set the Information gathered from the parent table that is
* required to perform a referential action on dependent table.
*/
@Override
void setRefActionInfo(long fkIndexConglomId,
int[]fkColArray,
String parentResultSetId,
boolean dependentScan)
{
this.fkIndexConglomId = fkIndexConglomId;
this.fkColArray = fkColArray;
this.raParentResultSetId = parentResultSetId;
this.raDependentScan = dependentScan;
}
/**
* Accept the visitor for all visitable children of this node.
*
* @param v the visitor
*
* @exception StandardException on error
*/
@Override
void acceptChildren(Visitor v)
throws StandardException
{
super.acceptChildren(v);
if (nonStoreRestrictionList != null) {
nonStoreRestrictionList.accept(v);
}
if (restrictionList != null) {
restrictionList.accept(v);
}
if (nonBaseTableRestrictionList != null) {
nonBaseTableRestrictionList.accept(v);
}
if (requalificationRestrictionList != null) {
requalificationRestrictionList.accept(v);
}
if (tableName != null) {
tableName = (TableName) tableName.accept(v);
}
}
/**
* Tells if the given table qualifies for a statistics update check in the
* current configuration.
*
* @param td the table to check
* @return {@code true} if qualified, {@code false} if not
*/
private boolean qualifiesForStatisticsUpdateCheck(TableDescriptor td)
throws StandardException {
int qualifiedIndexes = 0;
// Only base tables qualifies.
if (td.getTableType() == TableDescriptor.BASE_TABLE_TYPE) {
IndexStatisticsDaemonImpl istatDaemon = (IndexStatisticsDaemonImpl)
getDataDictionary().getIndexStatsRefresher(false);
// Usually only tables with at least one non-unique index or
// multi-column unique indexes qualify, but soft-upgrade mode is a
// special case (as is the temporary user override available).
// TODO: Rewrite if-logic when the temporary override is removed.
if (istatDaemon == null) { // Read-only database
qualifiedIndexes = 0;
} else if (istatDaemon.skipDisposableStats) {
qualifiedIndexes = td.getQualifiedNumberOfIndexes(2, true);
} else {
qualifiedIndexes = td.getTotalNumberOfIndexes();
}
}
return (qualifiedIndexes > 0);
}
}