| /********************************************************************** |
| // @@@ 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: BindWA.Cpp |
| * Description: The workarea used by the name binder |
| * Created: 4/27/94 |
| * Language: C++ |
| * |
| * |
| * A Norwegian named Johan Vaaler should be credited with the invention |
| * of the paper clip in 1899. However, as the story goes, Norway had no |
| * patent law at the time, and though Vaaler's drawing was accepted by a |
| * special government commission, he had to seek an actual patent in Germany. |
| * Norwegians are said to have remembered proudly the humble item's origins |
| * in their country when, during WWII, they "fastened paper clips to their |
| * jacket lapels to show patriotism and irritate the Germans." Wearing a |
| * paper clip could result in arrest, but the function of the device, |
| * "TO BIND TOGETHER," took on the fiercely symbolic meaning of |
| * "people joining against the forces of occupation." |
| * -- Henry Petroski, _The Evolution of Useful Things_ |
| * |
| ************************************************************************* |
| */ |
| |
| #define SQLPARSERGLOBALS_FLAGS // should precede all other #include's |
| |
| #include "Sqlcomp.h" |
| #include "BindWA.h" |
| #include "CmpContext.h" |
| #include "StmtDDLCreateView.h" |
| #include "RelMisc.h" |
| #include "ItemOther.h" |
| #include "RelJoin.h" |
| #include "ItemSubq.h" |
| #include "ItemFunc.h" |
| |
| #include "RelMisc.h" |
| #include "ItemOther.h" |
| #include "RelJoin.h" |
| #include "RelUpdate.h" |
| #include "MvRefreshBuilder.h" |
| |
| |
| #define SQLPARSERGLOBALS_NADEFAULTS |
| #include "SqlParserGlobalsCmn.h" |
| #include "SqlParserGlobals.h" // should be last #include |
| |
| // *********************************************************************** |
| // BindScope() |
| // *********************************************************************** |
| BindScope::BindScope(BindWA* bindWA) : |
| bindWA_(bindWA), |
| xtnm_(bindWA ? bindWA->wHeap() : NULL), |
| RETDesc_(NULL), |
| sequenceNode_(NULL), |
| ncToOldMap_(NULL), |
| OlapPartitionChange_ (NULL), |
| HasOlapSeqFunctions_ ( BindScope::OLAPUNKNOWN_ ), |
| isFirstOlapWindowSpec_( TRUE ), |
| inViewExpansion_(FALSE) |
| { |
| xtnmStack()->createXTNM(); |
| } // BindScope::BindScope() |
| |
| // *********************************************************************** |
| // ~BindScope() |
| // *********************************************************************** |
| BindScope::~BindScope() |
| { |
| } // BindScope::~BindScope() |
| |
| // *********************************************************************** |
| // BindScope::mergeOuterRefs() |
| // *********************************************************************** |
| void BindScope::mergeOuterRefs(const ValueIdSet& other, NABoolean keepLocalRefs) |
| { |
| outerRefs_ += other; |
| |
| if (!keepLocalRefs) |
| { |
| // Get the list of valueIds that this scope exposes and remove |
| // them from the local references |
| ValueIdList localRefList; |
| if (RETDesc_) |
| RETDesc_->getValueIdList(localRefList,USER_AND_SYSTEM_COLUMNS); |
| |
| ValueIdSet localRefSet = localRefList; |
| outerRefs_ -= localRefSet; |
| |
| // subtract any other local references |
| outerRefs_ -= localRefs_; |
| } |
| } // BindScope::mergeOuterRefs() |
| |
| |
| // *********************************************************************** |
| // BindWA() |
| // *********************************************************************** |
| BindWA::BindWA(SchemaDB *schemaDB, CmpContext* cmpContext, NABoolean inDDL, NABoolean allowExtTables) |
| : schemaDB_(schemaDB) |
| , currentCmpContext_(cmpContext) |
| , inputVars_(cmpContext ? cmpContext->statementHeap() : NULL) |
| , scopes_(cmpContext ? cmpContext->statementHeap() : NULL) |
| , stoiList_(cmpContext ? cmpContext->statementHeap() : NULL) |
| , udrStoiList_(cmpContext ? cmpContext->statementHeap() : NULL) |
| , coProcAggrList_(cmpContext ? cmpContext->statementHeap() : NULL) |
| , seqValList_(cmpContext ? cmpContext->statementHeap() : NULL) |
| , defaultSchema_(cmpContext ? cmpContext->statementHeap() : NULL) |
| , RETDescList_(cmpContext ? cmpContext->statementHeap() : NULL) |
| , tableViewUsageList_(cmpContext ? cmpContext->statementHeap() : NULL) |
| , inDDL_(inDDL) |
| // , inIndexMaint_(FALSE) |
| // , inRIMaint_(FALSE) |
| , inViewWithCheckOption_(NULL) |
| , viewCount_(0) |
| , errFlag_(FALSE) |
| , uniqueNum_(0) |
| , uniqueIudNum_(0) //++Triggers, |
| , maxIudNum_(0) //++Triggers, |
| , isBindingMvRefresh_(FALSE) |
| , isPropagateOpAndSyskeyColumns_(FALSE) |
| , isExpandMvTree_(FALSE) |
| , isBindingOnStatementMv_(FALSE) |
| , isBindingIUD_(FALSE) |
| , triggersList_(NULL) |
| , pNameLocList_(NULL) |
| , pUsageParseNode_(NULL) |
| , hostArraysArea_(NULL) |
| , assignmentStArea_(NULL) |
| , topRoot_(NULL) |
| , inTrigger_ ( FALSE ) |
| , spInParams_ (ItemExprList (wHeap())) |
| , spOutParams_ (ItemExprList (wHeap())) |
| , currOrdinalPosition_ (0) |
| , currParamMode_ (COM_UNKNOWN_DIRECTION) |
| , bindingCall_ (FALSE) |
| , maxResultSets_(0) |
| , spHVDPs_ (wHeap ()) |
| , currSPName_ (NULL) |
| , dupWarning_ (FALSE) |
| , inGenericUpdate_ (FALSE) |
| , renameToScanTable_ (FALSE) |
| , inViewExpansion_ (FALSE) |
| , inliningInfoFlagsToSetRecursivly_(0) |
| , currCSE_(NULL) |
| , inCTAS_(FALSE) |
| , viewsUsed_("", wHeap()) |
| , hasDynamicRowsetsInQuery_(FALSE) |
| , inReadOnlyQuery_(FALSE) |
| , embeddedIUDStatement_(FALSE) |
| , insertSelectStatement_(FALSE) |
| , mergeStatement_(FALSE) |
| , corrNameTokens_("", wHeap()) |
| , uninitializedMvList_ (NULL) |
| , toOverrideSchema_ (TRUE) |
| , overrideSchemaEnabled_ (FALSE) |
| , routineInvocationNum_ (0) |
| , isBindTrueRoot_(FALSE) |
| , noNeedToLimitSchemaAccess_(FALSE) |
| , holdableType_(SQLCLIDEV_NONHOLDABLE) |
| , isFastExtract_(FALSE) |
| , failedForPrivileges_(FALSE) |
| , shouldLogAccessViolations_(FALSE) |
| , queryCanUseSeaMonster_(-1) |
| , volatileTableFound_(FALSE) |
| , outerAggScope_(NULL) |
| , hasCallStmts_(FALSE) |
| , isTrafLoadPrep_(FALSE) |
| , flags_(0) |
| , udfList_(wHeap()) |
| { |
| // get current default schema, using NAMETYPE NSK or ANSI rules |
| defaultSchema_ = schemaDB_->getDefaultSchema(SchemaDB::APPLY_NAMETYPE_RULES); |
| |
| // create sentinel scope |
| initNewScope(); |
| |
| hcui_ = new(cmpContext->statementHeap()) HbaseColUsageInfo(cmpContext->statementHeap()); |
| |
| setAllowExternalTables(allowExtTables); |
| } // BindWA::BindWA() |
| |
| // *********************************************************************** |
| // ~BindWA() |
| // *********************************************************************** |
| BindWA::~BindWA() |
| { |
| inputVars_.clearAndDestroy(); |
| //cerr << "BindWA::~BindWA " << (void*)this << " " << RETDescList_.entries() << endl; // ## |
| } // BindWA::~BindWA() |
| |
| // *********************************************************************** |
| // BindWA::initNewScope() |
| // *********************************************************************** |
| void BindWA::initNewScope() |
| { |
| CollIndex i = scopes_.entries(); |
| BindScope* newScope = new(wHeap()) BindScope(this); |
| scopes_.insert(newScope); |
| if (i) { |
| // newScope->context()->inSubquery() = scopes_[i-1]->context()->subqSeen(); |
| newScope->context()->inUpdateOrInsert() = scopes_[i-1]-> |
| context()->inUpdateOrInsert(); |
| // do not pass inViewExpansion to subquery, for override_schema, etc. |
| if (//overrideSchemaEnabled_ && |
| (!scopes_[i-1]->context()->inSubquery())) |
| newScope->setInViewExpansion(scopes_[i-1]->getInViewExpansion()); |
| } |
| } |
| |
| // *********************************************************************** |
| // BindWA::getCurrentScope() |
| // *********************************************************************** |
| BindScope* BindWA::getCurrentScope() const |
| { |
| return scopes_[scopes_.entries() - 1]; // Return the last item of the list |
| } // BindWA::getCurrentScope() |
| |
| // *********************************************************************** |
| // BindWA::getPreviousScope() |
| // *********************************************************************** |
| BindScope* BindWA::getPreviousScope(BindScope *currentScope) const |
| { |
| // |
| // Find the index of the current scope in the BindScope list. Assert that |
| // the scope is in the list. If the scope is not the first, return a pointer |
| // to the previous scope. Otherwise, return a NULL. |
| // |
| CollIndex i = scopes_.index(currentScope); |
| CMPASSERT(i != NULL_COLL_INDEX); |
| if (i > 0) |
| return scopes_[i - 1]; |
| return NULL; |
| } // BindWA::getPreviousScope() |
| |
| // *********************************************************************** |
| // BindWA::findNextScopeWithTriggerInfo() |
| // Starting with the next scope, look for a scope that has trigger |
| // information. If the parameter is NULL, start with the current scope. |
| // *********************************************************************** |
| BindScope *BindWA::findNextScopeWithTriggerInfo(BindScope *currentScope) |
| { |
| if (currentScope == NULL) |
| currentScope = getCurrentScope(); |
| else |
| currentScope = getPreviousScope(currentScope); |
| |
| while ((currentScope!=NULL) && |
| (currentScope->context()->triggerObj() == NULL)) |
| currentScope = getPreviousScope(currentScope); |
| |
| return currentScope; |
| } |
| |
| // *********************************************************************** |
| // MV -- |
| void BindWA::markScopeWithMvBindContext(MvBindContext *mvContext) |
| { |
| getCurrentScope()->context()->getMvBindContext() = mvContext; |
| } |
| |
| // *********************************************************************** |
| const MvBindContext * |
| BindWA::getClosestMvBindContext(BindScope *currentScope) const |
| { |
| if (!isBindingMvRefresh() && !isBindingOnStatementMv()) |
| return NULL; |
| |
| if (currentScope == NULL) |
| currentScope = getCurrentScope(); |
| |
| while ((NULL != currentScope) && |
| (NULL == currentScope->context()->getMvBindContext())) |
| { |
| currentScope = getPreviousScope(currentScope); |
| } |
| |
| if(NULL != currentScope) |
| { |
| return currentScope->context()->getMvBindContext(); |
| } |
| |
| return NULL; |
| } // getClosestMvBindContext |
| /****************************************************************************** |
| Method: BindWA::addUninitializedMv |
| |
| Description: |
| Add uninitialized mvs to this list. This list is kept for runtime |
| to report errors on DML statments made on uninitialized mvs. |
| |
| Parameters: |
| const char *physicalName |
| - the location of the mv |
| |
| const char *ansiName |
| - the name of the mv |
| |
| Return: NONE |
| |
| ******************************************************************************/ |
| void |
| BindWA::addUninitializedMv( const char *physicalName, const char *ansiName ) |
| { |
| UninitializedMvName * pMvName = new(wHeap())UninitializedMvName; |
| |
| // first use? |
| if( NULL == uninitializedMvList_ ) |
| { |
| uninitializedMvList_ = new (wHeap()) |
| UninitializedMvNameList( wHeap(), 1 ); |
| } |
| |
| // add the names to the list |
| pMvName->setPhysicalName( physicalName ); |
| pMvName->setAnsiName( ansiName ); |
| uninitializedMvList_->insert( pMvName ); |
| } |
| |
| |
| |
| |
| |
| // *********************************************************************** |
| // BindWA::getPreviousScope() |
| // *********************************************************************** |
| BindScope* BindWA::getSubqueryScope (BindScope *currentScope) const |
| { |
| // This method returns the BindScope* with which we determine whether |
| // we're in a subquery (for the purposes to determining a certain syntax |
| // error). This involves checking the previousScope, and if that's a |
| // GroupByAgg (or possibly some other node, in some, as yet unfound bug |
| // ...?), then we look ahead one scope further. |
| // |
| // It is not clear whether we need to recurse (potentially) multiple |
| // times. For now we do, though this may later introduce problems, so |
| // the 'while' stmt below can be replaced with an 'if' if necessary. |
| // |
| // Guaranteed semantics of this function: If getPreviousScope() returns |
| // a non-NULL BindScope*, then this method will too. |
| // |
| // See the comments in bindRowValues(), BindRelExpr.cpp, for a more |
| // complete discussion of how/why this method is used. |
| // |
| BindScope * prev = NULL ; |
| BindScope * retVal = getPreviousScope (currentScope) ; |
| while ( retVal && retVal->context()->lookAboveToDecideSubquery() ) |
| { |
| prev = retVal ; |
| retVal = getPreviousScope (prev) ; |
| } |
| |
| if ( retVal ) return retVal ; |
| else return prev ; |
| } // BindWA::getSubqueryScope() |
| |
| |
| // *********************************************************************** |
| // BindWA::removeCurrentScope() |
| // *********************************************************************** |
| void BindWA::removeCurrentScope(NABoolean keepLocalRefs) |
| { |
| // |
| // Remove the current scope from the BindScope list. If there is a parent |
| // scope, merge the outer references of the old current scope into the new |
| // current scope. |
| // This is NOT the same as BindScopeList::removeScope(), called by |
| // ~BindScopeList() as a safety net against memory leaks. |
| // |
| BindScope *currScope; |
| scopes_.getLast(currScope); |
| if (NOT scopes_.isEmpty()) |
| getCurrentScope()->mergeOuterRefs(currScope->getOuterRefs(),keepLocalRefs); |
| |
| delete currScope; |
| } // BindWA::removeCurrentScope() |
| |
| // *********************************************************************** |
| // BindWA::findColumn() |
| // |
| // The first method searches for the given name in the BindScopes. |
| // It starts from the current BindScope. If the given name is not |
| // found in the ColumnNameMap associated with a BindScope, the |
| // search progresses to the previous BindScope. The search terminates |
| // either when a ColumnNameMap corresponding to the given name is |
| // found or the outermost BindScope has been searched unsucessfully. |
| // |
| // If the name is found, the method returns a pointer to the xcnmEntry |
| // and a pointer to the BindScope in which it was found. Otherwise, it |
| // returns a NULL and a pointer to the outermost BindScope. |
| // |
| // The second findColumn() locates the xcnmEntry to a ColumnDesc |
| // known to exist in the current scope (no backward searching thru scopes!). |
| // *********************************************************************** |
| ColumnNameMap *BindWA::findColumn(const ColRefName &colRefName, |
| BindScope *&bindScope) |
| { |
| bindScope = getCurrentScope(); |
| while (bindScope) { |
| RETDesc *resultTable = bindScope->getRETDesc(); |
| if (resultTable) { |
| ColumnNameMap *result = resultTable->findColumn(colRefName); |
| if (!result) // check if need to try public schema if not found in default schema |
| { |
| NAString publicSchema = ""; |
| CmpCommon::getDefault(PUBLIC_SCHEMA_NAME, publicSchema, FALSE); |
| ComSchemaName pubSchema(publicSchema); |
| ColRefName newColRef = colRefName; |
| if ( !pubSchema.getSchemaNamePart().isEmpty() |
| && colRefName.getCorrNameObj().getQualifiedNameObj().getSchemaName().isNull() ) |
| { |
| newColRef.getCorrNameObj().getQualifiedNameObj().setSchemaName( |
| pubSchema.getSchemaNamePart().getInternalName()); |
| if ( !pubSchema.getCatalogNamePart().isEmpty() ) |
| newColRef.getCorrNameObj().getQualifiedNameObj().setCatalogName( |
| pubSchema.getCatalogNamePart().getInternalName()); |
| result = resultTable->findColumn(newColRef); |
| } |
| } |
| if (result) |
| return result; |
| } |
| bindScope = getPreviousScope(bindScope); |
| } |
| return NULL; |
| } // BindWA::findColumn() the first |
| |
| ColumnNameMap *BindWA::findColumn(const ColumnDesc &columnDesc) |
| { |
| ColumnNameMap *result = NULL; |
| RETDesc *resultTable = getCurrentScope()->getRETDesc(); |
| if (resultTable) |
| result = resultTable->findColumn(columnDesc.getColRefNameObj()); |
| CMPASSERT(result); |
| return result; |
| } // BindWA::findColumn() the second |
| |
| // *********************************************************************** |
| // BindWA::findCorrName() |
| // |
| // Same search strategy as the first findColumn(). |
| // *********************************************************************** |
| ColumnDescList *BindWA::findCorrName(const CorrName &corrName, |
| BindScope *&bindScope) |
| { |
| bindScope = getCurrentScope(); |
| while (bindScope) { |
| RETDesc *resultTable = bindScope->getRETDesc(); |
| if (resultTable) { |
| ColumnDescList *result = resultTable->getQualColumnList(corrName); |
| if (result) |
| return result; // This list may have zero entries (SYSKEY the only |
| } // column from that table -- see RETDesc::addColumn). |
| bindScope = getPreviousScope(bindScope); |
| } |
| return NULL; |
| } // BindWA::findCorrName() |
| |
| // Same search strategy as the first findColumn(). |
| StmtLevelAccessOptions *BindWA::findUserSpecifiedAccessOption() |
| { |
| BindScope *bindScope = getCurrentScope(); |
| while (bindScope) { |
| BindContext *context = bindScope->context(); |
| if (context) { |
| StmtLevelAccessOptions *axOpts = CONST_CAST |
| (StmtLevelAccessOptions*,context->stmtLevelAccessOptions()); |
| if (axOpts && axOpts->userSpecified()) { |
| return axOpts; |
| } |
| } |
| bindScope = getPreviousScope(bindScope); |
| } |
| return NULL; |
| } // BindWA::findUserSpecifiedAccessOption() |
| |
| // *********************************************************************** |
| // BindWA::getTablesInScope() and BindScope::getTablesInScope() |
| // |
| // Return a list of TableNameMaps of all tables that are in scope. |
| // Caller can get the exposed names of the tables and display them, |
| // or tell this function to do that via the optional NAString parameter. |
| // |
| // Same search strategy as the first findColumn(). |
| // *********************************************************************** |
| void BindWA::getTablesInScope(LIST(TableNameMap*) &xtnmList, |
| NAString *formattedList) const |
| { |
| xtnmList.clear(); |
| LIST(TableNameMap*) xtnmPerScope(wHeap()); |
| BindScope *bindScope = getCurrentScope(); |
| while (bindScope) { |
| bindScope->getTablesInScope(xtnmPerScope, NULL); |
| bindScope = getPreviousScope(bindScope); |
| xtnmList.insert(xtnmPerScope); |
| } |
| RETDesc::formatTableList(xtnmList, formattedList); |
| } // BindWA::getTablesInScope() |
| |
| void BindScope::getTablesInScope(LIST(TableNameMap*) &xtnmList, |
| NAString *formattedList) const |
| { |
| xtnmList.clear(); |
| RETDesc *resultTable = getRETDesc(); |
| if (resultTable) resultTable->getTableList(xtnmList, formattedList); |
| } // BindWA::getTablesInScope() |
| |
| // BindScope::addUnresolvedAggregate() |
| |
| // Optionally add this newAggrId to the set of unresolved aggregates. |
| // It will be added if there is not already an equivalent aggregate in |
| // the unresolved aggregate set. The return value is the value id |
| // representing this aggregate. This is either the new ValueId or the |
| // equivalent ValueId found in the set. |
| // |
| ValueId BindScope::addUnresolvedAggregate(ValueId newAggrId) |
| { |
| // Assume that there is no other equivalent aggregate. |
| // |
| ValueId equivId = newAggrId; |
| |
| ItemExpr *newItem = newAggrId.getItemExpr(); |
| Aggregate *newAggr = NULL; |
| if(newItem->isAnAggregate()) |
| { |
| newAggr = (Aggregate *)newItem; |
| } |
| |
| // If this is not an aggregate, then skip the search for an equiv. |
| // |
| if(newAggr) { |
| |
| // Check all the existing aggregates for an equivalent |
| // |
| for(ValueId aggrId = unresolvedAggregates_.init(); |
| unresolvedAggregates_.next(aggrId); |
| unresolvedAggregates_.advance(aggrId) |
| ) |
| { |
| |
| ItemExpr *aggr = aggrId.getItemExpr(); |
| |
| if(newAggr->isEquivalentForBinding(aggr)) { |
| |
| // Found an equivalent, use this one instead. |
| // |
| equivId = aggrId; |
| if(newAggr->origOpType() != aggr->origOpType()) { |
| aggr->setOrigOpType(aggr->getOperatorType()); |
| } |
| break; |
| } |
| } |
| } |
| |
| // Add the new (or maybe old) aggregate to the set. If this was an |
| // existing aggregate that is equivalent to the new aggregatem this |
| // operation is essentially a no-op. |
| // |
| unresolvedAggregates_ += equivId; |
| |
| // Return the equivalent ValueId to the caller so it can be used as |
| // a replacement for the new aggrid. |
| // |
| return equivId; |
| } |
| |
| ValueId BindScope::getEquivalentItmSequenceFunction(ValueId newSeqId) |
| { |
| // |
| ValueId equivId = newSeqId; |
| |
| ItemExpr *newItem = newSeqId.getItemExpr(); |
| ItmSequenceFunction *newSeq = NULL; |
| if(newItem->isASequenceFunction()) |
| { |
| newSeq = (ItmSequenceFunction *)newItem; |
| } |
| |
| // |
| if(newSeq) { |
| |
| // |
| for(ValueId seqId = unresolvedSequenceFunctions_.init(); |
| unresolvedSequenceFunctions_.next(seqId); |
| unresolvedSequenceFunctions_.advance(seqId) ) |
| { |
| |
| ItemExpr *seq = seqId.getItemExpr(); |
| |
| if(newSeq->isEquivalentForBinding(seq)) { |
| |
| equivId = seqId; |
| if(newSeq->origOpType() != seq->origOpType()) { |
| seq->setOrigOpType(seq->getOperatorType()); |
| } |
| break; |
| } |
| } |
| } |
| |
| // Add the new (or maybe old) seq to the set. If this was an |
| // existing seq that is equivalent to the new this |
| // operation is essentially a no-op. |
| // |
| unresolvedSequenceFunctions_ += equivId; |
| |
| // Return the equivalent ValueId to the caller so it can be used as |
| // a replacement for the new aggrid. |
| // |
| return equivId; |
| } |
| |
| |
| // *********************************************************************** |
| // Get default schema/catalog. |
| // |
| // Set default schema/catalog -- |
| // This should be called by the caller of the Binder, and should remain |
| // constant over the binding/compiling of the query. |
| // Caller needs to know what type of query (create schema/dynamic/static) |
| // and pass in the appropriate defaults; autorecompiles must pass in the |
| // *saved* defaults (saved by Generator). |
| // So, Compiler needs to keep track of static defaults and interpret |
| // our 'Declare Schema' stmt; default for 'Decl Sch :hostvar' will be |
| // the prototype value if given, and the previous static defaults otherwise. |
| // Executor needs to keep track of *both* the static defaults and the |
| // dynamic ones (Ansi 'Set Schema' stmts); in particular, 'Decl Sch :hostvar' |
| // is executed, which means the string in :hv is parsed and saved in the |
| // Executor context for static defaults. Both sets of defaults are sent |
| // to Compiler when a compilation is needed. Executor passes its current |
| // static defaults to do a similarity-check on each static stmt it is about |
| // to execute (and of course it opens/accesses the correct table based on |
| // the tablename with current default applied). |
| // *********************************************************************** |
| const SchemaName &BindWA::getDefaultSchema() const |
| { |
| // Return current default schema, using NAMETYPE NSK or ANSI rules. |
| // |
| // *** BindWA::getDefaultSchema() differs from SchemaDB::getDefaultSchema(). |
| // *** The latter by default (not passing in any flags) |
| // *** returns the ANSI schema only. |
| // |
| return defaultSchema_; |
| } |
| |
| void BindWA::setDefaultSchema(const SchemaName& defaultSchema) |
| { |
| defaultSchema_ = defaultSchema; |
| } |
| |
| // *********************************************************************** |
| // BindWA::getCreateViewParseNode() |
| // *********************************************************************** |
| StmtDDLCreateView * BindWA::getCreateViewParseNode() const |
| { |
| if (getUsageParseNodePtr()) |
| { |
| return getUsageParseNodePtr()->castToElemDDLNode()-> |
| castToStmtDDLCreateView(); |
| } |
| return NULL; |
| } |
| |
| // *********************************************************************** |
| // BindWA::inViewDefinition() |
| // BindWA::inMVDefinition() |
| // BindWA::inCheckConstraintDefinition() |
| // *********************************************************************** |
| |
| NABoolean BindWA::inCheckConstraintDefinition() const |
| { |
| return getUsageParseNodePtr() && |
| getUsageParseNodePtr()->getOperatorType() == DDL_ALTER_TABLE_ADD_CONSTRAINT_CHECK; |
| } |
| |
| NABoolean BindWA::inViewDefinition() const |
| { |
| return getUsageParseNodePtr() && |
| getUsageParseNodePtr()->getOperatorType() == DDL_CREATE_VIEW; |
| } |
| |
| NABoolean BindWA::inMVDefinition() const |
| { |
| return getUsageParseNodePtr() && |
| getUsageParseNodePtr()->getOperatorType() == DDL_CREATE_MV; |
| } |
| |
| // *********************************************************************** |
| // Display/print, for debugging. |
| // *********************************************************************** |
| void BindWA::display() const { print(); } |
| |
| void BindWA::print(FILE* ofd, const char* indent, const char* title) const |
| { |
| } // BindWA::print() |
| |
| //============================================================================ |
| //====================== class MvBindContext =============================== |
| //============================================================================ |
| |
| MvBindContext::~MvBindContext() |
| { |
| delete builder_; |
| } |
| |
| void MvBindContext::setReplacementFor(const QualifiedName *tableName, |
| RelExpr *replacementTree) |
| { |
| //replacementTreeHash_[tableName] = replacementTree; |
| replacementTreeHash_.remove(tableName); |
| replacementTreeHash_.insert(tableName, replacementTree); |
| } |
| |
| RelExpr *MvBindContext::getReplacementFor(const QualifiedName& tableName) const |
| { |
| return replacementTreeHash_.getFirstValue(&tableName); |
| } |
| |
| |
| //============================================================================ |
| //====================== class HostArraysWA =============================== |
| //============================================================================ |
| |
| ItemExpr *BindWA::getHVorDPFromSPDups (ItemExpr *h) |
| { |
| CollIndex numEntries = spHVDPs_.entries(); |
| |
| const NAString &name1 = (h->getOperatorType() == ITM_HOSTVAR) ? |
| ((HostVar *)h)->getName() : ((DynamicParam *)h)->getName(); |
| |
| // The param mode in the BindWA is correct one for the current ItemExpr |
| ComColumnDirection mode1 = this->getCurrParamMode(); |
| |
| for (CollIndex i = 0; i < numEntries; i++) |
| { |
| ItemExpr *expr = spHVDPs_[i]; |
| |
| const NAString &name2 = (expr->getOperatorType() == ITM_HOSTVAR) ? |
| ((HostVar *) expr)->getName() : ((DynamicParam *) expr)->getName(); |
| |
| ComColumnDirection mode2 = (expr->getOperatorType() == ITM_HOSTVAR) ? |
| ((HostVar *) expr)->getParamMode() : |
| ((DynamicParam *) expr)->getParamMode(); |
| |
| if (name1.compareTo(name2) == 0 && mode1 == mode2) |
| return expr; |
| } |
| return NULL; |
| } |
| |
| NABoolean BindWA::checkHVorDPinSPDups (ItemExpr *h) |
| { |
| if ( NULL == getHVorDPFromSPDups (h)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| NABoolean BindWA::checkMultiOutSPParams (ItemExpr *h) |
| { |
| ComColumnDirection mode = (ITM_HOSTVAR == h->getOperatorType()) ? |
| ((HostVar *)h)->getParamMode() : ((DynamicParam *)h)->getParamMode(); |
| |
| if (COM_INPUT_COLUMN == mode) |
| return FALSE; |
| |
| CollIndex numEntries = spOutParams_.entries(); |
| |
| for (CollIndex i = 0; i < numEntries; i++) |
| { |
| const NAString &s1 = (ITM_HOSTVAR == h->getOperatorType()) ? |
| ((HostVar *)h)->getName() : ((DynamicParam *)h)->getName(); |
| const NAString &s2 = (ITM_HOSTVAR == spOutParams_[i]->getOperatorType()) ? |
| ((HostVar *)spOutParams_[i])->getName() : |
| ((DynamicParam *)spOutParams_[i])->getName(); |
| |
| if ((s1.compareTo(s2) == 0) && (s1.compareTo("") != 0)) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| void BindWA::setColumnRefsInStoi(const char* fileName, Lng32 colPosition) |
| { |
| |
| if (getCurrentScope()->context()->inAnyConstraint()) return; |
| |
| if ( |
| (!isBindingMvRefresh()) && |
| ((!inViewExpansion()) || |
| (inViewExpansion() && getCurrentScope()->context()->inWhereClause()))) |
| { |
| // mark column as referenced for select access in stoi. |
| OptSqlTableOpenInfo* stoiInList = NULL; |
| for (CollIndex ii=0; ii < getStoiList().entries(); ii++) |
| { |
| if (strcmp((getStoiList())[ii]->getStoi()->fileName(), |
| fileName) == 0) |
| { |
| stoiInList = getStoiList()[ii]; |
| break; |
| } |
| } |
| if (stoiInList) |
| { |
| stoiInList->getStoi()->setSelectAccess(); |
| stoiInList->addSelectColumn(colPosition); |
| } |
| } |
| } |
| |
| // Scans list of expressions in a VALUES node and replaces all host vars found with |
| // the names used in the RENAME node |
| void HostArraysWA::collectArrays(ItemExpr *parent) |
| { |
| |
| ItemExpr *ptr = parent; |
| |
| while (ptr) { |
| |
| ItemExpr *leftChild; |
| |
| if (ptr->getOperatorType() != ITM_ITEM_LIST) { |
| leftChild = ptr; |
| } |
| else { |
| leftChild = ptr->child(0); |
| } |
| |
| // We create a temporary constant to serve as a dummy tree root |
| ItemExpr *parent = new (CmpCommon::statementHeap()) SystemLiteral(); |
| parent->setChild(0,leftChild); |
| |
| // Traverse this list element. Find all rowset host arrays and replace them |
| // with appropriate scalar variables |
| collectHostVarsInPred(parent, 0); |
| |
| leftChild = parent->child(0); |
| |
| // Add list element (now that we have processed it) to the list |
| // pointed by newItemsList_ |
| if (!lastItemList_) { |
| newItemsList_ = new (bindWA_->wHeap()) ItemList(leftChild, NULL); |
| lastItemList_ = newItemsList_; |
| } |
| else { |
| lastItemList_->child(1) = new (bindWA_->wHeap()) ItemList(leftChild, NULL); |
| lastItemList_ = lastItemList_->child(1); |
| } |
| |
| // Move to the next list element |
| if (ptr->getOperatorType() == ITM_ITEM_LIST) { |
| ptr = ptr->child(1); |
| } |
| else { |
| break; |
| } |
| } |
| if (hasHostArrays()) { |
| setHasHostArraysInTuple(TRUE); |
| } |
| |
| } |
| |
| // Scans list of expressions in a VALUES node and replaces all host vars found with |
| // the names used in the RENAME node. Replaces node with a ROOT, RENAME and ROWSET nodes. |
| RelExpr * HostArraysWA::modifyTupleNode(RelExpr *node) |
| { |
| // listofHostArrays for insert has been collected prior to bind phase, by xformRowsetsinTree() |
| if (hasHostArrays()) { |
| |
| if (getRowwiseRowset()) { |
| // arrays cannot be specified with rowwise rowset. |
| *CmpCommon::diags() << DgSqlCode(-30008); |
| bindWA_->setErrStatus(); |
| return NULL; |
| } |
| |
| // Create new Rowset node |
| RelExpr *newRowSet = new (bindWA_->wHeap()) Rowset(listOfHostArrays_, indexExpr_, |
| inputSizeExpr_); |
| |
| // Create list of scalar variables |
| createNewNames(); |
| |
| RelExpr *newRename = new (bindWA_->wHeap()) RenameTable(newRowSet, "Rowset___", newNames_); |
| |
| RelExpr *newRoot = new (bindWA_->wHeap()) RelRoot(newRename, REL_ROOT, newItemsList_); |
| |
| if (hasHostArraysInTuple()) { |
| ((RelRoot *) newRoot)->setDontOpenNewScope() ; |
| } |
| |
| return newRoot->bindNode(bindWA_); |
| |
| } |
| else if (getRowwiseRowset()) { |
| if (node->getOperatorType() != REL_TUPLE) |
| { |
| bindWA_->setErrStatus(); |
| |
| return NULL; |
| } |
| |
| Tuple * tuple = (Tuple*)node; |
| |
| // Mark all params in the tuple to be part of row which will be |
| // moved from the rowwise rowset buffer. |
| ItemExprList tList(tuple->tupleExprTree(), bindWA_->wHeap()); |
| |
| for (CollIndex i = 0; (i < (CollIndex) tList.entries());i++) |
| { |
| ItemExpr * tupleVal = |
| ((ItemExpr *) tList[i])->castToItemExpr(); |
| if (tupleVal->getOperatorType() == ITM_DYN_PARAM) |
| { |
| // mark this param that it is part of the row in the rowset. |
| ((DynamicParam*)tupleVal)->setRowInRowwiseRowset(); |
| } |
| } |
| |
| // Create new Rowset node |
| RelExpr *newRowSet = |
| new (bindWA_->wHeap()) RowsetRowwise(node); |
| |
| return newRowSet->bindNode(bindWA_); |
| } |
| |
| return NULL; |
| } |
| |
| |
| // Given a node in an ItemExpr whose root is parent, this function traverses |
| // the expression and stores in listOfHostArrays_ all host arrays found; |
| // it also replaces the array with a new variable that will appear |
| // in a Rename node. |
| void HostArraysWA::collectHostVarsInPred(ItemExpr *parent, Int32 childNumber) |
| { |
| if (!parent || !parent->child(childNumber)) { |
| return; |
| } |
| |
| Subquery *tempSubquery; |
| ItemExpr *op = parent->child(childNumber); |
| ItemExpr *tempExpr; |
| |
| switch (op->getOperatorType()) { |
| |
| case REL_ROOT: |
| { |
| collectHostVarsInRelExprTree((RelRoot *) (ItemExpr *) op, RelExpr::UNSPECIFIED_); |
| if (bindWA_->errStatus()) |
| return; |
| } |
| break; |
| |
| // At this point we store the host variable in listOfHostArrays_ if |
| // it happens to be a rowset variable. If it is a dynamic parameter, we |
| // will treat it as a host variable |
| case ITM_DYN_PARAM: |
| case ITM_HOSTVAR: |
| processArrayHostVar(parent, childNumber); |
| break; |
| |
| case ITM_CASE: |
| // Case statement. It has an operand which is not one of its children, |
| // so we have to process it explicitly... |
| |
| // so we have to process it by hand... |
| // to process it explicitly... |
| tempExpr = new (CmpCommon::statementHeap()) SystemLiteral(); |
| tempExpr->setChild(0,((Case *) op)->getCaseOperand()); |
| |
| // Process the operand |
| collectHostVarsInPred(tempExpr, 0); |
| |
| // Reinstall case operand, now that we have replaced any rowset host var |
| // that could have been there |
| ((Case *) op)->setCaseOperand(tempExpr->child(0)); |
| |
| break; |
| |
| default: |
| if (op->isASubquery()) { |
| HostArraysWA *tempWA = bindWA_->getHostArraysArea(); |
| HostArraysWA *subQueryHostArraysWA = new (bindWA_->wHeap()) HostArraysWA(bindWA_); |
| subQueryHostArraysWA->inputSizeExpr_ = inputSizeExpr_ ; //subquery inherits outer query's |
| // ROWSET FOR INPUT SIZE clause. |
| |
| // We create a new environment to process a subquery |
| bindWA_->setHostArraysArea(subQueryHostArraysWA); |
| tempSubquery = (Subquery *) (ItemExpr *) op; |
| tempSubquery->getSubquery()->xformRowsetsInTree(*bindWA_); |
| |
| // Restore previous environment |
| bindWA_->setHostArraysArea(tempWA); |
| } |
| } |
| |
| Lng32 nc = op->getArity(); |
| |
| for (Lng32 j = 0; j < nc; j++) { |
| collectHostVarsInPred(op, j); |
| } |
| |
| } |
| // Searches in list for a host variable whose name is in *inputVar. |
| // If it finds it, replaces *inputVar with the host variable found |
| NABoolean HostArraysWA::findHostVar(ItemExpr **inputVar, ItemExpr *list) |
| { |
| ItemExpr *ptr = list; |
| ItemExpr *child; |
| HostVar *hostVar = (HostVar *) *inputVar; |
| |
| while (ptr) { |
| |
| if (ptr->getOperatorType() != ITM_ITEM_LIST) { |
| child = ptr; |
| } |
| else { |
| child = ptr->child(0); |
| } |
| |
| HostVar * host = (HostVar *) child; |
| |
| // Found it |
| if (host->getName() == hostVar->getName()) { |
| *inputVar = child; |
| return TRUE; |
| } |
| |
| if (ptr->getOperatorType() == ITM_ITEM_LIST) { |
| ptr = ptr->child(1); |
| } |
| else |
| break; |
| } |
| |
| *inputVar = NULL; |
| return FALSE; |
| } |
| |
| // We have found an array host variable in the parse tree. |
| // We store it and replace it with a name that will be used in the rename node. |
| void HostArraysWA::processArrayHostVar(ItemExpr *parent, Int32 childNumber) |
| { |
| ItemExpr *child = parent->child(childNumber); |
| HostVar *node; |
| |
| if (child->getOperatorType() == ITM_DYN_PARAM) { |
| ULng32 inputArrayMaxSize; |
| if (((inputArrayMaxSize = getInputArrayMaxSize()) > 0) |
| || (((DynamicParam *)child)->getRowsetSize() > 0)) { |
| setHasDynamicRowsets(TRUE) ; // dynamic rowsets from either ODBC (first cond.) |
| // (second cond.) or embedded SQL |
| bindWA_->setHasDynamicRowsetsInQuery(TRUE) ; // setting a flag that is global in scope |
| |
| DynamicParam *param = (DynamicParam *) child; |
| ULng32 size ; |
| if (inputArrayMaxSize > 0) { |
| size = inputArrayMaxSize; |
| param->setRowsetSize(size); // for ODBC queries set rowsetsize_ attribute. |
| // Used in Relroot bindnode. |
| } |
| else { |
| size = param->getRowsetSize() ; |
| } |
| // This is an dynamic query. |
| // We do not know yet what the type of the individual rowset elements will be. This |
| // will be determined on a later binding stage. In the case in which we are doing |
| // an INSERT, for instance, the types will be determined in the Insert node |
| SQLRowset *rowsetType = |
| new (bindWA_->wHeap()) SQLRowset(bindWA_->wHeap(), (new (bindWA_->wHeap()) SQLUnknown(bindWA_->wHeap())), |
| size, size); |
| NAString name = param->getName(); |
| if (name.isNull()) { |
| name = "__array" + bindWA_->fabricateUniqueName(); |
| } |
| |
| if (!(param->getIndicatorName().isNull())) { |
| node = new (bindWA_->wHeap()) HostVar(name, param->getIndicatorName(), |
| rowsetType); |
| } |
| else { |
| node = new (bindWA_->wHeap()) HostVar(name, rowsetType); |
| } |
| } |
| else { |
| return; |
| } |
| } |
| else { |
| // Make sure it is a Rowset host var |
| node = (HostVar *) (ItemExpr *) parent->child(childNumber); |
| if (node->getType()->getTypeQualifier() != NA_ROWSET_TYPE) { |
| return; |
| } |
| } |
| |
| ItemExpr *ptrList = listOfHostArrays_; |
| |
| Int32 found = FALSE; |
| Int32 i = 0; |
| numHostArrays_++; |
| |
| // Traverse the list of name we already have stored to see if our |
| // new HostVar is already there |
| while (!found && ptrList) { |
| HostVar *tmp = (HostVar *) (ItemExpr *) (ptrList->child(0)); |
| found = (tmp->getName() == node->getName()); |
| if (!found) { |
| ptrList = ptrList->child(1); |
| i++; |
| } |
| } |
| |
| // The rowset host variable is not in listOfHostArrays_. We append it. |
| if (!found) { |
| |
| if (!lastHostArray_) { |
| listOfHostArrays_ = new (bindWA_->wHeap()) ItemList(node, NULL); |
| lastHostArray_ = listOfHostArrays_; |
| } |
| else { |
| lastHostArray_->child(1) = new (bindWA_->wHeap()) ItemList(node, NULL); |
| lastHostArray_ = lastHostArray_->child(1); |
| } |
| } |
| |
| // We create the new scalar variable and replace the rowset host var with it. |
| // The name of the scalar variable is an "x" followed by its position number |
| // listHostArrays_ |
| // We create the new scalar variable and replace the rowset host var with it. |
| // The name of the scalar variable is an "x" followed by its position number |
| // listHostArrays_ |
| char tmp1[100], tmp2[100]; |
| #ifdef NA_ITOA_NOT_SUPPORTED |
| sprintf(tmp1, "%d",i); |
| #else |
| itoa(i, tmp1, 10); |
| #endif // NA_ITOA_NOT_SUPPORTED |
| strcpy(tmp2,"x"); |
| |
| ItemExpr *ptr = new (bindWA_->wHeap()) ColReference(new (bindWA_->wHeap()) |
| ColRefName(strcat(tmp2,tmp1), bindWA_->wHeap())); |
| parent->setChild(childNumber,ptr); |
| } |
| |
| |
| void HostArraysWA::processKeyVar(ItemExpr *parent, Int32 childNumber) |
| { |
| } |
| |
| // Traverses listOfHostArrays_ and creates a list of mapping variable names |
| // that will be used in the Rename node. The list of names is pointed by newNames_ |
| void HostArraysWA::createNewNames() |
| { |
| ItemExpr *tmp = listOfHostArrays_; |
| |
| char tmp1[100], tmp2[100]; |
| |
| CollHeap *heap = bindWA_->wHeap(); |
| |
| for (Int32 i = 0; tmp; i++, tmp = tmp->child(1)) { |
| #ifdef NA_ITOA_NOT_SUPPORTED |
| sprintf(tmp1, "%d",i); |
| #else |
| itoa(i, tmp1, 10); |
| #endif // NA_ITOA_NOT_SUPPORTED |
| strcpy(tmp2,"x"); |
| |
| ItemExpr *tempExpr = new (heap) |
| ItemList(new (heap) RenameCol(NULL, new (heap) |
| ColRefName(strcat(tmp2,tmp1),heap)), NULL); |
| |
| if (!lastName_) { |
| newNames_ = tempExpr; |
| lastName_ = newNames_; |
| } |
| else { |
| lastName_->child(1) = tempExpr; |
| lastName_ = lastName_->child(1); |
| } |
| |
| } |
| |
| // There is a ROWSET FOR KEY BY <var> statement. We add <var> to the list of |
| // variables of the RenameTable node. |
| if (indexExpr_) { |
| |
| ColReference *ref = (ColReference *) indexExpr_; |
| ColRefName name1 = ref->getColRefNameObj(); |
| ColRefName *name2 = new (heap) ColRefName(name1,heap); |
| ItemExpr *tempExpr = new (heap) |
| ItemList(new (heap) RenameCol(NULL, name2), NULL); |
| |
| newTable_ = "Rowset___"; |
| CMPASSERT(lastName_); |
| lastName_->child(1) = tempExpr; |
| lastName_ = lastName_->child(1); |
| } |
| } |
| |
| // Collects all rowset host variables found in the tree rooted |
| // by root. |
| void HostArraysWA::collectHostVarsInRelExprTree(RelExpr *root, RelExpr::AtomicityType atomicity) |
| { |
| if (!root) { |
| return; |
| } |
| |
| NABoolean doneWithChildren = FALSE; |
| |
| ItemExpr *selectList = root->selPredTree(); |
| ItemExpr *parent = new (CmpCommon::statementHeap()) SystemLiteral(); |
| |
| |
| if (selectList) { |
| parent->setChild(0,selectList); |
| Lng32 savedNumHostArrays = numHostArrays_ ; |
| collectHostVarsInPred(parent, 0); |
| if ( numHostArrays_ > savedNumHostArrays) { |
| setHasHostArraysInWhereClause(TRUE); |
| // have where clause and we are not in delete or upadate for direct rowsets. |
| // therefore we are in a select. |
| if (hasInputRowsetsInSelectPredicate() == HostArraysWA::UNKNOWN_) |
| setHasInputRowsetsInSelectPredicate(HostArraysWA::YES_); |
| } |
| } |
| |
| // Case when we have a subquery |
| if (root->getOperatorType() == REL_ROOT) { |
| if (((RelRoot *) root)->getCompExprTree()) { |
| selectList = ((RelRoot *) root)->getCompExprTree(); |
| |
| // We create a temporary constant to serve as a dummy tree root |
| parent->setChild(0,selectList); |
| |
| collectHostVarsInPred(parent, 0); |
| ((RelRoot *) root)->removeCompExprTree(); |
| ((RelRoot *) root)->addCompExprTree(parent->child(0)); |
| |
| } |
| } |
| |
| if (root->getOperatorType() == REL_UNARY_DELETE) { |
| setHasInputRowsetsInSelectPredicate(HostArraysWA::NO_); |
| } |
| |
| |
| // When we have an UPDATE node, we may have rowset arrays in |
| // the SET clause. |
| if (root->getOperatorType() == REL_UNARY_UPDATE) { |
| |
| Update *node = (Update *) root; |
| |
| // For regular update, the SET clause comes first in the query before |
| // the WHERE predicate. |
| // For merge/upsert stmt, the ON clause is specified before the SET |
| // or INSERT clauses. |
| // For merge/upsert stmt, process child first. This will |
| // get the params that were specified in the ON clause of merge stmt |
| // before processing SET or INSERT clauses. |
| // This ON clause is part of the scan node which is update's child. |
| if (node->isMerge()) { |
| // Process the children of this node |
| for (Int32 i = 0; i < root->getArity(); i++) { |
| collectHostVarsInRelExprTree(root->child(i), atomicity); |
| if (bindWA_->errStatus()) |
| return; |
| } |
| |
| doneWithChildren = TRUE; |
| } |
| |
| ItemExpr *setClause = node->recExprTree(); |
| setHasInputRowsetsInSelectPredicate(HostArraysWA::NO_); |
| |
| if (setClause) { |
| |
| // We create a temporary constant to serve as a dummy tree root |
| parent->setChild(0,setClause); |
| |
| Lng32 savedNumHostArrays = numHostArrays_ ; |
| // Traverse the SET clause. Find and replace any rowset host |
| // variables found there |
| collectHostVarsInPred(parent, 0); |
| if ( numHostArrays_ > savedNumHostArrays ) { |
| setHasHostArraysInSetClause(TRUE); |
| } |
| } |
| |
| if (node->isMerge()) { |
| // process INSERT VALUES clause |
| ItemExpr * insertClause = |
| (node->isMergeUpdate() ? ((MergeUpdate*)node)->insertValues() |
| : ((MergeDelete*)node)->insertValues()); |
| |
| if (insertClause) { |
| |
| // We create a temporary constant to serve as a dummy tree root |
| parent->setChild(0,insertClause); |
| |
| Lng32 savedNumHostArrays = numHostArrays_ ; |
| // Traverse the INSERT clause. Find and replace any rowset host |
| // variables found there |
| collectHostVarsInPred(parent, 0); |
| } |
| } |
| |
| } |
| |
| // indicate the presence of derived rowsets in the query |
| if ((root->getOperatorType() == REL_ROWSET) || |
| (root->getOperatorType() == REL_ROWSET_INTO)) |
| { |
| setHasDerivedRowsets(TRUE); |
| } |
| |
| // If we have a UNION node then we treat its children as subqueries, in |
| // the sense that any arrays found in them act independently of the rest |
| // of the relational tree |
| if ((root->getOperatorType() == REL_UNION) && |
| !((Union *) root)->getUnionForIF()) { |
| |
| HostArraysWA *tempWA = bindWA_->getHostArraysArea(); |
| |
| // We create a new environment to process each child |
| bindWA_->setHostArraysArea(new (bindWA_->wHeap()) HostArraysWA(bindWA_)); |
| root->child(0)->xformRowsetsInTree(*bindWA_); |
| |
| bindWA_->setHostArraysArea(new (bindWA_->wHeap()) HostArraysWA(bindWA_)); |
| root->child(1)->xformRowsetsInTree(*bindWA_); |
| |
| // Restore previous environment |
| bindWA_->setHostArraysArea(tempWA); |
| return; |
| } |
| |
| if (root->getOperatorType() == REL_UNARY_INSERT) |
| { |
| if ((CmpCommon::getDefault(ODBC_PROCESS) == DF_ON) || |
| (CmpCommon::getDefault(JDBC_PROCESS) == DF_ON)) |
| { |
| // disabling this check |
| /*if (root->getTolerateNonFatalError() != RelExpr::UNSPECIFIED_) |
| { |
| // ATOMIC/NOT ATOMIC clause not supported from ODBC and JDBC |
| *CmpCommon::diags() << DgSqlCode(-30030); |
| bindWA_->setErrStatus(); |
| return; |
| }*/ |
| |
| //transfer atomicity setting from statement attribute to Insert RelExpr |
| //if the Insert node doesn't already have atomicity set. |
| if (root->getTolerateNonFatalError() == RelExpr::UNSPECIFIED_) |
| root->setTolerateNonFatalError(atomicity); |
| } |
| } |
| |
| if (root->getOperatorType() == REL_TUPLE) { |
| // We replace all arrays found with the scalar variable names in the Rename node |
| collectArrays(((Tuple *) root)->tupleExprTree()); |
| } |
| |
| if (NOT doneWithChildren) { |
| // Process the children of this node |
| for (Int32 i = 0; i < root->getArity(); i++) { |
| collectHostVarsInRelExprTree(root->child(i), atomicity); |
| if (bindWA_->errStatus()) |
| return; |
| } |
| } |
| } |
| |
| // Used by Rowsets. Traverses the relational expression pointed to by |
| // queryExpr. It finds all rowsets that ocurr in selection or select |
| // lists and replaces them with scalar variables. It also introduces |
| // a join operator so that one of its operands will be an unPack node |
| // that will extract all elements in the rowsets, and the other operand |
| // is the subtree pointed by the child of queryexpr. The net result is |
| // that the original query gets executed as many times as there are |
| // elements in the array. |
| RelExpr *HostArraysWA::modifyTree(RelExpr *queryExpr, RelExpr::AtomicityType atomicity) |
| { |
| RelExpr *tempTree = queryExpr; |
| |
| if (queryExpr->child(0)->getOperatorType() == REL_ROWSETFOR) { |
| RowsetFor *tempVar = (RowsetFor *) (RelExpr *) queryExpr->child(0); |
| inputSizeExpr_ = tempVar->getInputSize(); |
| outputSizeExpr_ = tempVar->getOutputSize(); |
| indexExpr_ = tempVar->getIndexExpr(); |
| rwrsMaxSize_ = tempVar->getMaxSizeExpr(); |
| rwrsMaxInputRowlen_ = tempVar->getMaxInputRowlen(); |
| rwrsBuffer_ = tempVar->getRwrsBuffer(); |
| partnNum_ = tempVar->partnNum(); |
| setRowwiseRowset(tempVar->rowwiseRowset()); |
| tempVar->getBufferAttributes(packedFormat_, |
| compressed_, dcompressInMaster_, |
| compressInMaster_, partnNumInBuffer_); |
| queryExpr->child(0) = queryExpr->child(0)->child(0); |
| } |
| |
| // Put into listOfHostArrays_ all rowset host variables found |
| collectHostVarsInRelExprTree(queryExpr, atomicity); |
| if (bindWA_->errStatus()) |
| return queryExpr; |
| |
| // For insert tree transformation is done in modifyTupleNode(), not here. |
| if ((listOfHostArrays_) && (!hasHostArraysInTuple())) { |
| |
| if ((queryExpr->child(0)->getOperatorType() == REL_UNARY_INSERT) && |
| (queryExpr->child(0)->child(0)->getOperatorType() == REL_ROOT)) { |
| tempTree = queryExpr->child(0)->child(0); |
| } |
| |
| if ((queryExpr->child(0)->getOperatorType() == REL_ROWSET_INTO) && |
| (queryExpr->child(0)->child(0)->getOperatorType() == REL_ROOT)) { |
| tempTree = queryExpr->child(0)->child(0); |
| } |
| |
| // Create new Rowset node with information collected so far |
| RelExpr *newRowSet = new (bindWA_->wHeap()) Rowset |
| (listOfHostArrays_, indexExpr_, inputSizeExpr_); |
| |
| // Create the new names for the arrays so they can be referenced as scalars |
| createNewNames(); |
| |
| // The rename node will map the arrays to their new names |
| RelExpr *newRename = new (bindWA_->wHeap()) RenameTable |
| (newRowSet, "Rowset___", newNames_); |
| |
| // Create a root node below the Join node. It will have the same access type |
| // and lock mode as the root of the tree, so that the scans, deletes and inserts |
| // used in rowsets inherit that |
| RelExpr *newRoot1 = new (bindWA_->wHeap()) RelRoot |
| (tempTree->child(0), |
| ((RelRoot *) queryExpr)->accessOptions().accessType(), |
| ((RelRoot *) queryExpr)->accessOptions().lockMode(), |
| REL_ROOT, NULL, NULL, NULL); |
| |
| // The new subroot will have the aggregates of the original one, as well as the |
| // specification for ordering. In this way, a query that contains an array |
| // in the WHERE clause and some aggregates in the select list, will compute |
| // those aggregates the number of times that corresponds to the number of |
| // elements in the array, i.e. |
| // select sum(a) into :array from t where t1 = :array will return many sums, |
| // one for each array element |
| |
| ((RelRoot *) newRoot1)->removeCompExprTree(); |
| ((RelRoot *) newRoot1)->addCompExprTree(((RelRoot *) tempTree)->removeCompExprTree()); |
| |
| // lac: |
| // Fix for defect 10-010522-2978 |
| // Move the order by tree to the new RelRoot (i.e. the new Subroot). |
| // Save a pointer to the upper level RelRoot in the new RelRoot |
| // Leave order by tree processing until binding is done |
| // in RelRoot::bindNode(); in that code the new RelRoot handles |
| // moving the properly bound reqdOrder_ to the upper level RelRoot. |
| |
| ((RelRoot *) newRoot1)->addOrderByTree( |
| ((RelRoot *) tempTree)->removeOrderByTree()); |
| ((RelRoot *) newRoot1)->setParentForRowsetReqdOrder((RelRoot *)tempTree); |
| |
| // End Fix for defect 10-010522-2978 |
| |
| // This join node will send tuples from the rowset node to the original |
| // query (which now contains the arrays names changed to scalars) |
| RelExpr *newJoin = new (bindWA_->wHeap()) Join |
| (newRename, newRoot1, REL_TSJ, NULL); |
| |
| if (!getHasDerivedRowsets()) { |
| // this flag is being set here so that it can be read in OptPhysRelExpr.cpp |
| // and in BindRelExpr.cpp, to indicate that this is a flow node for rowset unpack. |
| // This setting does not affect the rownumber functionality and is not used in the generator. |
| newJoin->setRowsetIterator(TRUE); |
| } |
| |
| tempTree->child(0) = newJoin; |
| ((RelRoot *) tempTree)->removeCompExprTree(); |
| |
| } |
| |
| |
| if ((atomicity != RelExpr::UNSPECIFIED_) && (!hasHostArraysInTuple())) |
| { |
| // ATOMIC/NOT ATOMIC statement attribute set for statement that is not an insert |
| *CmpCommon::diags() << DgSqlCode(-30025); |
| bindWA_->setErrStatus(); |
| } |
| |
| if ((getRowwiseRowset()) && |
| (atomicity == RelExpr::NOT_ATOMIC_)) |
| { |
| // NAR not yet supported for RWRS inserts |
| *CmpCommon::diags() << DgSqlCode(-30025); |
| bindWA_->setErrStatus(); |
| } |
| |
| return queryExpr; |
| } |
| |
| |
| // *********************************************************************** |
| // Add trigger Id's to the triggersList_, only if that trigger was not added |
| // alreday. Used for the enable/disable mechanism. |
| // *********************************************************************** |
| CollIndex |
| BindWA::addTrigger(const ComTimestamp& triggerId) |
| { |
| // first use? |
| if (triggersList_ == NULL) |
| triggersList_ = new (wHeap()) LIST(ComTimestamp)(wHeap(),1); |
| |
| CollIndex triggerIndex = triggersList_->index(triggerId); |
| |
| // exists already? |
| if (triggerIndex == NULL_COLL_INDEX) |
| { |
| triggersList_->insert(triggerId); |
| triggerIndex = triggersList_->entries() - 1; |
| // index of 1st trigger is 0 |
| } |
| |
| return triggerIndex; |
| } |
| |
| // apply xformRowsetsInTree to our descendants & return transformed tree |
| RelExpr *RelExpr::xformRowsetsInTree(BindWA& wa, |
| const ULng32 arrayMaxsize, |
| const AtomicityType atomicity) |
| { |
| for (Int32 i = 0; i < getArity(); i++) { |
| if (child(i)) { |
| child(i) = child(i)->xformRowsetsInTree(wa, arrayMaxsize); |
| } |
| } |
| |
| return this; |
| } |
| |
| // apply xformRowsetsInTree to RelRoot & return transformed tree |
| RelExpr *RelRoot::xformRowsetsInTree(BindWA& wa, |
| const ULng32 arrayMaxsize, |
| const AtomicityType atomicity) |
| { |
| if (hostArraysArea_) return this; // we've been here, done that |
| |
| if (wa.getHostArraysArea()) { |
| // start out with array area pointed to by bindWA. |
| // For subqueries and children of union node we allocate a new HostArrayWA, |
| // set bindWA to point to it, and call xformRowsetsInTree. For subqueries |
| // the newly created array WA has a copy of the outer query's inputSizeExpr_. |
| hostArraysArea_ = wa.getHostArraysArea(); |
| } |
| else { |
| // start out with empty host arrays |
| hostArraysArea_ = new (wa.wHeap()) HostArraysWA(&wa); |
| } |
| |
| hostArraysArea_->setInputArrayMaxSize(arrayMaxsize) ; // used to determine input array max. size |
| // for ODBC queries. |
| |
| if (child(0)) { |
| switch (child(0)->getOperatorType()) { |
| case REL_COMPOUND_STMT: |
| // Drill down until it's rowset-safe. |
| child(0) = child(0)->xformRowsetsInTree(wa); |
| break; |
| case REL_UNION: |
| if (((Union *)(child(0).getPtr()))->getUnionForIF()) { |
| child(0) = child(0)->xformRowsetsInTree(wa); |
| } |
| else { |
| return hostArraysArea_->modifyTree(this, atomicity); |
| } |
| break; |
| |
| default: |
| { |
| // REL_ROOT above non-compound-stmt |
| RelExpr * newExpr = |
| hostArraysArea_->modifyTree(this, atomicity); // open sesame! |
| |
| return newExpr; |
| } |
| } |
| } |
| |
| return this; |
| } |
| |
| // retrive the source/target schema names from OVERRIDE_SCHEMA |
| void BindWA::initializeOverrideSchema() |
| { |
| NAString osSettings = schemaDB_->getDefaults().getValue(OVERRIDE_SCHEMA); |
| Int32 len = osSettings.length(); |
| |
| osFromSchema_ = ""; |
| osToSchema_ = ""; |
| |
| if (len == 0) // empty |
| return; |
| |
| extractOverrideSchemas(osSettings.data(), osFromSchema_, osToSchema_); |
| |
| // remove "" and leading/trailing spaces |
| if ( osFromSchema_(0) == '\"' ) |
| { |
| osFromSchema_ = osFromSchema_(1,osFromSchema_.length()-2); |
| osFromSchema_ = osFromSchema_.strip(NAString::both); |
| } |
| if ( osToSchema_(0) == '\"' ) |
| { |
| osToSchema_ = osToSchema_(1,osToSchema_.length()-2); |
| osToSchema_ = osToSchema_.strip(NAString::both); |
| } |
| |
| if ( (!osFromSchema_.isNull()) && (!osToSchema_.isNull()) |
| && (osFromSchema_ != osToSchema_) ) |
| overrideSchemaEnabled_ = TRUE; |
| else |
| overrideSchemaEnabled_ = FALSE; |
| |
| } |
| |
| // to extract override_schema settings from value (fromSchema:toSchema) |
| // and return in fromSchema and toSchema |
| void extractOverrideSchemas(const char *value, NAString& fromSchema, NAString& toSchema) |
| { |
| fromSchema = ""; |
| toSchema = ""; |
| Int32 len = strlen(value); |
| |
| if ( (len > ComMAX_1_PART_EXTERNAL_UTF8_NAME_LEN_IN_BYTES*2+2) |
| || (len < 3) ) // at least x:y |
| return; |
| |
| char in[ComMAX_1_PART_EXTERNAL_UTF8_NAME_LEN_IN_BYTES*2+2]; |
| char fromSch[ComMAX_1_PART_EXTERNAL_UTF8_NAME_LEN_IN_BYTES*2+2]; |
| char toSch[ComMAX_1_PART_EXTERNAL_UTF8_NAME_LEN_IN_BYTES*2+2]; |
| strcpy(in, value); |
| char *p = in; |
| |
| //find from schema |
| len = extractDelimitedName(fromSch, p, ':'); |
| |
| if ( (len > 0) && (len <= ComMAX_1_PART_EXTERNAL_UTF8_NAME_LEN_IN_BYTES) |
| && (strlen(value) > (size_t)len+1) ) // value long enough for at least one more chr for toSch |
| { |
| // find to schema |
| len = extractDelimitedName(toSch, p + len + 1, '\0'); |
| |
| if ( (len > 0) && (len <= ComMAX_1_PART_EXTERNAL_UTF8_NAME_LEN_IN_BYTES) ) |
| { |
| fromSchema = NAString(fromSch); |
| toSchema = NAString(toSch); |
| // remove leading and trailing spaces |
| fromSchema = fromSchema.strip(NAString::both); |
| toSchema = toSchema.strip(NAString::both); |
| // UPPER if not delimited |
| if (fromSchema(0)!='\"') |
| fromSchema.toUpper(); |
| if (toSchema(0)!='\"') |
| toSchema.toUpper(); |
| } |
| } |
| |
| return; |
| } |
| |
| // OVERRIDE_SCHEMA: replace the source schema with the target schema |
| void BindWA::doOverrideSchema(CorrName& corrName) |
| { |
| if ( |
| (toOverrideSchema_ ) |
| // the database object must have been named |
| && ( ! corrName.getQualifiedNameObj().getObjectName().isNull() ) |
| // do not override if no schema was specified (table alias) |
| && ( ! corrName.getQualifiedNameObj().getSchemaName().isNull() ) |
| // not to override for other tables like TRIGTEMP_TABLE or IUD_LOG_TABLE |
| // NORMAL_TABLE will also cover synonym, view and MV |
| && ( corrName.getSpecialType() == ExtendedQualName::NORMAL_TABLE ) |
| // not in DDL |
| && ( ! inDDL() ) |
| // not in view expansion |
| && ( ! getCurrentScope()->getInViewExpansion() ) |
| // not in CTAS |
| && ( ! inCTAS() ) |
| ) |
| { |
| // specified schema is the same as the from_schema |
| if ( corrName.getQualifiedNameObj().getSchemaName() == osFromSchema_ ) |
| { |
| corrName.getQualifiedNameObj().setSchemaName(osToSchema_); |
| return; |
| } |
| // wildcard override for any schema |
| if ( osFromSchema_ == "*" ) |
| { |
| corrName.getQualifiedNameObj().setSchemaName(osToSchema_); |
| return; |
| } |
| } |
| } |
| |
| // if DEFAULT_SCHEMA_ACCESS_ONLY, can only access default and public schemas |
| NABoolean BindWA::violateAccessDefaultSchemaOnly(const QualifiedName& objName) |
| { |
| NAString cat=objName.getCatalogName(); |
| NAString sch=objName.getSchemaName(); |
| |
| // when this is called, catalog and schema should have been assigned |
| if (!cat.isNull() && !sch.isNull()) |
| { |
| |
| // let the following system catalogs and schemas pass |
| // they are used in tools like HP-DM, etc. |
| if ( (cat == "MANAGEABILITY") || |
| (cat == "HP_SYSTEM_CATALOG") || |
| (sch == "HP_METRICS") || |
| (sch == "HP_DEFINITION_SCHEMA") ) |
| return FALSE; |
| |
| // let volatile objects pass |
| if (!strncmp(sch.data(), "VOLATILE_SCHEMA_MX", 18)) |
| return FALSE; |
| |
| SchemaName objSchName(sch,cat); |
| if (objSchName.matchDefaultPublicSchema()) |
| return FALSE; |
| } |
| |
| return raiseAccessDefaultSchemaOnlyError(); |
| } |
| |
| NABoolean BindWA::raiseAccessDefaultSchemaOnlyError() |
| { |
| // no error for the following situations |
| if ( (CmpCommon::getDefault(DEFAULT_SCHEMA_ACCESS_ONLY)!=DF_ON) |
| || noNeedToLimitSchemaAccess() // if the temporary flag is set |
| || CmpCommon::context()->isSecondaryMxcmp() // second mxcmp (update stats, etc.) |
| || getCurrentScope()->getInViewExpansion() // in view expansion |
| || Get_SqlParser_Flags(INTERNAL_QUERY_FROM_EXEUTIL) |
| // internal query from executor |
| || (isInTrigger() && !inDDL()) // in trigger actions |
| ) |
| return FALSE; |
| |
| *CmpCommon::diags() << DgSqlCode(-30044); |
| setErrStatus(); |
| return TRUE; |
| } |
| |
| NABoolean BindWA::queryCanUseSeaMonster() |
| { |
| // Values defined for the queryCanUseSeaMonster_ variable |
| // -1 : the variable is not yet initialized |
| // 0 : the query cannot use SM |
| // 1 : the query can use SM |
| |
| // If the variable is not initialized, we compute the value based on |
| // two things: an environment variable and the SEAMONSTER default. |
| if (queryCanUseSeaMonster_ == -1) |
| { |
| // Initialize to OFF |
| queryCanUseSeaMonster_ = 0; |
| |
| // Allow SM if CQD is ON, or CQD is SYSTEM and env var is 1 |
| if (CmpCommon::getDefault(SEAMONSTER) == DF_ON) |
| { |
| queryCanUseSeaMonster_ = 1; |
| } |
| else if (CmpCommon::getDefault(SEAMONSTER) == DF_SYSTEM) |
| { |
| const char *e = getenv("SQ_SEAMONSTER"); |
| if (e && e[0] == '1') |
| queryCanUseSeaMonster_ = 1; |
| } |
| } |
| |
| return (queryCanUseSeaMonster_ == 0 ? FALSE : TRUE); |
| } |
| |
| //---------------------------------------------------------------------- |
| // qualNameHashFunc() |
| // calculates a hash value given a QualifiedName.Hash value is mod by |
| // the hashTable size in HashDictionary. |
| //---------------------------------------------------------------------- |
| ULng32 BindWA::qualNameHashFunc(const QualifiedName& qualName) |
| { |
| ULng32 index = 0; |
| const NAString& name = qualName.getObjectName(); |
| |
| for(UInt32 i=0;i<name.length();i++) |
| { |
| index += (ULng32) (name[i]); |
| } |
| return index; |
| } |
| |
| // *********************************************************************** |
| // Additional BindWA methods are found in BindRelExpr.C ... |
| // *********************************************************************** |