blob: 4fb9a0d1fdef33f96126441ade00405a4cb7f0d7 [file] [log] [blame]
/**********************************************************************
// @@@ START COPYRIGHT @@@
//
// 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.
//
// @@@ END COPYRIGHT @@@
**********************************************************************/
/* -*-C++-*-
******************************************************************************
*
* File: RelCache.cpp
* Description: All the RelExpr methods introduced by query caching
* Created: 2/23/2001
* Language: C++
*
*
******************************************************************************
*/
#include "AllRelExpr.h"
#include "CacheWA.h"
#include "CmpMain.h"
#include "GroupAttr.h"
#include "OptHints.h"
#include "QRDescGenerator.h"
#include "HDFSHook.h"
// append an ascii-version of GenericUpdate into cachewa.qryText_
void GenericUpdate::generateCacheKey(CacheWA& cwa) const
// NB: This comment applies to all generateCacheKey methods.
// generateCacheKey is used to generate a string representation s of the
// "parameterized" query. Since this string s is used by QCache::lookUp
// to determine if a query is in the cache, it is essential that:
// (1) two different queries have different string representations
// (2) two queries that differ only in their query literals should
// have the same string representations
// One possible implementation of generateCacheKey is to use the query's
// original query text. But, original query text does not satisfy (2).
// To get (2), we call generateCacheKey() from RelRoot::normalizeForCache
// which, by definition, replaced query literals with constant parameters.
// However, generateCacheKey must also satisfy (1). generateCacheKey must
// generate two different strings for two logically different queries.
//
// To satisfy requirements (1) and (2), generateCacheKey and
// normalizeForCache must be in sync -- every user-specified expr that
// generateCacheKey emits into cwa.qryText_ must be examined by
// normalizeForCache for possible replacement of any literal there into
// a constant parameter.
//
// In order for the literal-into-constantparameter replacement to be safe,
// isCacheableExpr must visit all user-specified exprs to make sure that
// only constants that can be safely cast into the query's target types
// are considered cacheable. For example, given this update query
// update t set a = 'xyz' where pk = 1;
// isCacheableeExpr, normalizeForCache, and generateCacheKey must cooperate
// so that:
// 1) isCacheableExpr rejects the query as noncacheble if 'xyz' cannot be
// safely cast into a's target type, eg, 'xyz' may be too long if a's
// type is char(1).
// 2) normalizeForCache must visit and replace both 'xyz' and 1 with
// appropriate constant parameters.
// 3) generateCacheKey must emit some string representation of the
// parameterized query, eg, "update t set a = % where pk = %".
// generateCacheKey can emit more stuff, eg, internally specified
// begin/end-key predicates, but it must emit a string representation
// of all user-specified parts of the query.
{
// append to cwa.qryText_ GenericUpdate's "essential" data members
RelExpr::generateCacheKey(cwa);
// An extension of the fix to 10-010618-3505, 10-010619-3515:
// for "after bind" Insert/Update/Delete queries, include table's
// RedefTime into cwa.qryText_ to make sure we get a cache hit only on
// query that reference table(s) that have not changed since the query's
// addition to the cache. The queries that reference altered table(s)
// will never be hit again and will eventually age out of the cache.
// This is not strictly necessary, but it speeds up the processing
// of insert/update/delete queries on altered tables.
const NATable *tbl;
if (cwa.getPhase() >= CmpMain::BIND &&
getTableDesc() && (tbl=getTableDesc()->getNATable()) != NULL) {
char redefTime[40];
convertInt64ToAscii(tbl->getRedefTime(), redefTime);
cwa += " redef:";
cwa += redefTime;
}
ItemExpr *newExpr = newRecExprTree_ ? newRecExprTree_ :
newRecExpr_.rebuildExprTree(ITM_ITEM_LIST);
if (newExpr) {
cwa += " newRecExpr:";
newExpr->generateCacheKey(cwa);
}
// make sure cache key can distinguish these 2 queries:
// prepare s from select * from (update t042qT8 set b=7 where a=2) as t;
// prepare s from select * from (update t042qT8 set b=7 set on rollback c=2
// where a=2) as t;
ItemExpr *setOnRollback;
if (newRecBeforeExpr_.entries() > 0 &&
(setOnRollback=newRecBeforeExpr_.rebuildExprTree(ITM_ITEM_LIST))) {
cwa += " setOnRollback:";
setOnRollback->generateCacheKey(cwa);
}
ItemExpr *execPred = executorPredTree_ ? executorPredTree_ :
executorPred_.rebuildExprTree();
if (execPred) {
cwa += " execPred:";
execPred->generateCacheKey(cwa);
}
// MVs --
// The NOLOG parameter is essential.
if (isNoLogOperation()) {
cwa += " NOLOG";
}
// "current of cursor/hostvar" is essential
if (currOfCursorName_) {
currOfCursorName_->generateCacheKey(cwa);
}
// not sure if the following are essential, but better to be safe &
// slightly inefficient than to deliver a false hit (ie, wrong plan)
cwa += mtsStatement_ ? "m1" : "m0";
cwa += noFlow_ ? "n1" : "n0";
cwa += noRollback_ ? "o1" : "o0";
cwa += noCheck_ ? "nc" : "dc";
// not sure if the following are essential, but we don't know how
// to quickly & cheaply include them into our cachekey:
// updatedTableName_, tabId_, updateToSelectMap_, indexDesc_,
// newRecExprArray_, usedColumns_, newRecBeforeExpr_,
// newRecBeforeExprArray_, usedBeforeColumns_, potentialOutputs_
// indexNumberArray_, scanIndexDesc_, rowsAffected_, stoi_,
// oldToNewMap_
// The following data members are not "essential" to generateCacheKey
// (at least "after bind") because they are either covered by other
// data members (eg, beginKeyPred and endKeyPred_ are covered by the
// selection pred in RelExpr) or they are not yet defined until later
// (eg, after the optimize phase):
// indexNewRecExprArrays_, beginKeyPred_, endKeyPred_,
// pathKeys_, partKeys_, indexBeginKeyPredArray_,
// indexEndKeyPredArray_, checkConstraints_
}
// is this entire expression cacheable after this phase?
NABoolean GenericUpdate::isCacheableExpr(CacheWA& cwa)
{
// descend to scans early to get cwa.numberOfScans_
if (!RelExpr::isCacheableExpr(cwa)) {
return FALSE;
}
// Make "{update|delete} ... where current of cursor" non-cacheable
// so that stale cache will not lead to timestamp mismatch error at
// runtime. AQR attempts to handle this error, but only after the
// referenced cursor is closed due to transaction rollback. This is
// Solution 10-100425-9755.
if (currOfCursorName()) {
return FALSE;
}
if (cwa.getPhase() >= CmpMain::BIND) {
// make sure any literals in the assignment clause can be safely
// cast and assigned to their target types at plan-backpatch-time
ItemExpr *newExpr = newRecExprTree_ ? newRecExprTree_ :
newRecExpr_.rebuildExprTree(ITM_ITEM_LIST);
if (newExpr && !newExpr->isSafelyCoercible(cwa)) {
return FALSE;
}
// reject as non-cacheable queries such as
// prepare s from select * from (update t042qT8 set b=7
// set on rollback c=12345678901234567890 where a=2) as t;
ItemExpr *setOnRollback;
if (newRecBeforeExpr_.entries() > 0 &&
(setOnRollback=newRecBeforeExpr_.rebuildExprTree(ITM_ITEM_LIST))
&& !setOnRollback->isSafelyCoercible(cwa)) {
return FALSE;
}
// make sure any executor predicate is cacheable
ItemExpr *execPred = executorPredTree_ ? executorPredTree_ :
executorPred_.rebuildExprTree();
if (execPred) {
cwa.setHasPredicate();
if (execPred->hasNoLiterals(cwa)) {
// predicate with no literals is cacheable
}
else {
cwa.setPredHasNoLit(FALSE);
return execPred->isCacheableExpr(cwa);
}
}
// at this time, not cacheable if subquery is specified in
// UPDATE SET clause.
// This could be enabled later.
if (subqInUpdateAssign()) {
return FALSE;
}
}
else {
if ((getTableName().isPartitionNameSpecified()) ||
(getTableName().isLocationNameSpecified()) ||
(getTableName().isPartitionRangeSpecified()))
return FALSE; // If PartnClause is used no cache hit before bind stage.
}
return TRUE; // may be cacheable
}
RelExpr* Scan::normalizeForCache(CacheWA& cwa, BindWA& bindWA)
{
if (nodeIsNormalizedForCache()) {
return this;
}
if (CmpCommon::getDefault(QUERY_CACHE_TABLENAME) == DF_OFF) {
// replace descendants' literals into ConstantParameters
return RelExpr::normalizeForCache(cwa, bindWA);
}
// replace tablename with a prototyped tablename.
TableDesc * td = getTableDesc();
CorrName &origName = td->getCorrNameObj();
if (origName.getPrototype() == NULL)
{
Lng32 CACHED_MAX_ANSI_NAME_EXTERNAL_LEN = 128;
NAString hvName("dummy_name");
HostVar * hv =
new(bindWA.wHeap())
HostVar(hvName,
new(bindWA.wHeap()) SQLChar(bindWA.wHeap(), CACHED_MAX_ANSI_NAME_EXTERNAL_LEN));
hv->setPrototypeValue(origName.getQualifiedNameAsString());
hv->synthTypeAndValueId();
hv->setIsCachedParam(TRUE);
CorrName cn("HostVar$",
bindWA.wHeap(),
hv->getName(), // debugging ease
"$bogus");
cn.setPrototype(hv);
NAString *tmpName =
new (bindWA.wHeap())
NAString(hv->getPrototypeValue(), bindWA.wHeap());
cn.setUgivenName(*tmpName);
cn.applyDefaults(&bindWA, bindWA.getDefaultSchema());
td->setCorrName(cn);
setTableName(cn);
char * strval =
new(bindWA.wHeap()) char[CACHED_MAX_ANSI_NAME_EXTERNAL_LEN];
strcpy(strval, origName.getQualifiedNameAsString().data());
CharType * typ =
new(bindWA.wHeap()) SQLChar(bindWA.wHeap(), CACHED_MAX_ANSI_NAME_EXTERNAL_LEN, FALSE);
ConstValue * cv =
new(bindWA.wHeap()) ConstValue(typ, strval, CACHED_MAX_ANSI_NAME_EXTERNAL_LEN);
ConstantParameter* result = new(bindWA.wHeap()) ConstantParameter
(*cv, bindWA.wHeap(), cwa.getPhase() == CmpMain::PARSE);
result->synthTypeAndValueId();
cwa.addConstParam(result, bindWA);
hv->setPMOrdPosAndIndex(COM_UNKNOWN_DIRECTION,
-1,
(Int32)cwa.getConstParams().entries());
}
// replace descendants' literals into ConstantParameters
return RelExpr::normalizeForCache(cwa, bindWA);
}
// change literals of a cacheable query into ConstantParameters
RelExpr* GenericUpdate::normalizeForCache(CacheWA& cwa, BindWA& bindWA)
{
if (nodeIsNormalizedForCache()) {
return this;
}
if (cwa.getPhase() >= CmpMain::BIND) {
if (currOfCursorName_) {
// do NOT parameterize the assignment clause(s) of positioned updates
// because "update t051t22 set b = -1 where current of c1" in esqlc
// program such as core/etest051.sql generates an assertion clause:
// "...if_then_else(b= :hv0),return_true,return_true(raiserror())"
// (see BindItemExpr.cpp Scan::bindUpdateCurrentOf) as part of
// GenericUpdate::bindNode. Otherwise, the result is an error 8106.
// The root cause is incomplete parameterization -- the update
// becomes "update t051t22 set b = 0-? where current of c1" but
// "...if_then_else(b= :hv0),return_true,return_true(raiserror())"
// is untouched causing an error 8106 at runtime.
}
else {
if (newRecExprTree_) {
newRecExprTree_->normalizeForCache(cwa, bindWA);
}
else {
newRecExpr_.normalizeForCache(cwa, bindWA);
}
// parameterize "set on rollback" clause for queries such as
// prepare s from select * from (update t042qT8 set b=7
// set on rollback c=2 where a=2) as t;
newRecBeforeExpr_.normalizeForCache(cwa, bindWA);
}
if (executorPredTree_) {
executorPredTree_->normalizeForCache(cwa, bindWA);
}
else {
executorPred_.normalizeForCache(cwa, bindWA);
}
}
// Solution: 10-060327-5370 and 10-060418-5903
// Record the context-wide isolation_level_for_updates value in
// CacheWA when procssing an IDU stmt. Use ISOLATION_LEVEL_FOR_UPDATES
// if it is specified. Otherwise, use ISOLATION_LEVEL. The initial value
// in cwa is IL_NOT_SPECIFIED_.
if ( cwa.getIsoLvlForUpdates() == TransMode::IL_NOT_SPECIFIED_ ) {
TransMode::IsolationLevel il;
ActiveSchemaDB()->getDefaults().getIsolationLevel (il,
CmpCommon::getDefault(ISOLATION_LEVEL_FOR_UPDATES));
if ( il == TransMode::IL_NOT_SPECIFIED_ ) {
ActiveSchemaDB()->getDefaults().getIsolationLevel (il,
CmpCommon::getDefault(ISOLATION_LEVEL));
}
cwa.setIsoLvlForUpdates(il);
}
// replace descendants' literals into ConstantParameters
return RelExpr::normalizeForCache(cwa, bindWA);
}
// append an ascii-version of IsolatedScalarUDF into cachewa.qryText_
void IsolatedScalarUDF::generateCacheKey(CacheWA &cwa) const
{
NARoutine *routine = NULL;
NARoutine *action = NULL;
RelExpr::generateCacheKey(cwa);
cwa += " UDFname:";
cwa += getRoutineName().getQualifiedNameAsAnsiString().data();
if (cwa.getPhase() >= CmpMain::BIND &&
getRoutineDesc() &&
(routine=getRoutineDesc()->getNARoutine()) != NULL)
{
char redefTime[40];
convertInt64ToAscii(routine->getRedefTime(), redefTime);
cwa += " redef:";
cwa += redefTime;
}
if (getRoutineDesc() != NULL && getRoutineDesc()->isUUDFRoutine())
{
cwa += " action:";
cwa += getRoutineDesc()->getActionNameAsGiven();
if (cwa.getPhase() >= CmpMain::BIND &&
getRoutineDesc() &&
(action=getRoutineDesc()->getActionNARoutine()) != NULL)
{
char redefTime[40];
convertInt64ToAscii(action->getRedefTime(), redefTime);
cwa += " actredef:";
cwa += redefTime;
}
}
const ItemExpr *paramExpr = (getProcAllParamsTree() == NULL) ?
getProcInputParamsVids().rebuildExprTree(ITM_ITEM_LIST) :
getProcAllParamsTree();
if (paramExpr)
{
cwa += " arg:(";
paramExpr->generateCacheKey(cwa);
cwa += ")";
}
}
// append an ascii-version of CallSP into cachewa.qryText_
void CallSP::generateCacheKey(CacheWA &cwa) const
{
RelExpr::generateCacheKey(cwa);
cwa += " CallSPname:";
cwa += getRoutineName().getQualifiedNameAsAnsiString().data();
const ItemExpr *paramExpr = (getProcAllParamsTree() == NULL) ?
getProcInputParamsVids().rebuildExprTree(ITM_ITEM_LIST) :
getProcAllParamsTree();
if (paramExpr)
{
cwa += " arg:";
paramExpr->generateCacheKey(cwa);
}
}
// append an ascii-version of GroupByAgg into cachewa.qryText_
void GroupByAgg::generateCacheKey(CacheWA &cwa) const
{
RelExpr::generateCacheKey(cwa);
// group by col/expr is an important part of the key
ItemExpr *grpExpr = groupExprTree_ ? groupExprTree_ :
groupExpr_.rebuildExprTree(ITM_ITEM_LIST);
if (grpExpr) {
cwa += " gBy:";
if (isRollup())
{
cwa += " roll:";
ItemExpr * ie = rollupGroupExprList().rebuildExprTree(ITM_ITEM_LIST);
if (ie)
{
ie->generateCacheKey(cwa);
}
}
if (NOT extraOrderExpr().isEmpty() )
{
cwa += " extraOrder:";
ItemExpr * ie = extraOrderExpr().rebuildExprTree(ITM_ITEM_LIST);
if (ie)
{
ie->generateCacheKey(cwa);
}
}
grpExpr->generateCacheKey(cwa);
}
}
// is this entire expression cacheable after this phase?
NABoolean GroupByAgg::isCacheableExpr(CacheWA& cwa)
{
// descend to scans early to get cwa.numberOfScans_
if (!RelExpr::isCacheableExpr(cwa)) {
return FALSE;
}
// is the group by col/expr cacheable?
ItemExpr *grpExpr = groupExprTree_ ? groupExprTree_ :
groupExpr_.rebuildExprTree(ITM_ITEM_LIST);
if (grpExpr && !grpExpr->isCacheableExpr(cwa)) {
return FALSE;
}
return TRUE; // may be cacheable
}
// append an ascii-version of Insert into cachewa.qryText_
void Insert::generateCacheKey(CacheWA &cwa) const
{
GenericUpdate::generateCacheKey(cwa);
if (insertColTree_) {
cwa += " insCol:";
insertColTree_->generateCacheKey(cwa);
}
// order by clause is important
ItemExpr *orderBy = orderByTree_ ? orderByTree_ :
reqdOrder_.rebuildExprTree();
if (orderBy) {
cwa += " order:";
orderBy->generateCacheKey(cwa);
}
const NATable *tbl;
if (cwa.getPhase() >= CmpMain::BIND &&
getTableDesc() && (tbl=getTableDesc()->getNATable()) != NULL) {
// If PARTITION clause has been used we must reflect that in the key.
if (tbl->isPartitionNameSpecified()) {
cwa += " partition:";
cwa += tbl->getClusteringIndex()->getFileSetName().getQualifiedNameAsString().data();
}
// If PARTITION range has been used we must reflect that in the key.
else if (tbl->isPartitionRangeSpecified()) {
cwa += " partition:";
char str[100];
sprintf(str, " from %d to %d",
tbl->getExtendedQualName().getPartnClause().getBeginPartitionNumber() ,
tbl->getExtendedQualName().getPartnClause().getEndPartitionNumber());
cwa += str;
}
}
if (isUpsert())
{
cwa += " upsert:";
}
}
// is this entire expression cacheable after this phase?
NABoolean Insert::isCacheableExpr(CacheWA& cwa)
{
// non-single-row inserts are non-cacheable
if (insertType_ != SIMPLE_INSERT) {
return FALSE;
}
// single-row insert may be cacheable
return GenericUpdate::isCacheableExpr(cwa);
}
// change literals of a cacheable query into ConstantParameters
RelExpr* Insert::normalizeForCache(CacheWA& cwa, BindWA& bindWA)
{
if (nodeIsNormalizedForCache()) {
return this;
}
// replace descendants' literals into ConstantParameters
return GenericUpdate::normalizeForCache(cwa, bindWA);
}
// is this entire expression cacheable after this phase?
NABoolean Delete::isCacheableExpr(CacheWA& cwa)
{
return GenericUpdate::isCacheableExpr(cwa);
}
// append an ascii-version of Merge into cachewa.qryText_
void MergeUpdate::generateCacheKey(CacheWA &cwa) const
{
Update::generateCacheKey(cwa);
if (insertCols_) {
cwa += " insertCols:";
insertCols_->generateCacheKey(cwa);
}
if (insertValues_) {
cwa += " insertValues:";
insertValues_->generateCacheKey(cwa);
}
}
// is this entire expression cacheable after this phase?
NABoolean MergeUpdate::isCacheableExpr(CacheWA& cwa)
{
if ((insertValues_) &&
(insertValues_->isCacheableExpr(cwa))) {
setNonCacheable();
return FALSE;
}
return Update::isCacheableExpr(cwa);
}
// change literals of a cacheable query into ConstantParameters
RelExpr* MergeUpdate::normalizeForCache(CacheWA& cwa, BindWA& bindWA)
{
if (nodeIsNormalizedForCache()) {
return this;
}
if (insertValues_) {
insertValues_ = insertValues_->normalizeForCache(cwa, bindWA);
}
// replace descendants' literals into ConstantParameters
return Update::normalizeForCache(cwa, bindWA);
}
// append an ascii-version of Merge into cachewa.qryText_
void MergeDelete::generateCacheKey(CacheWA &cwa) const
{
Delete::generateCacheKey(cwa);
if (insertCols_) {
cwa += " insertCols:";
insertCols_->generateCacheKey(cwa);
}
if (insertValues_) {
cwa += " insertValues:";
insertValues_->generateCacheKey(cwa);
}
}
// is this entire expression cacheable after this phase?
NABoolean MergeDelete::isCacheableExpr(CacheWA& cwa)
{
if ((insertValues_) &&
(insertValues_->isCacheableExpr(cwa))) {
setNonCacheable();
return FALSE;
}
return Delete::isCacheableExpr(cwa);
}
// change literals of a cacheable query into ConstantParameters
RelExpr* MergeDelete::normalizeForCache(CacheWA& cwa, BindWA& bindWA)
{
if (nodeIsNormalizedForCache()) {
return this;
}
if (insertValues_) {
insertValues_ = insertValues_->normalizeForCache(cwa, bindWA);
}
// replace descendants' literals into ConstantParameters
return Delete::normalizeForCache(cwa, bindWA);
}
// append an ascii-version of Join into cachewa.qryText_
void Join::generateCacheKey(CacheWA &cwa) const
{
RelExpr::generateCacheKeyNode(cwa);
if (isNaturalJoin_) {
cwa += " natj ";
}
ItemExpr *pred = joinPredTree_ ? joinPredTree_ :
joinPred_.rebuildExprTree();
if (pred) {
cwa += " joinPred:";
pred->generateCacheKey(cwa);
}
generateCacheKeyForKids(cwa);
}
// is this entire expression cacheable after this phase?
NABoolean Join::isCacheableExpr(CacheWA& cwa)
{
if (cwa.getPhase() >= CmpMain::BIND) {
// must first descend to scans to get cwa.numberOfScans_
if (!RelExpr::isCacheableExpr(cwa)) {
return FALSE;
}
if (isCacheableNode(cwa.getPhase())) {
cwa.setConditionallyCacheable();
}
// if we allow joins of views to be cached, query caching cannot
// distinguish between (see note at bottom of cachewa.h)
// select avg(f.a) from v f, v s group by f.b;
// select avg(s.a) from v f, v s group by f.b;
// select avg(t.a) from v f, t group by f.b;
// assuming v is "create view v from select * from t". We avoid
// false cache hits by detecting the possible occurrence of such
// view joins here and later using cwa.isViewJoin_ to include
// their query texts into their cache keys.
//
// A view is repsented by a renamed table with isView() returnning
// TRUE.
RelExpr *c0 = child(0);
RelExpr *c1 = child(1);
if ((c0->getOperatorType() == REL_RENAME_TABLE &&
((RenameTable *)c0)->isView() == TRUE)
||
(c1->getOperatorType() == REL_RENAME_TABLE &&
((RenameTable *)c1)->isView() == TRUE)) {
cwa.foundViewJoin();
}
// check its join predicate
ItemExpr *pred = joinPredTree_ ? joinPredTree_ :
joinPred_.rebuildExprTree();
if (pred) {
cwa.setHasPredicate();
// is join predicate cacheable?
if (pred->hasNoLiterals(cwa)) {
// predicate with no literals is cacheable
}
else {
cwa.setPredHasNoLit(FALSE);
if (!pred->isCacheableExpr(cwa)) {
// a non-cacheable predicate renders Join non-cacheable.
setNonCacheable();
return FALSE;
}
}
}
return TRUE; // join may be cacheable
}
return FALSE;
}
// change literals of a cacheable query into ConstantParameters
RelExpr* Join::normalizeForCache(CacheWA& cwa, BindWA& bindWA)
{
return RelExpr::normalizeForCache(cwa, bindWA);
}
// are RelExpr's kids cacheable after this phase?
NABoolean RelExpr::cacheableKids(CacheWA& cwa)
{
switch (cwa.getPhase()) {
case CmpMain::PARSE:
case CmpMain::BIND: {
Int32 arity = getArity();
if (arity <= 0) { // we have no kids
if (cwa.isConditionallyCacheable()) {
// we're conditionally cacheable and have no kids
setCacheableNode(cwa.getPhase());
return TRUE; // so, we're cachable
}
else {
return FALSE; // MAYBECACHEABLE is not cacheable at this phase
// don't mark this node non-cacheable because this
// RelExpr may be cacheable after the next phase.
}
}
// cacheability of child(ren) determine our cacheability
for (Int32 x = 0; x < arity; x++) {
if (!child(x) || // cases like "insert into t default values"
// return 1 from getArity() even if child(0) is NULL; so
// guard against this potential mxcmp crash and consider
// these cases non-cacheable during the PARSE stage.
child(x)->isNonCacheable()) {
// the 1st noncacheable child makes us noncacheable
setNonCacheable();
return FALSE;
}
else if (!child(x)->isCacheableExpr(cwa)) {
// noncacheable child
return FALSE;
// don't mark this node non-cacheable because this
// RelExpr may be cacheable after the next phase.
}
else { // cacheable child
continue; // look at next child
}
}
// all children are cacheable, so we're cacheable too
setCacheableNode(cwa.getPhase());
return TRUE;
}
default:
return FALSE;
}
}
// append an ascii-version of RelExpr into cachewa.qryText_
void RelExpr::generateCacheKey(CacheWA &cwa) const
{
generateCacheKeyNode(cwa);
generateCacheKeyForKids(cwa);
}
// append an ascii-version of RelExpr node into cachewa.qryText_
void RelExpr::generateCacheKeyNode(CacheWA &cwa) const
{
// emit any "[firstn_sorted]" modifier
if (firstNRows_ != -1) {
char firstN[40];
convertInt64ToAscii(((RelExpr*)this)->getFirstNRows(), firstN);
cwa += firstN;
cwa += " ";
}
// emit other "significant" parts of RelExpr
cwa += getText();
ItemExpr *pred = selPredTree() ? selPredTree() :
getSelectionPred().rebuildExprTree();
if (pred) {
cwa += " selPred:";
pred->generateCacheKey(cwa);
}
// make any optimizer hints part of the postbinder cache key so that
// 2 cacheable queries with different optimizer hints do not match
if (hint_) {
CollIndex x, cnt=hint_->indexCnt();
if (cnt > 0) {
cwa += " xhint:";
for (x = 0; x < cnt; x++) {
cwa += (*hint_)[x].data();
cwa += ",";
}
}
char str[100];
if (hint_->hasCardinality()) {
sprintf(str, "card:%g", hint_->getCardinality());
cwa += str;
}
if (hint_->hasSelectivity()) {
sprintf(str, ",sel:%g", hint_->getSelectivity());
cwa += str;
}
}
}
// append an ascii-version of RelExpr's kids into cachewa.qryText_
void RelExpr::generateCacheKeyForKids(CacheWA& cwa) const
{
Int32 maxi = getArity();
if (maxi) {
cwa += " kids(";
for (Lng32 i = 0; i < maxi; i++) {
if (i > 0) {
cwa += ",";
}
if ( child(i).getPtr() == NULL ) {
continue;
}
child(i)->generateCacheKey(cwa);
}
cwa += ")";
}
}
// return any Scan node from this RelExpr
Scan *RelExpr::getAnyScanNode() const
{
if (getOperatorType() == REL_SCAN) {
return (Scan*)this;
}
Scan *result = NULL;
Int32 arity = getArity();
for (Int32 x = 0; x < arity && !result; x++) {
if (child(x)) {
result = child(x)->getAnyScanNode();
}
}
return result;
}
// is this entire expression cacheable after this phase?
NABoolean RelExpr::isCacheableExpr(CacheWA& cwa)
{
switch (cwa.getPhase()) {
case CmpMain::PARSE:
case CmpMain::BIND: {
// does query have too many ExprNodes?
if (cwa.inc_N_check_still_cacheable() == FALSE) {
// yes. query with too many ExprNodes is not cacheable.
return FALSE;
}
if (isNonCacheable()) { // this node is not cacheable
return FALSE; // so the entire expression is not cacheable
// don't mark this node non-cacheable because this
// RelExpr may be cacheable after the next phase.
}
if (isCacheableNode(cwa.getPhase())) {
// must be an INSERT, UPDATE, DELETE, or SELECT node;
// so, mark this expression as conditionally cacheable.
cwa.setConditionallyCacheable();
}
// must descend to scans to get cwa.numberOfScans_
if (!cacheableKids(cwa)) {
return FALSE;
}
// this node is either cacheable or maybecacheable
// check its selection predicate
ItemExpr *pred = selPredTree() ? selPredTree() :
getSelectionPred().rebuildExprTree();
if (pred) {
cwa.setHasPredicate();
// is selection predicate cacheable?
if (pred->hasNoLiterals(cwa)) {
// predicate with no literals is cacheable
}
else {
cwa.setPredHasNoLit(FALSE);
if (!pred->isCacheableExpr(cwa)) {
// a non-cacheable selection predicate
// renders entire RelExpr non-cacheable.
setNonCacheable();
return FALSE;
}
}
}
return TRUE; // RelExpr may be cacheable
}
default: { const NABoolean notYetImplemented = FALSE;
CMPASSERT(notYetImplemented);
return FALSE;
}
}
}
// is this ExprNode cacheable after this phase?
NABoolean RelExpr::isCacheableNode(CmpPhase phase) const
{
switch (phase) {
case CmpMain::PARSE:
return cacheable_ == ExprNode::CACHEABLE_PARSE;
case CmpMain::BIND:
return cacheable_ == ExprNode::CACHEABLE_BIND ||
cacheable_ == ExprNode::CACHEABLE_PARSE;
default:
break;
}
return FALSE;
}
// change literals of a cacheable query into ConstantParameters
RelExpr* RelExpr::normalizeForCache(CacheWA& cwa, BindWA& bindWA)
{
if (nodeIsNormalizedForCache()) {
return this;
}
// replace descendants' literals into ConstantParameters
normalizeKidsForCache(cwa, bindWA);
if (cwa.getPhase() >= CmpMain::BIND) {
if (selection_) {
selection_ = selection_->normalizeForCache(cwa, bindWA);
}
else {
selectionPred().normalizeForCache(cwa, bindWA);
}
// RelExpr::bindSelf has done this line during binding; but, we
// must redo it to recognize any new constantparameters created
// by the above normalizeForCache call(s) as RelExpr inputs.
getGroupAttr()->addCharacteristicInputs
(bindWA.getCurrentScope()->getOuterRefs());
}
markAsNormalizedForCache();
return this;
}
// change literals in cacheable query's kids into ConstantParameters
void RelExpr::normalizeKidsForCache(CacheWA& cachewa, BindWA& bindWA)
{
Int32 arity = getArity();
for (Int32 x = 0; x < arity; x++) {
child(x) = child(x)->normalizeForCache(cachewa, bindWA);
}
}
// mark this ExprNode as cacheable after this phase
void RelExpr::setCacheableNode(CmpPhase phase)
{
switch (phase) {
case CmpMain::PARSE:
cacheable_ = ExprNode::CACHEABLE_PARSE;
break;
case CmpMain::BIND:
cacheable_ = ExprNode::CACHEABLE_BIND;
break;
default:
break;
}
}
// append an ascii-version of RelRoot into cachewa.qryText_
void RelRoot::generateCacheKey(CacheWA &cwa) const
{
RelExpr::generateCacheKey(cwa);
ItemExpr *cExpr = compExprTree_ ? compExprTree_ :
compExpr_.rebuildExprTree();
if (cExpr) {
// append any select list into cache key
cwa += " cExpr:";
cExpr->generateCacheKey(cwa);
// reflect any "[first n]"
cwa += ((RelRoot*)this)->needFirstSortedRows() ? " 1stN" : " ";
// Should the select_list aliases be a part of the cache key?
// Their not affecting the compiled plan argues for their exclusion.
// Their affecting sqlci's expected output argues for their inclusion.
RETDesc *rDesc = getRETDesc();
CollIndex degree, x;
if (rDesc && (degree=rDesc->getDegree()) > 0) {
cwa += " sla:";
for (x = 0; x < degree; x++){
cwa += rDesc->getColRefNameObj(x).getColName().data();
cwa += " ";
}
// fix 0-061115-0532 (query cache didn't handle select with embedded
// update correctly). New/Old corr. names are recorded for embedded
// updates here for exact match. This is important because otherwise
// a reuse of a query returning the old/new version of values for
// a query requesting new/old version is totally possible and
// unacceptable.
//
// Sample embedded update queries
// select * from (update tab1 set x = x + 1 where x > 1 return new.*) y;
// select * from (update tab1 set x = x + 1 where x > 1 return new.x, old.y) y;
//
if ( cwa.isUpdate() && isTrueRoot() == FALSE ) {
cwa += " corrNamTok:";
cwa += rDesc->getBindWA()->getCorrNameTokens();
}
}
}
// order by clause is important
ItemExpr *orderBy = orderByTree_ ? orderByTree_ :
reqdOrder_.rebuildExprTree();
if (orderBy) {
cwa += " order:";
orderBy->generateCacheKey(cwa);
}
// statement-level access type & lock mode are important for multiuser
// applications. both are reflected in the stmt-level and/or context-wide
// TransMode. So, we mimic RelRoot::codeGen logic here: "copy the current
// context-wide TransMode, then overlay with this stmt's 'FOR xxx ACCESS'
// setting, if any".
TransMode tmode;
tmode.updateTransMode(CmpCommon::transMode());
StmtLevelAccessOptions &opts = ((RelRoot*)this)->accessOptions();
if (opts.accessType() != TransMode::ACCESS_TYPE_NOT_SPECIFIED_) {
tmode.updateAccessModeFromIsolationLevel
(TransMode::ATtoIL(opts.accessType()));
tmode.setStmtLevelAccessOptions();
}
if (isTrueRoot()) {
// these are needed by Javier's qc stats virtual tbl interface
cwa.setIsoLvl(tmode.getIsolationLevel());
cwa.setAccessMode(tmode.getAccessMode());
cwa.setAutoCommit(tmode.getAutoCommit());
cwa.setFlags(tmode.getFlags());
cwa.setRollbackMode(tmode.getRollbackMode());
cwa.setAutoabortInterval(tmode.getAutoAbortIntervalInSeconds());
cwa.setMultiCommit(tmode.getMultiCommit());
}
// needed to distinguish these queries and avoid a false hit
// select * from (delete from t where a=2) as t;
// select * from (delete from t where a=2 for SKIP CONFLICT ACCESS) as t;
char mode[40];
str_itoa(tmode.getIsolationLevel(), mode); cwa += " il:"; cwa += mode;
str_itoa(tmode.getAccessMode(), mode); cwa += " am:"; cwa += mode;
// Solution: 10-060418-5903
str_itoa(cwa.getIsoLvlForUpdates(), mode); cwa += " ilu:"; cwa += mode;
str_itoa(tmode.getAutoCommit(), mode); cwa += " ac:"; cwa += mode;
str_itoa(tmode.getFlags(), mode); cwa += " fl:"; cwa += mode;
str_itoa(tmode.getRollbackMode(), mode); cwa += " rm:"; cwa += mode;
str_itoa(tmode.getAutoAbortIntervalInSeconds(), mode); cwa += " ai:"; cwa += mode;
str_itoa(tmode.getMultiCommit(), mode); cwa += " mc:"; cwa += mode;
if (opts.lockMode() != LOCK_MODE_NOT_SPECIFIED_) {
// need to distinguish these queries and avoid a false hit
// select * from t in share mode;
// select * from t in exclusive mode;
str_itoa(opts.lockMode(), mode); cwa += " lm:"; cwa += mode;
}
// updatableSelect_ is essential. Otherwise, queries like
// "select * from t" and "select * from t for update" can confuse
// query caching into a false hit, causing fullstack/test051 to fail.
if (updatableSelect_) {
cwa += " 4updt ";
}
// for update of col [,col]... clause is important
ItemExpr *updCol = updateColTree_ ? updateColTree_ :
updateCol_.rebuildExprTree();
if (updCol) {
cwa += " updCol:";
updCol->generateCacheKey(cwa);
}
// making the CQS part of the key is more efficient than calling
// CompilerEnv::changeEnv() in ControlDB::setRequiredShape()
if (reqdShape_) {
reqdShape_->unparse(cwa.reqdShape_);
}
}
// is this entire expression cacheable after this phase?
NABoolean RelRoot::isCacheableExpr(CacheWA& cwa)
{
//queries prefixed by display are not cacheable e.g. display select * from ...
if(getDisplayTree())
return FALSE;
// Parallel extract producer queries are not cacheable
if (numExtractStreams_ > 0)
return FALSE;
// descend to scans early to get cwa.numberOfScans_
if (!RelExpr::isCacheableExpr(cwa)) {
return FALSE;
}
if (cwa.getPhase() == CmpMain::PARSE) {
if (compExprTree_ || compExpr_.entries() > 0) {
// insert-returning is not cacheable after parse
return FALSE;
}
}
else if (cwa.getPhase() >= CmpMain::BIND) {
// make sure select list is cacheable
if (compExprTree_) {
if (!compExprTree_->isCacheableExpr(cwa)) {
return FALSE;
}
}
else if (!compExpr_.isCacheableExpr(cwa)) {
return FALSE;
}
}
if (isAnalyzeOnly())
return FALSE;
return TRUE;
}
// change literals of a cacheable query into ConstantParameters and save
// true root into cachewa so we can "bind" ConstantParameters as "inputvars"
RelExpr* RelRoot::normalizeForCache(CacheWA& cwa, BindWA& bindWA)
{
if (nodeIsNormalizedForCache()) {
return this;
}
if (isTrueRoot()) {
cwa.setTopRoot(this);
}
if (cwa.getPhase() >= CmpMain::BIND) {
// replace any select list literals into constant parameters
if (compExprTree_) {
compExprTree_ = compExprTree_->normalizeForCache(cwa, bindWA);
}
else {
compExpr_.normalizeForCache(cwa, bindWA);
}
}
// replace descendants' literals into ConstantParameters
RelExpr *result = RelExpr::normalizeForCache(cwa, bindWA);
if (cwa.getPhase() >= CmpMain::BIND) {
// query tree has undergone BINDing, but RelExpr::normalizeForCache
// may have introduced new ConstantParameters in place of ConstValues;
// we want to BIND these new ConstantParameters but a RelRoot::bindNode()
// call here would be overkill; we just want these new ConstantParameters
// to be "bound" as "inputvars"; so, we selectively cut and paste code
// from BindRelExpr.cpp RelRoot::bindNode into here to "bind" any new
// ConstantParameters as "inputvars".
ItemExpr *inputVarTree = removeInputVarTree();
if (inputVarTree) {
inputVarTree->convertToValueIdList(inputVars(), &bindWA, ITM_ITEM_LIST);
if (bindWA.errStatus()) {
return NULL;
}
}
}
return result;
}
// append an ascii-version of Scan into cachewa.qryText_
void Scan::generateCacheKey(CacheWA &cwa) const
{
RelExpr::generateCacheKey(cwa);
// Fix to 10-010618-3505, 10-010619-3515: include this Scan table's
// RedefTime into cwa.qryText_ to make sure we get a cache hit only on
// query that reference table(s) that have not changed since the query's
// addition to the cache. The queries that reference altered table(s)
// will never be hit again and will eventually age out of the cache.
const NATable *tbl;
if (cwa.getPhase() >= CmpMain::BIND &&
getTableDesc() && (tbl=getTableDesc()->getNATable()) != NULL) {
char redefTime[40];
convertInt64ToAscii(tbl->getRedefTime(), redefTime);
cwa += " redef:";
cwa += redefTime;
if (tbl->isHiveTable()) {
char lastModTime[40];
Int64 mTime = tbl->getClusteringIndex()->getHHDFSTableStats()->getModificationTS();
convertInt64ToAscii(mTime, lastModTime);
cwa += " lastMod:";
cwa += lastModTime;
cwa += " numFiles:";
char numFiles[20];
Int64 numberOfFiles = tbl->getClusteringIndex()->getHHDFSTableStats()->getNumFiles();
sprintf(numFiles, " %ld", numberOfFiles);
cwa += numFiles ;
}
// save pointer to this table. later, QueryCache::addEntry will use
// this pointer to get to this table's histograms's timestamp
cwa.addTable( (NATable*)tbl );
// If PARTITION clause has been used we must reflect that in the key.
if (tbl->isPartitionNameSpecified()) {
cwa += " partition:";
cwa += tbl->getClusteringIndex()->getFileSetName().getQualifiedNameAsString().data();
}
// If PARTITION range has been used we must reflect that in the key.
else if (tbl->isPartitionRangeSpecified()) {
cwa += " partition:";
char str[100];
sprintf(str, " from %d to %d",
tbl->getExtendedQualName().getPartnClause().getBeginPartitionNumber() ,
tbl->getExtendedQualName().getPartnClause().getEndPartitionNumber());
cwa += str;
}
}
// We must reflect userTableName_.location into cache key.
// Otherwise, two queries which differ only in location such as
// table table (table T058a, location $system.zsd12345.x1234500);
// table table (table T058a, location $data .zsd12345.x1234500);
// can confuse our query caching code to return a false hit and
// cause fullstack/test058 to fail.
cwa += userTableName_.getLocationName().data();
// Same with stream_ because queries like
// "select * from t" and "select * from stream(t)" can
// confuse query caching into a false hit causing test079 to fail.
if (stream_) {
cwa += " stream ";
}
if (getHbaseAccessOptions())
{
cwa += " hbaseVersions: ";
char numVersions[20];
sprintf(numVersions, " %d", getHbaseAccessOptions()->getHbaseVersions());
cwa += numVersions ;
}
}
// is this entire expression cacheable after this phase?
NABoolean Scan::isCacheableExpr(CacheWA& cwa)
{
if (cwa.getPhase() >= CmpMain::BIND) {
// save scan's TableDesc
cwa.incNofScans(tabId_);
// native hbase access is not cacheable for now.
if ((getTableDesc()->getNATable()->isHbaseRowTable()) ||
(getTableDesc()->getNATable()->isHbaseCellTable()))
return FALSE;
if (stream_) { // pub-sub streams are not cacheable
return FALSE;
}
cwa.setConditionallyCacheable();
if (CmpCommon::getDefaultLong(MVQR_REWRITE_LEVEL) >= 1 &&
QRDescGenerator::hasRewriteEnabledMVs(getTableDesc())) {
cwa.setRewriteEnabledMV();
}
return RelExpr::isCacheableExpr(cwa);
}
return FALSE;
}
// append an ascii-version of Tuple into cachewa.qryText_
void Tuple::generateCacheKey(CacheWA &cwa) const
{
// Do not call RelExpr::generateCacheKey(cwa) here because it's redundant.
// It does the same things as the code below. RelExpr::generateCacheKey()
// calls Tuple::getText() which has logic similar to the following code.
ItemExpr *tExpr = tupleExprTree() ? tupleExprTree() :
tupleExpr_.rebuildExprTree();
if (tExpr) {
cwa += " tupExpr:";
tExpr->generateCacheKey(cwa);
}
else {
RelExpr::generateCacheKey(cwa);
}
}
// is this entire expression cacheable after this phase?
NABoolean Tuple::isCacheableExpr(CacheWA& cwa)
{
// we do not call RelExpr::isCacheableExpr here because it's redundant
// -- Tuple is a leaf node and has no predicates.
ItemExpr *tExpr = tupleExprTree() ? tupleExprTree() :
tupleExpr_.rebuildExprTree();
return tExpr->isCacheableExpr(cwa);
}
// change literals of a cacheable query into ConstantParameters
RelExpr* Tuple::normalizeForCache(CacheWA& cwa, BindWA& bindWA)
{
if (nodeIsNormalizedForCache()) {
return this;
}
if (tupleExprTree_) {
tupleExprTree_ = tupleExprTree_->normalizeForCache(cwa, bindWA);
}
else {
tupleExpr_.normalizeForCache(cwa, bindWA);
}
// replace descendants' literals into ConstantParameters
return RelExpr::normalizeForCache(cwa, bindWA);
}
// append an ascii-version of Union into cachewa.qryText_
void Union::generateCacheKey(CacheWA &cwa) const
{
RelExpr::generateCacheKeyNode(cwa);
char buf[40];
cwa += " flgs_: ";
convertInt64ToAscii(flags_, buf);
cwa += buf;
cwa += " ctrFlgs_: ";
convertInt64ToAscii(controlFlags_, buf);
cwa += buf;
cwa += " sysGen_: ";
cwa += (isSystemGenerated_) ? "1" : "0";
// turn on the following when condExprTree_ and trigExceptExprTree_
// are considered part of the key
//
//if (condExprTree_) {
// cwa += " condExprTree_: ";
// condExprTree_->generateCacheKey(cwa);
//}
//if (trigExceptExprTree_) {
// cwa += " trigExceptExprTree_: ";
// trigExceptExprTree_->generateCacheKey(cwa);
//}
generateCacheKeyForKids(cwa);
}
NABoolean Update::isCacheableExpr(CacheWA& cwa)
{
cwa.setIsUpdate(TRUE);
return GenericUpdate::isCacheableExpr(cwa);
}
// append an ascii-version of FastExtract into cachewa.qryText_
void FastExtract::generateCacheKey(CacheWA &cwa) const
{
RelExpr::generateCacheKeyNode(cwa);
char buf[40];
cwa += " targType_ ";
str_itoa(getTargetType(), buf);
cwa += buf;
cwa += " targName_ ";
cwa += getTargetName();
cwa += " delim_ ";
cwa += getDelimiter();
cwa += " isAppend_ ";
cwa += isAppend() ? "1" : "0";
cwa += " includeHeader_ ";
cwa += includeHeader() ? "1" : "0";
cwa += " cType_ ";
str_itoa(getCompressionType(), buf);
cwa += buf;
cwa += " nullString_ ";
cwa += getNullString();
cwa += " recSep_ ";
cwa += getRecordSeparator();
generateCacheKeyForKids(cwa);
}