| /********************************************************************** |
| // @@@ 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: BindRelExpr.C |
| * Description: Relational expressions (both physical and logical operators) |
| * Methods related to the SQL binder |
| * |
| * Created: 5/17/94 |
| * Language: C++ |
| * |
| * |
| * |
| * It is the secret sympathy, |
| * The silver link, the silken tie, |
| * Which heart to heart, and mind to mind, |
| * In body and in soul can bind. |
| * -- Sir Walter Scott, |
| * "The Lay of the Last Minstrel" |
| * |
| ****************************************************************************** |
| */ |
| |
| |
| #define SQLPARSERGLOBALS_FLAGS // must precede all #include's |
| #define SQLPARSERGLOBALS_NADEFAULTS |
| |
| #include "Platform.h" |
| #include "NAWinNT.h" |
| |
| |
| #include "Sqlcomp.h" |
| #include "AllItemExpr.h" |
| #include "AllRelExpr.h" |
| #include "BindWA.h" |
| #include "ComOperators.h" |
| #include "ComTransInfo.h" |
| #include "ComLocationNames.h" |
| #include "ControlDB.h" |
| #include "Debug.h" |
| #include "ex_error.h" |
| #include "GroupAttr.h" |
| #include "ParNameLocList.h" |
| #include "parser.h" |
| #include "Rel3GL.h" |
| #include "RelDCL.h" |
| #include "RelPackedRows.h" |
| #include "RelSequence.h" |
| #include "ShowSchema.h" // GetControlDefaults class |
| #include "StmtDDLAddConstraintCheck.h" |
| #include "StmtDDLCreateView.h" |
| #include "ElemDDLColRefArray.h" |
| #include "ElemDDLSaltOptions.h" |
| #include "TrafDDLdesc.h" |
| #include "UdrErrors.h" |
| #include "SequenceGeneratorAttributes.h" |
| |
| #include "wstr.h" |
| #include "Inlining.h" |
| #include "Triggers.h" |
| #include "TriggerDB.h" |
| #include "MVInfo.h" |
| #include "Refresh.h" |
| #include "ChangesTable.h" |
| #include "MvRefreshBuilder.h" |
| #include "OptHints.h" |
| #include "CmpStatement.h" |
| #include "OptimizerSimulator.h" |
| #include "charinfo.h" |
| #include "UdfDllInteraction.h" |
| #include "SqlParserGlobals.h" // must be last #include |
| #include "ItmFlowControlFunction.h" |
| #include "ComSchemaName.h" // for ComSchemaName |
| #include "ItemSample.h" |
| #include "NAExecTrans.h" |
| #include "HDFSHook.h" |
| #include "CmpSeabaseDDL.h" |
| #include "ComUser.h" |
| #include "ComSqlId.h" |
| #include "PrivMgrCommands.h" |
| #include "PrivMgrComponentPrivileges.h" |
| #include "PrivMgrDefs.h" |
| #include "PrivMgrMD.h" |
| |
| #define SLASH_C '/' |
| |
| NAWchar *SQLTEXTW(); |
| |
| // ----------------------------------------------------------------------- |
| // external declarations |
| // ----------------------------------------------------------------------- |
| |
| // |
| |
| |
| |
| // ----------------------------------------------------------------------- |
| // static functions |
| // ----------------------------------------------------------------------- |
| #ifdef NDEBUG |
| THREAD_P NABoolean GU_DEBUG = FALSE; |
| #else |
| THREAD_P NABoolean GU_DEBUG; |
| #endif |
| |
| static void GU_DEBUG_Display(BindWA *bindWA, GenericUpdate *gu, |
| const char *text, |
| RelExpr *reDown = NULL, |
| NABoolean preEndl = FALSE, |
| NABoolean postEndl = FALSE) |
| { |
| #ifndef NDEBUG |
| if (!GU_DEBUG) |
| return; |
| |
| // LCOV_EXCL_START - dpm |
| if (preEndl) cerr << endl; |
| cerr << "---" << endl; |
| |
| if (gu->getTableDesc()) { |
| NAString tmp; |
| ValueIdList vtmp(gu->getTableDesc()->getColumnList()); |
| vtmp.unparse(tmp); |
| cerr << gu->getUpdTableNameText() << " this>td(" << text << ") " |
| << gu->getTableDesc()->getCorrNameObj().getExposedNameAsAnsiString() |
| << " " << tmp << endl; |
| } |
| |
| RETDesc *rd = gu->getRETDesc(); |
| if (rd) { |
| cerr << gu->getUpdTableNameText() << " this>grd(" << text << ") " << flush; |
| rd->display(); |
| } |
| if (reDown) RETDesc::displayDown(reDown); |
| |
| if (bindWA->getCurrentScope()->getRETDesc() && |
| bindWA->getCurrentScope()->getRETDesc() != rd) { |
| cerr << gu->getUpdTableNameText() << " bwa>cs>grd(" << text << ") " <<flush; |
| bindWA->getCurrentScope()->getRETDesc()->display(); |
| } |
| // LCOV_EXCL_STOP |
| |
| if (postEndl) cerr << endl; |
| #endif |
| } // GU_DEBUG_Display() |
| |
| #pragma nowarn(770) // warning elimination |
| static RETDesc *bindRowValues(BindWA *bindWA, |
| ItemExpr *exprTree, |
| ValueIdList &vidList, |
| RelExpr *parent, |
| NABoolean inTrueRoot) |
| { |
| // Before we convert the row value expressions into a ValueIdList, save the |
| // original value expression root nodes in an ItemExprList. |
| // |
| ItemExprList exprList(exprTree, bindWA->wHeap()); |
| // |
| // Bind the row value expressions and create a ValueIdList. |
| // |
| exprTree->convertToValueIdList(vidList, bindWA, ITM_ITEM_LIST, parent); |
| if (bindWA->errStatus()) return NULL; |
| |
| // Set up context flags. |
| // We are in a subquery if the previous scope's flag is set, note. |
| // |
| BindScope *currScope = bindWA->getCurrentScope(); |
| BindScope *prevScope = bindWA->getPreviousScope(currScope); |
| NABoolean inSelectList = currScope->context()->inSelectList(); |
| NABoolean inInsert = currScope->context()->inInsert(); |
| NABoolean inSubquery = FALSE; |
| if (prevScope) |
| inSubquery = prevScope->context()->inSubquery(); |
| |
| // See if UDF_SUBQ_IN_AGGS_AND_GBYS is enabled. It is enabled if the |
| // default is ON, or if the default is SYSTEM and ALLOW_UDF is ON. |
| NABoolean udfSubqInAggGrby_Enabled = FALSE; |
| DefaultToken udfSubqTok = CmpCommon::getDefault(UDF_SUBQ_IN_AGGS_AND_GBYS); |
| if ((udfSubqTok == DF_ON) || |
| (udfSubqTok == DF_SYSTEM)) |
| udfSubqInAggGrby_Enabled = TRUE; |
| |
| // See if ALLOW_MULTIDEGREE_SUBQ_IN_SELECTLIST is enabled. It is |
| // enabled if the default is ON, or if the default is SYSTEM and |
| // ALLOW_UDF is ON. |
| NABoolean allowMultiDegSubqInSelect_Enabled = FALSE; |
| DefaultToken allowMultiDegreeTok = |
| CmpCommon::getDefault(ALLOW_MULTIDEGREE_SUBQ_IN_SELECTLIST); |
| if ((allowMultiDegreeTok == DF_ON) || |
| (allowMultiDegreeTok == DF_SYSTEM)) |
| allowMultiDegSubqInSelect_Enabled = TRUE; |
| |
| // |
| // Create the result table. |
| // If a row value expression is not a column reference and does not have |
| // a rename AS clause, the column is an unnamed expression. |
| // |
| RETDesc *resultTable = new (bindWA->wHeap()) RETDesc(bindWA); |
| CollIndex j = 0; |
| for (CollIndex i = 0; i < exprList.entries(); i++, j++) |
| { |
| ItemExpr *itemExpr = (ItemExpr *) exprList[i]; |
| ValueId valId = itemExpr->getValueId(); |
| ValueId boundValId = vidList[j]; |
| CMPASSERT(boundValId != NULL_VALUE_ID); |
| |
| if (inSelectList && inTrueRoot && |
| (boundValId.getType().getTypeQualifier() == NA_UNKNOWN_TYPE)&& |
| (boundValId.getItemExpr()->getOperatorType() == ITM_CONSTANT)) |
| { |
| ConstValue * constItemExpr = (ConstValue*) boundValId.getItemExpr(); |
| if (constItemExpr->isNull()) |
| boundValId.coerceType(NA_NUMERIC_TYPE) ; |
| } |
| |
| switch (itemExpr->getOperatorType()) |
| { |
| case ITM_REFERENCE: { |
| ColReference *colRef = (ColReference *) itemExpr; |
| const ColRefName &colRefName = colRef->getColRefNameObj(); |
| CMPASSERT(valId != NULL_VALUE_ID || colRefName.isStar()); |
| |
| if (colRefName.isStar()) { |
| const ColumnDescList *star = colRef->getStarExpansion(); |
| CMPASSERT(star != NULL); |
| const ColumnDescList &starExpansion = *star; |
| CMPASSERT(starExpansion.entries() > 0); // ColRef::bind chked this alrdy |
| CMPASSERT(inSelectList); |
| |
| resultTable->addColumns(bindWA, starExpansion); |
| |
| j += starExpansion.entries() - 1; |
| } // isStar |
| else { |
| // Do another xcnm lookup so the column we add to our resultTable |
| // will have its CorrName object correct |
| // (e.g., in "SELECT TL.B,* FROM TA TL,TA TR ORDER BY B;" |
| // colref TL.B will resolve to TL.B, not CAT.SCH.TL.B) |
| // and its heading (Genesis 10-980126-5495). |
| BindScope *bindScope; |
| ColumnNameMap *xcnmEntry = bindWA->findColumn(colRefName, bindScope); |
| |
| if (NOT xcnmEntry) // ## I don't recall when this case occurs... |
| resultTable->addColumn(bindWA, |
| colRefName, |
| boundValId, |
| colRef->getTargetColumnClass()); |
| else |
| resultTable->addColumn(bindWA, |
| xcnmEntry->getColRefNameObj(), |
| boundValId, |
| colRef->getTargetColumnClass(), // MV -- |
| xcnmEntry->getColumnDesc()->getHeading()); |
| } |
| break; |
| } |
| case ITM_RENAME_COL: |
| { |
| RenameCol *renameCol = (RenameCol *) itemExpr; |
| const ColRefName &colRefName = *renameCol->getNewColRefName(); |
| CMPASSERT(NOT colRefName.isStar()); |
| |
| const char * heading = NULL; |
| |
| // if this rename was for a BLOB/CLOB column from JDBC, return |
| // the heading of the child base column. This is needed for JDBC |
| // as it uses the heading to figure out if the column is a LOB |
| // column. |
| if (CmpCommon::getDefault(JDBC_PROCESS) == DF_ON) |
| { |
| ItemExpr * childExpr = itemExpr->child(0)->castToItemExpr(); |
| if (childExpr->getOperatorType() == ITM_BASECOLUMN) |
| { |
| heading = ((BaseColumn *)childExpr)->getNAColumn()->getHeading(); |
| |
| if (heading) |
| { |
| if ((strcmp(heading, "JDBC_BLOB_COLUMN -") != 0) && |
| (strcmp(heading, "JDBC_CLOB_COLUMN -") != 0)) |
| heading = NULL; |
| } |
| } |
| } |
| |
| // No heading is passed here (whole point of SQL derived-column is rename) |
| // unless it is a jdbc blob/clob heading. |
| resultTable->addColumn(bindWA, |
| colRefName, |
| boundValId, |
| renameCol->getTargetColumnClass(), |
| heading); |
| break; |
| } |
| case ITM_ROW_SUBQUERY: |
| case ITM_USER_DEF_FUNCTION: { |
| |
| // Deal with multi Valued User Defined Functions or Subqueries with |
| // degree > 1. |
| // |
| // In order to have the correct degree during the bind phase, |
| // since we don't have all the information until after the transform |
| // phase, we need to put entries into the RETDesc early. |
| // |
| // Say you have a query like this: |
| // select mvf(a,b) from t1; |
| // and assume mvf outputs 2 values. |
| // |
| // at bind time, the select list will only have 1 entry in it, namely |
| // the ITM_USER_DEF_FUNCTION. |
| // Since we do degree checking at bind time, we need to know now that |
| // mvf() actually produces 2 values. |
| // |
| // So what we do here, is that we substitute the original |
| // ITM_USER_DEF_FUNCTION with ValueIdProxies. One for each output of |
| // the original function. The selectList of the RelRoot as well as the |
| // retDESC are updated with the additional elements. |
| // |
| // Similarly if we have a subquery like this: |
| // |
| // select (select max(a),max(b) from t2), a from t1; |
| // |
| // we will wrap the subquery in a ValeIdProxy representing the |
| // subquery from a transformation point of view, but representing |
| // max(a) from an output point of view. A second ValueIdProxy will be |
| // added for max(b), so the select list of the outer query would look |
| // like this: |
| // |
| // [ ValueIdProxy(Subq:max(a)), ValueIdProxy(Subq:max(b)), a ] |
| // |
| // instead of just |
| // |
| // [ Subq, a ] |
| // |
| // like we are used to. |
| // |
| // At transform time the valueIdProxies, will disappear and we will |
| // transform the UDF/Subquery carried inside the valueIdProxy |
| // marked to be transformed. Some might hang around until Normalization. |
| // Only the ValueIdProxy representing the first output will be marked |
| // to be transformed, so we only transform the UDF/Subquery once. |
| // |
| // Similarly, we update the outer query's retDESC. |
| |
| |
| NABoolean isSubquery = |
| (itemExpr->getOperatorType() == ITM_ROW_SUBQUERY) ? |
| TRUE : FALSE; |
| |
| NAColumnArray outCols; |
| ValueIdList outColVids; |
| CollIndex currIndex = j; |
| |
| if (isSubquery) |
| { |
| Subquery * subq = (Subquery *) itemExpr; |
| |
| const RETDesc *retDesc = subq->getSubquery()->getRETDesc(); |
| |
| if( retDesc ) |
| { |
| retDesc->getColumnList()->getValueIdList(outColVids); |
| } |
| } |
| else |
| { |
| UDFunction * udf = (UDFunction *) itemExpr; |
| CMPASSERT(udf->getRoutineDesc()); |
| const RoutineDesc *rDesc = udf->getRoutineDesc(); |
| |
| |
| // Get the outputs of this UDF, these are as defined in metadata |
| // including names etc. |
| outCols = rDesc->getEffectiveNARoutine()->getOutParams(); |
| outColVids = rDesc->getOutputColumnList(); |
| } |
| |
| if ( (outColVids.entries() == 1) || |
| ( isSubquery && |
| (!allowMultiDegSubqInSelect_Enabled) |
| )) |
| { |
| // Do exactly what we used to do if the degree is 1. |
| // or we have disallowed subqueries of degree > 1. |
| |
| if (isSubquery) |
| { |
| // ## Here we ought to manufacture a unique name per Ansi 7.9 SR 9c. |
| ColRefName colRefName; |
| resultTable->addColumn(bindWA, colRefName, boundValId); |
| } |
| else |
| { |
| NAColumn *col = outCols[0]; |
| const char * heading = col->getHeading(); |
| ColRefName colRefName( col->getColName()); |
| ColumnClass colClass( col->getColumnClass()); |
| |
| resultTable->addColumn(bindWA, |
| colRefName, |
| boundValId, |
| colClass, |
| heading); |
| } |
| break; |
| } |
| |
| // Wrap all the outputs with a ValueIdProxy |
| // so that we can deal with multiple outputs |
| // If we didn't have a RETDesc or a RoutineDesc, outColVids |
| // will be empty and we don't do anything. |
| |
| // Also we do not need to worry about recursing through the |
| // RETDesc entries as the call to convertToValueIdList() above |
| // did that already. |
| for (CollIndex idx = 0; idx < outColVids.entries(); idx++) |
| { |
| |
| NAColumn *col; |
| |
| NABoolean isRealOrRenameColumn = |
| (outColVids[idx].getItemExpr()->getOperatorType() == |
| ITM_BASECOLUMN) || |
| (outColVids[idx].getItemExpr()->getOperatorType() == |
| ITM_RENAME_COL) || |
| !isSubquery ? TRUE : FALSE; |
| |
| if (isSubquery) |
| { |
| col = ((NAColumn *) outColVids[idx].getItemExpr()); |
| } |
| else |
| { |
| col = ((NAColumn *) outCols[idx]); |
| } |
| |
| const char * heading = isRealOrRenameColumn ? |
| col->getHeading() : ""; |
| ColRefName colRefName( isRealOrRenameColumn ? |
| col->getColName() : ""); |
| ColumnClass colClass( isRealOrRenameColumn ? |
| col->getColumnClass() : USER_COLUMN); |
| |
| |
| // We are wrapping the MVF/Subquery and its additional outputs |
| // with a ValueIdProxy. This way we don't end up flattening or |
| // expanding the outputs of the MVF multiple times. |
| |
| // The valueId of the RoutineParam corresponding to the |
| // metadata column is used for the output valueId. |
| |
| // So if you had a query like this: |
| // |
| // select swap2(a,b) from t1; |
| // |
| // and swap2() returns 2 outputs (basically the inputs swapped) |
| // |
| // The new select list for the query would be: |
| // |
| // 1: ValueIdProxy with the derivedNode being swap2, and output |
| // valueId containing the first output parameter of swap2. |
| // Also the transformDerivedFrom flag would be set |
| // 2: ValueIdProxy with the derivedNode being swap2, and output |
| // valueId containing the second output parameter of swap2. |
| // |
| // These ValueIdProxy nodes will go away at transform time.. |
| |
| ValueIdProxy *proxyOutput = new (CmpCommon::statementHeap()) |
| ValueIdProxy( boundValId, |
| outColVids[idx], |
| idx); |
| |
| |
| // The type of the proxy is the same as the output valueId associated |
| // with it. |
| proxyOutput = (ValueIdProxy *) proxyOutput->bindNode(bindWA); |
| |
| if (bindWA->errStatus()) return NULL; |
| |
| // Make sure we transform the MVF |
| if (idx == 0) proxyOutput->setTransformChild(TRUE); |
| |
| |
| if (!isSubquery || isRealOrRenameColumn) |
| { |
| resultTable->addColumn(bindWA, |
| colRefName, |
| proxyOutput->getValueId(), |
| colClass, |
| heading); |
| |
| } |
| else |
| { |
| resultTable->addColumn(bindWA, colRefName, |
| proxyOutput->getValueId()); |
| } |
| |
| if (idx == 0) |
| { |
| vidList.removeAt(currIndex); // we need to delete the old valueId |
| } |
| else |
| j++; // The first entry simply replaces the original |
| |
| // Update the list with the new value. |
| // insertAt has the nice feature that it will push |
| // the residual elements to the right, so we do not need to |
| // manage the valueIds we haven't processed yet as long as we |
| // update the index (j++ above) correctly. |
| vidList.insertAt(currIndex++,proxyOutput->getValueId()); |
| } |
| |
| break; |
| } |
| |
| default: |
| { |
| // ## Here we ought to manufacture a unique name per Ansi 7.9 SR 9c. |
| ColRefName colRefName; |
| resultTable->addColumn(bindWA, colRefName, boundValId); |
| break; |
| } |
| } // switch |
| } // for |
| |
| // need this for static cursor declaration |
| cmpCurrentContext->saveRetrievedCols_ = resultTable->getDegree(); |
| |
| // Before we can return the result table, we need to check for the possible |
| // syntax error below, in which we can't use the definition of "inSubquery" |
| // that we calculate above. Our example case is, if we're directly below |
| // a GroupByAgg, then we need to look at the scope *before* the GroupByAgg |
| // to determine if we satisfy the error condition below. This is a problem |
| // with how our plan trees don't sync completely with SQL syntax. |
| // Here's the error case (Genesis 10-980518-0765): |
| // |
| // >> select (select distinct 1,2 from T1 t) from T1; |
| // |
| // First of all, yes, it's a really stupid query. Oh well! :-) |
| // |
| // It's pretty clear that the "1,2" is part of a "select list inside the |
| // subquery of a select list." However, the parser creates a GroupByAgg |
| // for the distinct keyword (sigh), which means that we have an |
| // additional scope between the scope of the SQLRecord (1,2) and the |
| // scope of the "TRUE" parent, the inner-select. This additional scope |
| // is for the GroupByAgg. So in the case of a GroupByAgg (and possibly |
| // another case will arise later ...?), we need to look at the |
| // GroupByAgg's parent to determine if we satisfy this error condition. |
| // |
| // To recap: To handle this one (stupid) case we've added a ton of |
| // comments and code here and in GroupByAgg::bindNode(), plus created |
| // the new functions/members BindWA::getSubqueryScope(), and |
| // BindContext::lookAboveToDecideSubquery_/(). Wonderful! |
| // |
| if (prevScope) { |
| |
| BindScope *subQScope = bindWA->getSubqueryScope(currScope); |
| // |
| // subQScope should be non-NULL when prevScope is non-NULL |
| // |
| CMPASSERT(subQScope); |
| |
| NABoolean inSubqueryInSelectList = subQScope->context()->inSubquery() && |
| subQScope->context()->inSelectList(); |
| |
| NABoolean inSubqueryInGroupByClause = subQScope->context()->inSubquery() && |
| subQScope->context()->inGroupByClause() && |
| (CmpCommon::getDefault(UDF_SUBQ_IN_AGGS_AND_GBYS) == DF_ON); |
| |
| //10-060602-6930 Begin |
| //Added a check to not enter this condition when we are in bindView scope |
| if (inSelectList && |
| (inSubqueryInSelectList || |
| inSubqueryInGroupByClause) && |
| !bindWA->inViewExpansion()) { |
| //10-060602-6930 End |
| // We now can check for the syntax error that we've done so much work |
| // above (and in GroupByAgg::bindNode(), BindWA.h & BindWA.cpp) |
| // to detect: |
| |
| if ((j > 1) && |
| (!allowMultiDegSubqInSelect_Enabled) ) { |
| // 4019 The select list of a subquery in a select list must be scalar |
| *CmpCommon::diags() << DgSqlCode(-4019); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| } // prevScope |
| |
| return resultTable; |
| } // bindRowValues() |
| #pragma warn(770) // warning elimination |
| |
| // Bind a constraint (MP Check Constraint). |
| // Returns NULL if error in constraint *OR* we can safely ignore the constraint |
| // (e.g., a NOT NULL NONDROPPABLE constraint); caller must check bindWA errsts. |
| // |
| static ItemExpr* bindCheckConstraint( |
| BindWA *bindWA, |
| CheckConstraint *constraint, |
| const NATable *naTable, |
| NABoolean catmanCollectUsages = FALSE, |
| ItemExpr *viewCheckPred = NULL) |
| { |
| ItemExpr *constraintPred = NULL; |
| if (viewCheckPred) { |
| // view WITH CHECK OPTION: the view's where-clause was already parsed |
| // in bindView |
| CMPASSERT(constraint->getConstraintText().isNull()); // sanity check |
| constraintPred = viewCheckPred; |
| } |
| else { |
| |
| Parser parser(bindWA->currentCmpContext()); |
| constraintPred = parser.getItemExprTree(constraint->getConstraintText().data(), |
| constraint->getConstraintText().length(), |
| CharInfo::UTF8 // ComGetNameInterfaceCharSet() |
| ); |
| |
| } |
| if (constraintPred) { |
| ParNameLocList *saveNameLocList = bindWA->getNameLocListPtr(); |
| if (!catmanCollectUsages || |
| !bindWA->getUsageParseNodePtr() || |
| bindWA->getUsageParseNodePtr()->getOperatorType() == DDL_CREATE_VIEW) |
| bindWA->setNameLocListPtr(NULL); |
| |
| CMPASSERT(!bindWA->getCurrentScope()->context()->inCheckConstraint()); |
| bindWA->getCurrentScope()->context()->inCheckConstraint() = constraint; |
| |
| constraintPred->bindNode(bindWA); |
| bindWA->setNameLocListPtr(saveNameLocList); |
| bindWA->getCurrentScope()->context()->inCheckConstraint() = NULL; |
| |
| if (bindWA->errStatus()) { |
| delete constraintPred; |
| constraintPred = NULL; |
| } |
| } |
| // A NOT NULL constraint on a single column which never allows nulls |
| // (has no null indicator bytes) |
| // -- i.e., the common case of a column declared NOT NULL NONDROPPABLE -- |
| // does not need to be separately enforced as a constraint, because |
| // Executor will raise a numeric-overflow error if someone tries to |
| // put a NULL into such a column. |
| // |
| // So we don't need to put this constraint into the list, but we do need |
| // to save its name, for run-time error diags. |
| // |
| // ##To be done: |
| // ## GenRelUpdate DP2Insert/Update: for each col in newRecExpr(), |
| // ## if getNotNullViolationCode(), then |
| // ## save the SqlCode and the getNotNullConstraintName()...asAnsiString() |
| // ## and some column identifier (pos or offset) in some per-query struct |
| // ## Executor: if error 8411, if truly a NULL violation, look up that column |
| // ## in the nnconstraint struct and populate diags with the info there. |
| // |
| if (constraintPred) { |
| ItemExprList nncols(bindWA->wHeap()); |
| constraintPred->getColumnsIfThisIsISNOTNULL(nncols); |
| for (CollIndex i = 0; i < nncols.entries(); i++) { |
| NAColumn *nacol = nncols[i]->getValueId().getNAColumn(); |
| if (!nacol->getType()->supportsSQLnullPhysical()) { |
| nacol->setNotNullNondroppable(constraint); |
| // |
| // DO *NOT* do: delete constraintPred; |
| // -- it deletes a whole tree of stuff referenced elsewhere! |
| // |
| constraintPred = NULL; |
| } else { |
| // Leaving the column's type's supportsSQLnullPhysical() as is (TRUE), |
| // set its supportsSQLnullLogical() to FALSE, |
| // for the Transform phase. |
| nacol->mutateType()->setNullable(TRUE/*supports physical nulls*/, |
| FALSE/*but not logical nulls */); |
| } |
| } |
| } |
| else { |
| *CmpCommon::diags() << DgSqlCode(-4025) |
| << DgConstraintName(ToAnsiIdentifier(constraint->getConstraintName().getObjectName())) |
| << DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| } |
| return constraintPred; |
| } // bindCheckConstraint() |
| |
| // LCOV_EXCL_START - cnu |
| static ItemExpr *intersectColumns(const RETDesc &leftTable, |
| const RETDesc &rightTable, |
| BindWA* bindWA) |
| { |
| ItemExpr *predicate = NULL; |
| for (CollIndex i = 0; i < leftTable.getDegree(); i++) { |
| ItemExpr *leftExpr = leftTable.getValueId(i).getItemExpr(); |
| ItemExpr *rightExpr = rightTable.getValueId(i).getItemExpr(); |
| BiRelat *compare = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, leftExpr, rightExpr, TRUE); |
| if (predicate) |
| predicate = new (bindWA->wHeap()) BiLogic(ITM_AND, predicate, compare); |
| else |
| predicate = compare; |
| } |
| // Binding this predicate must be done in caller's context/scope, not here... |
| return predicate; |
| } // intersectColumns() |
| // LCOV_EXCL_STOP |
| |
| static ItemExpr *joinCommonColumns(const RelExpr *const leftRelExpr, |
| const RelExpr *const rightRelExpr, |
| BindWA* bindWA) |
| { |
| const RETDesc &leftTable = *leftRelExpr->getRETDesc(); |
| const RETDesc &rightTable = *rightRelExpr->getRETDesc(); |
| // |
| // Find the common column names between two tables and create a predicate |
| // that joins the columns. For example, if tables T1 and T2 have common |
| // column names A and B, return the predicate T1.A = T2.A AND T1.B = T2.B. |
| // The checking for ambiguous common columns will be done when they are |
| // are coalesced for the output list. |
| // |
| ItemExpr *predicate = NULL; |
| for (CollIndex i = 0; i < leftTable.getDegree(); i++) { |
| ColRefName simpleColRefName(leftTable.getColRefNameObj(i).getColName()); // |
| if (NOT simpleColRefName.isEmpty()) { // |
| ColumnNameMap *commonCol = rightTable.findColumn(simpleColRefName); // |
| if (commonCol) { // |
| ItemExpr *leftExpr = leftTable.getValueId(i).getItemExpr(); |
| ItemExpr *rightExpr = commonCol->getValueId().getItemExpr(); // |
| |
| bindWA->markAsReferencedColumn(leftExpr); |
| |
| bindWA->markAsReferencedColumn(rightExpr); |
| |
| BiRelat *compare = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, leftExpr, rightExpr); |
| if (predicate) |
| predicate = new(bindWA->wHeap()) BiLogic(ITM_AND, predicate, compare); |
| else |
| predicate = compare; |
| } |
| } |
| } |
| // Binding this predicate is being done in caller, Join::bindNode() |
| return predicate; |
| } // joinCommonColumns() |
| |
| // Functions findNonCommonColumns() and coalesceCommonColumns() |
| // |
| // These create the column descriptors for the result of a natural join. |
| // A natural join is equivalent to |
| // |
| // SELECT SLCC, SLT1, SLT2 FROM T1, T2 |
| // |
| // where SLCC represents the list of coalesced common columns of T1 and T2, |
| // SLT1 represents the list of non-common columns of T1, and |
| // SLT2 represents the list of non-common columns of T2. |
| // |
| // A coalesced common column C is equivalent to |
| // |
| // COALESCE (T1.C, T2.C) AS C -- i.e. there is no table name; CorrName is "" |
| // |
| // where COALESCE (T1.C, T2.C) is equivalent to |
| // |
| // CASE WHEN T1.C IS NOT NULL THEN T1.C ELSE T2.C END |
| // |
| // Function findNonCommonColumns(), on the first call, coalesces common |
| // columns into the resultTable, and collects non-common columns. |
| // On the second call it continues to collect non-common columns. |
| // |
| // Function coalesceCommonColumns() adds SLCC, SLT1, SLT2 to the |
| // resultTable in the proper order. |
| // |
| static void findNonCommonColumns(BindWA *bindWA, |
| OperatorTypeEnum joinType, |
| const RETDesc &sourceTable, |
| const RETDesc &targetTable, |
| RETDesc &resultTable, |
| ColumnDescList &nonCommonCols) |
| { |
| // Used for ANSI 6.4 SR 3aii below. |
| CorrName implemDependCorr(bindWA->fabricateUniqueName(), TRUE); |
| // |
| for (CollIndex i = 0; i < sourceTable.getDegree(); i++) { |
| const ColRefName &sourceColRefName = sourceTable.getColRefNameObj(i); |
| ValueId sourceId = sourceTable.getValueId(i); |
| ColRefName simpleColRefName(sourceColRefName.getColName()); |
| // |
| // If a column is an unnamed expression, it is a non-common column. |
| // |
| if (simpleColRefName.isEmpty()) |
| nonCommonCols.insert(new (bindWA->wHeap()) |
| ColumnDesc(sourceColRefName, sourceId, NULL, bindWA->wHeap())); |
| else { |
| ColumnNameMap *commonCol = targetTable.findColumn(simpleColRefName); |
| // |
| // If the named column does not have a corresponding column in the |
| // target table, it is a non-common column. |
| // |
| if (NOT commonCol) |
| nonCommonCols.insert(new (bindWA->wHeap()) |
| ColumnDesc(sourceColRefName, sourceId, NULL, bindWA->wHeap())); |
| // |
| // If the target table has more than one corresponding column, error. |
| // |
| else if (commonCol->isDuplicate()) { |
| NAString fmtdList(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList(bindWA->wHeap()); |
| targetTable.getTableList(xtnmList, &fmtdList); // Tables in the RETDesc |
| |
| *CmpCommon::diags() << DgSqlCode(-4004) |
| << DgColumnName(simpleColRefName.getColName()) |
| << DgTableName(commonCol->getColRefNameObj().getCorrNameObj(). |
| getExposedNameAsAnsiString()) |
| << DgString0(fmtdList) |
| << DgString1(bindWA->getDefaultSchema().getSchemaNameAsAnsiString()); |
| |
| bindWA->setErrStatus(); |
| return; |
| } |
| else if (joinType != ITM_NO_OP) { |
| // |
| // Coalesce the common columns and add them to the result table. |
| // |
| ValueId resultId; |
| switch(joinType) { |
| case REL_JOIN: |
| case REL_LEFT_JOIN: |
| resultId = sourceId; |
| break; |
| case REL_RIGHT_JOIN: |
| resultId = commonCol->getValueId(); |
| break; |
| default: { |
| ItemExpr *sourceExpr = sourceId.getItemExpr(); |
| ItemExpr *targetExpr = commonCol->getValueId().getItemExpr(); |
| UnLogic *test = new (bindWA->wHeap()) |
| UnLogic(ITM_IS_NULL, sourceExpr); |
| ItemExpr *coalesce = new (bindWA->wHeap()) |
| Case(NULL, new (bindWA->wHeap()) |
| IfThenElse(test, |
| targetExpr, |
| sourceExpr)); |
| coalesce = coalesce->bindNode(bindWA)->castToItemExpr(); |
| if (bindWA->errStatus()) { |
| delete test; |
| delete coalesce; |
| return; |
| } |
| resultId = coalesce->getValueId(); |
| break; |
| } // default case (braces required since vars are initialized here) |
| } // switch |
| // |
| // ANSI 6.4 SR 3aii: |
| // We've fabricated a unique implementation-dependent CorrName |
| // outside the loop; the common columns have this basically |
| // invisible CorrName, the point of which seems to be that |
| // select * from |
| // ta natural join tb |
| // join -- not natural! |
| // (ta tx natural join tb ty) |
| // on 1=1; |
| // should not generate an ambiguous column reference error |
| // from the star-expansion. So according to ANSI, |
| // the two natural joins produce, respectively, |
| // fab1.slcc, ta.slt1, tb.slt2 |
| // fab2.slcc, tx.slt1, ty.slt2 |
| // so the join produces |
| // fab1.slcc, ta.slt1, tb.slt2, fab2.slcc, tx.slt1, ty.slt2 |
| // i.e. the two SLCC's are unambiguous. |
| // |
| ColRefName implemDepend(simpleColRefName.getColName(),implemDependCorr); |
| resultTable.addColumn(bindWA, implemDepend, resultId); |
| } // coalesce SLCC into resultTable |
| } // named column |
| } // for |
| } // findNonCommonColumns() |
| |
| // Comments for this function can be found above the preceding function. |
| static void coalesceCommonColumns(BindWA *bindWA, |
| OperatorTypeEnum joinType, |
| const RETDesc &leftTable, |
| const RETDesc &rightTable, |
| RETDesc &resultTable) |
| { |
| ColumnDescList nonCommonCols(bindWA->wHeap()); |
| // non-common columns of the left table |
| // |
| // Coalesce the common column names of the left and right tables and add |
| // them to the result table. |
| // Collect the non-common column names from the left. |
| // |
| findNonCommonColumns(bindWA, |
| joinType, |
| leftTable, |
| rightTable, |
| resultTable, |
| nonCommonCols); |
| if (bindWA->errStatus()) return; |
| // |
| // Collect the non-common column names from the right. |
| // |
| RETDesc irrelevantOnThisCall; |
| findNonCommonColumns(bindWA, |
| ITM_NO_OP, // do not add SLCC to resultTable |
| rightTable, |
| leftTable, |
| irrelevantOnThisCall, |
| nonCommonCols); |
| if (bindWA->errStatus()) return; |
| // |
| // Add the non-common columns from the left and right to the result table. |
| // |
| resultTable.addColumns(bindWA, nonCommonCols); |
| nonCommonCols.clearAndDestroy(); |
| // |
| // Add the system columns from the left and right to the result table. |
| // |
| resultTable.addColumns(bindWA, *leftTable.getSystemColumnList(), SYSTEM_COLUMN); |
| resultTable.addColumns(bindWA, *rightTable.getSystemColumnList(), SYSTEM_COLUMN); |
| } // coalesceCommonColumns() |
| |
| // For Catalog Manager, this function: |
| // 1) Fixes up the name location list to help with computing of the view text, |
| // check constraint search condition text, etc. |
| // 2) Collects the table (base table, view, etc.) usages information for |
| // view definitions, check constraint definitions, etc. |
| // |
| // ** Some of this could be implemented, perhaps more simply, |
| // ** using BindWA::viewCount() and BindWA::tableViewUsageList(). |
| // |
| static void BindUtil_CollectTableUsageInfo(BindWA *bindWA, |
| const CorrName& corrName) |
| { |
| // Task (1) |
| // |
| ParNameLocList *pNameLocList = bindWA->getNameLocListPtr(); |
| if (pNameLocList) |
| { |
| ParNameLoc * pNameLoc |
| = pNameLocList->getNameLocPtr(corrName.getNamePosition()); |
| if (pNameLoc) |
| { |
| if (NOT pNameLoc->getExpandedName(FALSE).isNull()) |
| CMPASSERT(pNameLoc->getExpandedName() == |
| corrName.getQualifiedNameObj().getQualifiedNameAsAnsiString()); |
| pNameLoc->setExpandedName( |
| corrName.getQualifiedNameObj().getQualifiedNameAsAnsiString()); |
| } |
| // |
| // Task (2) |
| // |
| ExprNode *pUsageParseNode = bindWA->getUsageParseNodePtr(); |
| if (pUsageParseNode) |
| { |
| if (pUsageParseNode->getOperatorType() == DDL_CREATE_VIEW) |
| { |
| StmtDDLCreateView &cvpn = *pUsageParseNode->castToElemDDLNode() |
| ->castToStmtDDLCreateView(); |
| ParTableUsageList &vtul = cvpn.getViewUsages().getViewTableUsageList(); |
| vtul.insert(corrName.getExtendedQualNameObj()); |
| } |
| else if (pUsageParseNode->getOperatorType() |
| == DDL_ALTER_TABLE_ADD_CONSTRAINT_CHECK) |
| { |
| StmtDDLAddConstraintCheck &node = *pUsageParseNode->castToElemDDLNode() |
| ->castToStmtDDLAddConstraintCheck(); |
| ParTableUsageList &tul = node.getTableUsageList(); |
| tul.insert(corrName.getQualifiedNameObj()); |
| } |
| } |
| } // if (pNameLocList) |
| |
| } // BindUtil_CollectTableUsageInfo() |
| |
| void castComputedColumnsToAnsiTypes(BindWA *bindWA, |
| RETDesc *rd, |
| ValueIdList &compExpr) |
| { |
| const ColumnDescList &cols = *rd->getColumnList(); |
| CollIndex i = cols.entries(); |
| CMPASSERT(i == compExpr.entries()); |
| |
| while (i--) { |
| ColumnDesc *col = cols[i]; |
| |
| if (col->getValueId().getType().getTypeQualifier() == NA_ROWSET_TYPE) { |
| return; |
| } |
| NAType *naType = &(NAType&)col->getValueId().getType(); |
| // |
| // Note: the unsupported and DATETIME cases are mutually exclusive with the LARGEDEC case below. |
| // |
| if (!naType->isSupportedType()) { |
| // Unsupported types are displayed as strings of '#' to their display length |
| ItemExpr *theRepeat = |
| new (bindWA->wHeap()) Repeat(new (bindWA->wHeap()) SystemLiteral("#"), |
| new (bindWA->wHeap()) SystemLiteral( |
| naType->getDisplayLength( |
| naType->getFSDatatype(), |
| 0, |
| naType->getPrecision(), |
| naType->getScale(), |
| 0))); |
| theRepeat = theRepeat->bindNode(bindWA); |
| col->setValueId(theRepeat->getValueId()); |
| compExpr[i] = theRepeat->getValueId(); |
| } |
| else if ((CmpCommon::getDefault(MODE_SPECIAL_1) == DF_ON) && |
| (NOT bindWA->inViewDefinition()) && |
| (NOT bindWA->inMVDefinition()) && |
| (NOT bindWA->inCTAS()) && |
| (naType->getTypeQualifier()== NA_DATETIME_TYPE && |
| ((const DatetimeType *)naType)->getSubtype() == |
| DatetimeType::SUBTYPE_SQLDate) && |
| (! CmpCommon::context()->getSqlmxRegress()) && |
| (strcmp(ActiveSchemaDB()->getDefaults().getValue(OUTPUT_DATE_FORMAT), |
| "ANSI") != 0)) |
| { // Special1 DATE, return as YY/MM/DD |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Format(col->getValueId().getItemExpr(), "YY/MM/DD", FALSE); |
| newChild = newChild->bindNode(bindWA); |
| col->setValueId(newChild->getValueId()); |
| compExpr[i] = newChild->getValueId(); |
| } |
| |
| if ((naType->getFSDatatype() == REC_BIN64_UNSIGNED) && |
| (CmpCommon::getDefault(TRAF_LARGEINT_UNSIGNED_IO) == DF_OFF) && |
| (NOT bindWA->inCTAS()) && |
| (NOT bindWA->inViewDefinition())) |
| { |
| NumericType *nTyp = (NumericType *)naType; |
| |
| ItemExpr * cast = new (bindWA->wHeap()) |
| Cast(col->getValueId().getItemExpr(), |
| new (bindWA->wHeap()) |
| SQLBigNum(MAX_HARDWARE_SUPPORTED_UNSIGNED_NUMERIC_PRECISION, |
| nTyp->getScale(), |
| FALSE, |
| FALSE, |
| naType->supportsSQLnull(), |
| NULL)); |
| |
| cast = cast->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return; |
| col->setValueId(cast->getValueId()); |
| compExpr[i] = cast->getValueId(); |
| |
| naType = (NAType*)&cast->getValueId().getType(); |
| } |
| |
| if ((naType->getFSDatatype() == REC_BOOLEAN) && |
| (CmpCommon::getDefault(TRAF_BOOLEAN_IO) == DF_OFF) && |
| (NOT bindWA->inCTAS()) && |
| (NOT bindWA->inViewDefinition())) |
| { |
| NumericType *nTyp = (NumericType *)naType; |
| |
| ItemExpr * cast = new (bindWA->wHeap()) |
| Cast(col->getValueId().getItemExpr(), |
| new (bindWA->wHeap()) |
| SQLChar(SQL_BOOLEAN_DISPLAY_SIZE, naType->supportsSQLnull())); |
| |
| cast = cast->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return; |
| col->setValueId(cast->getValueId()); |
| compExpr[i] = cast->getValueId(); |
| |
| naType = (NAType*)&cast->getValueId().getType(); |
| } |
| |
| // if OFF, return tinyint as smallint. |
| // This is needed until all callers/drivers have full support to |
| // handle IO of tinyint datatypes. |
| if (((naType->getFSDatatype() == REC_BIN8_SIGNED) || |
| (naType->getFSDatatype() == REC_BIN8_UNSIGNED)) && |
| (NOT bindWA->inCTAS()) && |
| (NOT bindWA->inViewDefinition()) && |
| ((CmpCommon::getDefault(TRAF_TINYINT_SUPPORT) == DF_OFF) || |
| (CmpCommon::getDefault(TRAF_TINYINT_RETURN_VALUES) == DF_OFF))) |
| { |
| NumericType *srcNum = (NumericType*)naType; |
| NumericType * newType; |
| if (srcNum->getScale() == 0) |
| newType = new (bindWA->wHeap()) |
| SQLSmall(NOT srcNum->isUnsigned(), |
| naType->supportsSQLnull()); |
| else |
| newType = new (bindWA->wHeap()) |
| SQLNumeric(sizeof(short), srcNum->getPrecision(), |
| srcNum->getScale(), |
| NOT srcNum->isUnsigned(), |
| naType->supportsSQLnull()); |
| |
| ItemExpr * cast = new (bindWA->wHeap()) |
| Cast(col->getValueId().getItemExpr(), newType); |
| |
| cast = cast->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return; |
| col->setValueId(cast->getValueId()); |
| compExpr[i] = cast->getValueId(); |
| } |
| else if (naType->getTypeQualifier() == NA_NUMERIC_TYPE && |
| !((NumericType &)col->getValueId().getType()).binaryPrecision()) { |
| NumericType *nTyp = (NumericType *)naType; |
| |
| ItemExpr * ie = col->getValueId().getItemExpr(); |
| NAType *newTyp = NULL; |
| Lng32 newPrec; |
| Lng32 newScale; |
| Lng32 oflow = -1; |
| Lng32 bignumOflow = -1; |
| NABoolean bignumIO = FALSE; |
| if (CmpCommon::getDefault(BIGNUM_IO) == DF_ON) |
| bignumIO = TRUE; // explicitely set to ON |
| else if (CmpCommon::getDefault(BIGNUM_IO) == DF_OFF) |
| bignumIO = FALSE; // explicitely set to OFF |
| else if (CmpCommon::getDefault(BIGNUM_IO) == DF_SYSTEM) |
| { |
| if ((nTyp->isBigNum()) && |
| (((SQLBigNum*)nTyp)->isARealBigNum())) |
| bignumIO = TRUE; |
| } |
| |
| if (CmpCommon::getDefaultNumeric(MAX_NUMERIC_PRECISION_ALLOWED) == |
| MAX_HARDWARE_SUPPORTED_SIGNED_NUMERIC_PRECISION) |
| bignumIO = FALSE; |
| |
| if (bignumIO) |
| bignumOflow = nTyp->getPrecision() - |
| (Lng32)CmpCommon::getDefaultNumeric(MAX_NUMERIC_PRECISION_ALLOWED); |
| else |
| { |
| if (nTyp->isSigned()) |
| oflow = nTyp->getPrecision() - MAX_HARDWARE_SUPPORTED_SIGNED_NUMERIC_PRECISION; |
| else |
| oflow = nTyp->getPrecision() - MAX_HARDWARE_SUPPORTED_UNSIGNED_NUMERIC_PRECISION; |
| } |
| |
| if ((bignumOflow > 0) || (oflow > 0)) |
| { |
| if (bignumOflow > 0) { |
| newPrec = |
| (Lng32)CmpCommon::getDefaultNumeric(MAX_NUMERIC_PRECISION_ALLOWED); |
| Lng32 orgMagnitude = nTyp->getPrecision() - nTyp->getScale(); |
| |
| // set the newScale |
| // IF there is overflow in magnitude set the scale to 0. |
| // ELSE set the accomodate the magnitude part and truncate the scale |
| newScale = (orgMagnitude >= newPrec) ? 0 : newPrec - orgMagnitude ; |
| |
| if (newScale > newPrec) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3015) |
| << DgInt0(newScale) << DgInt1(newPrec); |
| |
| bindWA->setErrStatus(); |
| return; |
| } |
| |
| newTyp = new (bindWA->wHeap()) |
| SQLBigNum(newPrec, |
| newScale, |
| ((SQLBigNum &)col->getValueId().getType()).isARealBigNum(), |
| nTyp->isSigned(), |
| nTyp->supportsSQLnull(), |
| NULL); |
| } |
| else if (oflow > 0) { |
| // If it's not a computed expr, but a column w/ a legal type, re-loop |
| if (col->getValueId().getNAColumn(TRUE/*don't assert*/)) { |
| //CMPASSERT(!nTyp->isInternalType()); |
| //continue; |
| } |
| |
| OperatorTypeEnum op = ie->origOpType(); |
| CMPASSERT(op != NO_OPERATOR_TYPE && // Init'd correctly? |
| op != ITM_RENAME_COL && // Expect these to have |
| op != ITM_REFERENCE); // been bound, vanished. |
| |
| ItemExpr *ie2 = ie; |
| |
| while (op == ITM_INSTANTIATE_NULL) |
| { |
| ie2 = ie2->child(0).getPtr(); |
| op = ie2->origOpType(); |
| } |
| |
| // ANSI 6.5 SR 7 - 9: aggregates must be exact if column is exact. |
| newPrec = MAX_NUMERIC_PRECISION; |
| Lng32 orgMagnitude = (nTyp->getMagnitude() + 9) / 10; |
| // set the newScale |
| // IF there is overflow in magnitude set the scale to 0. |
| // ELSE set the accomodate the magnitude part and truncate the scale |
| newScale = (orgMagnitude >= newPrec) ? 0 : newPrec - orgMagnitude ; |
| |
| // Based on the CQD set the scale to MIN value. |
| // CQD specifies the MIN scale that has to be preserved in case |
| // of overflow. |
| NADefaults &defs = ActiveSchemaDB()->getDefaults(); |
| Lng32 minScale = defs.getAsLong(PRESERVE_MIN_SCALE); |
| newScale = MAXOF(minScale, newScale); |
| |
| if (op == ITM_SUM || op == ITM_AVG) { |
| // AVG = DIVIDE( SUM(), COUNT() ) |
| ItemExpr *tmp = (op == ITM_SUM) ? |
| ie2 : ie2->child(0).getPtr(); |
| |
| // |
| // Now that we support OLAP functions, this may be |
| // a pointer to an ITM_NOTCOVERED node. If so, we |
| // need to check its child(0) node rather than |
| // the ITM_NOTCOVERED node. |
| // |
| if (tmp->getOperatorType() == ITM_NOTCOVERED ) |
| tmp = (Aggregate *)(ItemExpr *)tmp->child(0); |
| |
| CMPASSERT(tmp->isAnAggregate()); |
| Aggregate *sum = (Aggregate *)tmp; |
| ItemExpr *arg = (sum->getOriginalChild()) ? |
| sum->getOriginalChild() : sum->child(0).getPtr(); |
| if (arg->getValueId() == NULL_VALUE_ID) |
| arg = sum->child(0).getPtr(); |
| CMPASSERT(arg->getValueId() != NULL_VALUE_ID); |
| Lng32 needScale = arg->getValueId().getType().getScale(); |
| if (needScale > newPrec) |
| needScale = newPrec; |
| if (newScale < needScale || op == ITM_SUM) // ANSI 6.5 SR 9 b + c |
| newScale = needScale; |
| } |
| |
| if (newScale == 0) |
| newTyp = new (bindWA->wHeap()) |
| SQLLargeInt(TRUE, // hardware only supports signed |
| nTyp->supportsSQLnull()); |
| else |
| newTyp = new (bindWA->wHeap()) |
| SQLNumeric(sizeof(Int64), |
| newPrec, |
| newScale, |
| nTyp->isSigned(), |
| nTyp->supportsSQLnull()); |
| |
| } // overflow |
| |
| ItemExpr *cast = new (bindWA->wHeap()) |
| Cast(ie, newTyp, ITM_CAST, TRUE/*checkForTrunc*/); |
| cast = cast->bindNode(bindWA); |
| if (bindWA->errStatus()) return; |
| |
| if (!col->getColRefNameObj().getColName().isNull()) { |
| // We get here via CREATE VIEW v AS SELECT (expr op expr) AS nam ...; |
| |
| // ColumnDesc::setValueId() makes the RETDesc's XCNM inconsistent -- |
| // but this is ok because name lookup over this XCNM doesn't happen |
| // after the point we've gotten to here -- |
| // a) if caller is StmtDDLCreateView::bindNode via RelRoot::bindNode, |
| // there's no further lookup at all; |
| // b) if caller is bindView(), then thanks to the way RenameTable |
| // and RETDesc work, the inconsistent XCNM is not consulted |
| // so we don't have to worry about this issue ... (for now anyhow!) |
| } |
| col->setValueId(cast->getValueId()); |
| compExpr[i] = cast->getValueId(); |
| } // overflow (bignum or regular) |
| } // numeric |
| } // loop over cols in RETDesc |
| } // castComputedColumnsToAnsiTypes() |
| |
| TrafDesc *generateSpecialDesc(const CorrName& corrName) |
| { |
| TrafDesc * desc = NULL; |
| |
| if (corrName.getSpecialType() == ExtendedQualName::VIRTUAL_TABLE) |
| { |
| if (corrName.getQualifiedNameObj().getObjectName() == ExplainFunc::getVirtualTableNameStr()) |
| { |
| ExplainFunc ef; |
| desc = ef.createVirtualTableDesc(); |
| } |
| else if (corrName.getQualifiedNameObj().getObjectName() == StatisticsFunc::getVirtualTableNameStr()) |
| { |
| StatisticsFunc sf; |
| desc = sf.createVirtualTableDesc(); |
| } |
| else if (corrName.getQualifiedNameObj().getObjectName() == ExeUtilRegionStats::getVirtualTableNameStr()) |
| { |
| ExeUtilRegionStats eudss; |
| desc = eudss.createVirtualTableDesc(); |
| } |
| else if (corrName.getQualifiedNameObj().getObjectName() == ExeUtilRegionStats::getVirtualTableClusterViewNameStr()) |
| { |
| ExeUtilRegionStats eudss(TRUE); |
| desc = eudss.createVirtualTableDesc(); |
| } |
| else if (HiveMDaccessFunc::isHiveMD(corrName.getQualifiedNameObj().getObjectName())) |
| { |
| NAString mdType = |
| HiveMDaccessFunc::getMDType(corrName.getQualifiedNameObj().getObjectName()); |
| |
| HiveMDaccessFunc hivemd(&mdType); |
| desc = hivemd.createVirtualTableDesc(); |
| } |
| } |
| |
| return desc; |
| } // generateSpecialDesc() |
| |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class BindWA |
| // ----------------------------------------------------------------------- |
| NARoutine *BindWA::getNARoutine ( const QualifiedName &name ) |
| { |
| NARoutineDBKey key(name, wHeap()); |
| NARoutine * naRoutine = getSchemaDB()->getNARoutineDB()->get(this, &key); |
| if (!naRoutine) |
| { |
| TrafDesc *udfMetadata = NULL; |
| CmpSeabaseDDL cmpSBD(STMTHEAP); |
| udfMetadata = cmpSBD.getSeabaseRoutineDesc( |
| name.getCatalogName(), |
| name.getSchemaName(), |
| name.getObjectName()); |
| if (!udfMetadata) |
| return NULL; |
| |
| NAHeap *routineHeap; |
| if (getSchemaDB()->getNARoutineDB()->cachingMetaData()) |
| { |
| const Lng32 size = 16 * 1024; // The initial size |
| routineHeap = new CTXTHEAP NAHeap("NARoutine Heap", (NAHeap *)CTXTHEAP, |
| size); |
| routineHeap->setJmpBuf(CmpInternalErrorJmpBufPtr); |
| } |
| else |
| routineHeap=CmpCommon::statementHeap(); |
| |
| Int32 errors=0; |
| naRoutine = new (routineHeap) |
| NARoutine(name, |
| udfMetadata, |
| this, |
| errors, |
| routineHeap); |
| if ( NULL == naRoutine || errors != 0) |
| { |
| setErrStatus(); |
| return NULL; |
| } |
| |
| // Add NARoutine to the NARoutineDB cache. |
| if (getSchemaDB()->getNARoutineDB()->cachingMetaData()) |
| getSchemaDB()->getNARoutineDB()->put(naRoutine); |
| } |
| return naRoutine; |
| } |
| |
| NATable *BindWA::getNATable(CorrName& corrName, |
| NABoolean catmanCollectTableUsages, // default TRUE |
| TrafDesc *inTableDescStruct) // default NULL |
| { |
| BindWA *bindWA = this; // for coding convenience |
| |
| NATable * table = NULL; |
| // Search in volatile schema first. If not found, search in regular cat/sch. |
| NABoolean volatileTableFound = FALSE; |
| NAString userName; |
| if ((CmpCommon::context()->sqlSession()->volatileSchemaInUse()) && |
| (! inTableDescStruct) && |
| (corrName.getSpecialType() != ExtendedQualName::VIRTUAL_TABLE) && |
| (corrName.getSpecialType() != ExtendedQualName::HBMAP_TABLE)) |
| { |
| CorrName newCorrName = |
| CmpCommon::context()->sqlSession()->getVolatileCorrName |
| (corrName); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| //get NATable from cache |
| table = bindWA->getSchemaDB()->getNATableDB()-> |
| get(newCorrName, bindWA, inTableDescStruct); |
| if (!table) |
| { |
| // now search in regular cat/sch. |
| // clear diags area. |
| CmpCommon::diags()->clear(); |
| bindWA->resetErrStatus(); |
| } |
| else |
| { |
| NABoolean isValid = |
| CmpCommon::context()->sqlSession()->validateVolatileCorrName |
| (corrName); |
| |
| // if this table is found in volatile schema, then |
| // make sure it is a volatile table. |
| if ((isValid) && |
| (NOT table->isVolatileTable())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4190) << |
| DgTableName(table->getTableName(). |
| getQualifiedNameAsAnsiString(TRUE)); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (isValid) |
| { |
| newCorrName.setIsVolatile(TRUE); |
| corrName = newCorrName; |
| } |
| else |
| { |
| // table was found in the volatile schema but it is |
| // not a valid volatile name. |
| // Look for it in regular schema. |
| table = NULL; |
| |
| CmpCommon::diags()->clear(); |
| bindWA->resetErrStatus(); |
| |
| // remember that volatile table was found so we |
| // can generate a better error message later. |
| volatileTableFound = TRUE; |
| } |
| } |
| } |
| |
| if (! table) |
| { |
| // Expand the table (base table, view, etc.) name with |
| // the default catalog and schema parts if the specified |
| // table name does not include these parts. |
| // This method will also first apply any prototype value (from a host var) |
| // into the corrName's qualifiedName. |
| // |
| |
| NABoolean catNameSpecified = |
| (NOT corrName.getQualifiedNameObj().getCatalogName().isNull()); |
| NABoolean schNameSpecified = |
| (NOT corrName.getQualifiedNameObj().getSchemaName().isNull()); |
| |
| // try PUBLIC SCHEMA only when no schema was specified |
| // and CQD PUBLIC_SCHEMA_NAME is specified |
| NAString publicSchema = ""; |
| CmpCommon::getDefault(PUBLIC_SCHEMA_NAME, publicSchema, FALSE); |
| ComSchemaName pubSchema(publicSchema); |
| NAString pubSchemaIntName = ""; |
| if ( !schNameSpecified && !pubSchema.getSchemaNamePart().isEmpty() ) |
| { |
| pubSchemaIntName = pubSchema.getSchemaNamePart().getInternalName(); |
| } |
| |
| corrName.applyDefaults(bindWA, bindWA->getDefaultSchema()); |
| if (bindWA->errStatus()) |
| return NULL; // prototype value parse error |
| |
| // cannot use hbase map schema as table name |
| if ((corrName.getQualifiedNameObj().getSchemaName() == HBASE_EXT_MAP_SCHEMA) && |
| (! Get_SqlParser_Flags(INTERNAL_QUERY_FROM_EXEUTIL)) && |
| (! Get_SqlParser_Flags(ALLOW_SPECIALTABLETYPE))) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4261) |
| << DgSchemaName(corrName.getQualifiedNameObj().getSchemaName()); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // if this is an HBase mapped table and schema name is not specified, |
| // then set schema to hbase map schema. |
| if ((corrName.getSpecialType() == ExtendedQualName::HBMAP_TABLE) && |
| (NOT schNameSpecified)) |
| { |
| corrName.getQualifiedNameObj().setSchemaName(HBASE_EXT_MAP_SCHEMA); |
| } |
| |
| // override schema |
| if ( ( overrideSchemaEnabled() ) |
| // not volatile table |
| && ( ! volatileTableFound ) |
| ) |
| { |
| doOverrideSchema(corrName); |
| } |
| |
| // if DEFAULT_SCHEMA_ACCESS_ONLY, can only access default and public schemas |
| if (corrName.getSpecialType()==ExtendedQualName::NORMAL_TABLE) |
| // NORMAL_TABLE also covers synonym, view and MV |
| { |
| if (violateAccessDefaultSchemaOnly(corrName.getQualifiedNameObj())) |
| return NULL; |
| } |
| |
| // make sure that schema name is not a VOLATILE SCHEMA |
| if ((! bindWA->inDDL()) || |
| ((bindWA->inViewDefinition()) || |
| (bindWA->inMVDefinition()))) |
| { |
| if (! CmpCommon::context()->sqlSession()->validateVolatileQualifiedSchemaName |
| (corrName.getQualifiedNameObj())) |
| { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| |
| //get NATable (from cache or from metadata) |
| table = bindWA->getSchemaDB()->getNATableDB()-> |
| get(corrName, bindWA, inTableDescStruct); |
| |
| //try the public schema if not found |
| if ( !table && !pubSchemaIntName.isNull() ) |
| { |
| CorrName pCorrName(corrName); |
| pCorrName.getQualifiedNameObj().setSchemaName(pubSchemaIntName); |
| if ( !pubSchema.getCatalogNamePart().isEmpty() ) |
| { |
| pCorrName.getQualifiedNameObj().setCatalogName( |
| pubSchema.getCatalogNamePart().getInternalName()); |
| } |
| |
| bindWA->resetErrStatus(); |
| table = bindWA->getSchemaDB()->getNATableDB()-> |
| get(pCorrName, bindWA, inTableDescStruct); |
| if ( !bindWA->errStatus() && table ) |
| { // if found in public schema, do not show previous error |
| // and replace corrName |
| CmpCommon::diags()->clear(); |
| corrName.getQualifiedNameObj().setCatalogName( |
| pCorrName.getQualifiedNameObj().getCatalogName()); |
| corrName.getQualifiedNameObj().setSchemaName( |
| pCorrName.getQualifiedNameObj().getSchemaName()); |
| } |
| } |
| |
| // if sch name was not specified and table is not found in default schema, then |
| // look for it in HBase mapped schema. |
| if ( !table && ! schNameSpecified) |
| { |
| CorrName pCorrName(corrName); |
| pCorrName.getQualifiedNameObj().setSchemaName(HBASE_EXT_MAP_SCHEMA); |
| |
| bindWA->resetErrStatus(); |
| Lng32 diagsMark = CmpCommon::diags()->mark(); |
| table = bindWA->getSchemaDB()->getNATableDB()-> |
| get(pCorrName, bindWA, inTableDescStruct); |
| if ( !bindWA->errStatus() && table ) |
| { // if found in mapped schema, do not show previous error |
| // and replace corrName |
| CmpCommon::diags()->clear(); |
| corrName.getQualifiedNameObj().setCatalogName( |
| pCorrName.getQualifiedNameObj().getCatalogName()); |
| corrName.getQualifiedNameObj().setSchemaName( |
| pCorrName.getQualifiedNameObj().getSchemaName()); |
| } |
| else |
| { |
| // discard the errors from failed map table name lookup and only return |
| // the previous error. |
| CmpCommon::diags()->rewind(diagsMark); |
| } |
| } |
| |
| // move to here, after public schema try because BindUtil_CollectTableUsageInfo |
| // saves table info for mv definition, etc. |
| // Conditionally (usually) do stuff for Catalog Manager (static func above). |
| if (catmanCollectTableUsages) |
| if (corrName.getSpecialType() != ExtendedQualName::TRIGTEMP_TABLE) |
| BindUtil_CollectTableUsageInfo(bindWA, corrName); |
| |
| if (!table) |
| { |
| if (volatileTableFound) |
| { |
| if ((CmpCommon::diags()->mainSQLCODE() == -1003) && |
| (NOT catNameSpecified)) |
| { |
| // the name is in true USER_NAME.VOL_TAB_NAME form |
| // where the USER_NAME doesn't match current name. |
| // Clear errors and return an appropriate message. |
| CmpCommon::diags()->clear(); |
| CmpCommon::context()->sqlSession()->validateVolatileCorrName |
| (corrName); |
| bindWA->setErrStatus(); |
| } |
| } |
| |
| return NULL; |
| } |
| } |
| |
| // if a volatile table is found, make sure that volatile schema is in |
| // use and volatile tables are allowed. |
| if ((table) && (table->isVolatileTable())) |
| { |
| // set volatile table indication in table's tablename |
| ((QualifiedName&)(table->getTableName())).setIsVolatile(TRUE); |
| } |
| |
| // For now, don't allow access through the Trafodion external name created for |
| // native HIVE or HBASE objects unless the allowExternalTables flag is set. |
| // allowExternalTables is set for drop table and SHOWDDL statements. |
| // TDB - may want to merge the Trafodion version with the native version. |
| if ((table) && |
| (table->isExternalTable() && |
| (NOT table->getTableName().isHbaseMappedName()) && |
| (! bindWA->allowExternalTables()))) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4258) |
| << DgTableName(table->getTableName().getQualifiedNameAsAnsiString()); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // If the table is an external table and has an associated native table, |
| // check to see if the external table structure still matches the native table. |
| // If not, return an error |
| if ((table) && table->isExternalTable() && |
| (NOT table->getTableName().isHbaseMappedName())) |
| { |
| NAString adjustedName =ComConvertTrafNameToNativeName |
| (table->getTableName().getCatalogName(), |
| table->getTableName().getUnqualifiedSchemaNameAsAnsiString(), |
| table->getTableName().getUnqualifiedObjectNameAsAnsiString()); |
| |
| // Get a description of the associated Trafodion table |
| Int32 numNameParts = 3; |
| QualifiedName adjustedQualName(adjustedName,numNameParts,STMTHEAP, bindWA); |
| CorrName externalCorrName(adjustedQualName, STMTHEAP); |
| NATable *nativeNATable = bindWA->getSchemaDB()->getNATableDB()-> |
| get(externalCorrName, bindWA, inTableDescStruct); |
| if ((bindWA->externalTableDrop()) && |
| (bindWA->errStatus())) |
| { |
| return NULL; |
| } |
| |
| // Compare column lists |
| // TBD - return what mismatches |
| if ( nativeNATable && |
| !(table->getNAColumnArray() == nativeNATable->getNAColumnArray()) && |
| (NOT bindWA->externalTableDrop())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3078) |
| << DgString0(adjustedName) |
| << DgTableName(table->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| nativeNATable->setRemoveFromCacheBNC(TRUE); |
| return NULL; |
| } |
| } |
| |
| HostVar *proto = corrName.getPrototype(); |
| if (proto && proto->isPrototypeValid()) |
| corrName.getPrototype()->bindNode(bindWA); |
| |
| // This test is not "inAnyConstraint()" because we DO want to increment |
| // the count for View With Check Option constraints. |
| if (!getCurrentScope()->context()->inTableCheckConstraint() && |
| !getCurrentScope()->context()->inRIConstraint()) |
| table->incrReferenceCount(); |
| |
| if (table) |
| OSIM_captureTableOrView(table); |
| |
| return table; |
| } // BindWA::getNATable() |
| |
| NATable *BindWA::getNATableInternal( |
| CorrName& corrName, |
| NABoolean catmanCollectTableUsages, // default TRUE |
| TrafDesc *inTableDescStruct, // default NULL |
| NABoolean extTableDrop) |
| { |
| ULng32 savedParserFlags = Get_SqlParser_Flags (0xFFFFFFFF); |
| Set_SqlParser_Flags(ALLOW_VOLATILE_SCHEMA_IN_TABLE_NAME); |
| Set_SqlParser_Flags(INTERNAL_QUERY_FROM_EXEUTIL); |
| |
| setAllowExternalTables(TRUE); |
| if (extTableDrop) |
| setExternalTableDrop(TRUE); |
| |
| NATable * nat = |
| getNATable(corrName, catmanCollectTableUsages, inTableDescStruct); |
| |
| // Restore parser flags settings to what they originally were |
| Assign_SqlParser_Flags (savedParserFlags); |
| setAllowExternalTables(FALSE); |
| setExternalTableDrop(FALSE); |
| |
| return nat; |
| } |
| |
| static TableDesc *createTableDesc2(BindWA *bindWA, |
| const NATable *naTable, |
| CorrName &corrName, Hint *hint) |
| { |
| // Allocate a base table descriptor. |
| // |
| TableDesc *tdesc = new (bindWA->wHeap()) TableDesc(bindWA, naTable, corrName); |
| |
| // Insert the table name into the XTNM. |
| // |
| bindWA->getCurrentScope()->getXTNM()->insertNames(bindWA, corrName); |
| if (bindWA->errStatus()) return NULL; |
| |
| // For each NAColumn, allocate a BaseColumn, bind the BaseColumn, and |
| // add the ValueId to the TableDesc. |
| // |
| CollIndex i = 0; |
| for (i = 0; i < naTable->getColumnCount(); i++) { |
| BaseColumn *baseCol = new (bindWA->wHeap()) BaseColumn(tdesc, i); |
| baseCol->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| ValueId valId = baseCol->getValueId(); |
| tdesc->addToColumnList(valId); |
| } |
| |
| // set primary key for this table |
| tdesc->setPrimaryKeyColumns(); |
| |
| // For each index, create an IndexDesc. |
| // |
| |
| NAString indexChoice; |
| NADefaults &defs = ActiveSchemaDB()->getDefaults(); |
| defs.getValue(HIDE_INDEXES,indexChoice); |
| |
| for (i = 0; i < naTable->getIndexList().entries(); i++) |
| { |
| NAFileSet *nfs=naTable->getIndexList()[i]; |
| |
| IndexDesc *idesc = new (bindWA->wHeap()) |
| IndexDesc(tdesc, nfs, bindWA->currentCmpContext()); |
| |
| if (naTable->getClusteringIndex()->getFileSetName() == |
| idesc->getIndexName()) { |
| tdesc->setClusteringIndex(idesc); |
| idesc->markAsClusteringIndex(); |
| } |
| |
| if(indexChoice.compareTo("NONE") ==0 |
| OR indexChoice.compareTo("VERTICAL") ==0 |
| OR (indexChoice.compareTo("KEYINDEXES") ==0 AND |
| tdesc->isKeyIndex(idesc)) |
| OR naTable->getClusteringIndex()->getFileSetName() == |
| nfs->getFileSetName()) |
| { |
| tdesc->addIndex(idesc); |
| // implementation of optimizer hints |
| if (hint AND hint->hasIndexHint |
| (idesc->getNAFileSet()->getExtFileSetName())) |
| { |
| tdesc->addHintIndex(idesc); |
| } |
| if (idesc->isUniqueIndex() ) |
| tdesc->addUniqueIndex(idesc); |
| } |
| else |
| { |
| delete idesc; |
| } |
| |
| } |
| |
| // For each vertical partition, create an IndexDesc. |
| // Add this VP to the list of VPs for the TableDesc. |
| for (i = 0; i < naTable->getVerticalPartitionList().entries(); i++) { |
| if(indexChoice.compareTo("NONE") ==0 |
| OR indexChoice.compareTo("INDEXES")==0 |
| OR indexChoice.compareTo("KEYINDEXES")==0) |
| { |
| IndexDesc *idesc = new (bindWA->wHeap()) |
| IndexDesc(tdesc, naTable->getVerticalPartitionList()[i], |
| bindWA->currentCmpContext()); |
| tdesc->addVerticalPartition(idesc); |
| } |
| } |
| |
| // Allocate a RETDesc, attach it to the BindScope. |
| // |
| bindWA->getCurrentScope()->setRETDesc(new (bindWA->wHeap()) |
| RETDesc(bindWA, tdesc)); |
| |
| // Do not include tables-referenced-in-a-constraint (when/if we allow them) |
| // in the view-contains-table list; if we did include them, then |
| // TableViewUsageList::getViewsOnTable() would give wrong results |
| // for where it's used to prevent the Halloween problem. |
| // |
| // If we end up needing this extra info, I advise either a separate list, |
| // or a new field in TableViewUsage indicating usage type (containment |
| // versus reference), enhancing method getViewsOnTable() accordingly. |
| // |
| if (!bindWA->getCurrentScope()->context()->inAnyConstraint()) |
| bindWA->tableViewUsageList().insert(new (bindWA->wHeap()) |
| TableViewUsage( |
| tdesc->getCorrNameObj().getQualifiedNameObj(), |
| tdesc->getCorrNameObj().getSpecialType(), |
| naTable->getViewText() != NULL, |
| bindWA->viewCount())); |
| |
| return tdesc; |
| |
| } // static createTableDesc2() |
| |
| TableDesc *BindWA::createTableDesc(const NATable *naTable, |
| CorrName &corrName, |
| NABoolean catmanCollectUsages, Hint *hint) |
| { |
| BindWA *bindWA = this; // for coding convenience |
| |
| TableDesc *tdesc = createTableDesc2(bindWA, naTable, corrName, hint); |
| if (bindWA->errStatus()) return NULL; |
| |
| // Now bind any table check constraints and attach them to our new tdesc. |
| // These constraints must be processed for UPDATE and INSERT. |
| // DELETEs must clear them; see Delete::bindNode. |
| // |
| // For SELECTs, NOT NULL constraints are marked on the NAColumn::allowsNulls |
| // allowing more elaborate Transformations. For SELECTs, other types of |
| // constraints are not currently used, but could be in future, |
| // to optimize by providing additional predicate/selectivity info. |
| // |
| // ## We ought to write some regression test cases like |
| // INSERT INTO T (SELECT * FROM S) -- T's constraints yes, S irrelevant |
| // INSERT INTO T VALUES ((SELECT A FROM S WHERE..),..) |
| // INSERT INTO V3 ... -- underlying basetbl's constrts yes |
| // -- V3 atop VA atop T: let the views be |
| // -- WITH CHECK OPTION, then viewpred-constrt yes |
| // |
| const CheckConstraintList &ccl = naTable->getCheckConstraints(); |
| if (ccl.entries()) { |
| |
| // Table check constraint text is stored in the metadata tables |
| // with the underlying table/view name (e.g. "CHECK (C.S.T.COL > 0)"), |
| // whereas any correlation name in a query |
| // (e.g. "SELECT * FROM C.S.T FOO WHERE COL < 10") |
| // is irrelevant to the persistent constraint text -- |
| // when binding the check constraint, we want to find column C.S.T.COL, |
| // while the TableDesc/RETDesc just built only exposes the column |
| // under names COL and FOO.COL. |
| // |
| // So, if we have a correlation name, we must: |
| // - rename our TableDesc (rename FOO to C.S.T) |
| // - create a temporary table name scope for C.S.T that will hide FOO |
| // - construct a temporary RETDesc with names COL, T.COL, S.T.COL, C.S.T.COL |
| // but the same ValueId's they had before |
| // |
| // Then we bind the constraints using that RETDesc for name lookups. |
| // |
| // Then for the non-empty correlation, reset/undo the temporary stuff. |
| |
| RETDesc *savedRETDesc = NULL; |
| NABoolean corrNameIsNonEmpty = !corrName.getCorrNameAsString().isNull(); |
| CorrName synonymReferenceCorrName; |
| |
| if(naTable->getIsSynonymTranslationDone()){ |
| QualifiedName baseQualifiedName(naTable->getSynonymReferenceName(),3); |
| synonymReferenceCorrName=baseQualifiedName; |
| } |
| |
| if ((corrNameIsNonEmpty) || (naTable->getIsSynonymTranslationDone())) { |
| |
| CorrName baseCorrName; |
| baseCorrName = (naTable->getIsSynonymTranslationDone()) ? synonymReferenceCorrName : naTable->getTableName(); |
| |
| tdesc->setCorrName(baseCorrName); |
| |
| bindWA->getCurrentScope()->xtnmStack()->createXTNM(); |
| bindWA->getCurrentScope()->getXTNM()->insertNames(bindWA, baseCorrName); |
| if (bindWA->errStatus()) return NULL; |
| |
| savedRETDesc = bindWA->getCurrentScope()->getRETDesc(); |
| bindWA->getCurrentScope()->setRETDesc(new (bindWA->wHeap()) |
| RETDesc(bindWA, tdesc)); |
| if (bindWA->errStatus()) return NULL; |
| } |
| |
| for (CollIndex i = 0; i < ccl.entries(); i++) { |
| ItemExpr *constraintPred = |
| bindCheckConstraint(bindWA, ccl[i], naTable, catmanCollectUsages); |
| if (constraintPred) |
| tdesc->addCheckConstraint(bindWA, naTable, ccl[i], constraintPred); |
| else if (bindWA->errStatus()) |
| break; |
| } |
| |
| if ((corrNameIsNonEmpty) || (naTable->getIsSynonymTranslationDone())){ // reset temporaries |
| tdesc->setCorrName(corrName); |
| delete bindWA->getCurrentScope()->getRETDesc(); |
| bindWA->getCurrentScope()->setRETDesc(savedRETDesc); |
| bindWA->getCurrentScope()->xtnmStack()->removeXTNM(); |
| } |
| } // check constraint processing required |
| |
| // if the table contains computed columns, bind the expressions to compute the columns |
| for (CollIndex c = 0; c < naTable->getColumnCount(); c++) { |
| NAColumn *nac = tdesc->getNATable()->getNAColumnArray()[c]; |
| |
| if (nac->isComputedColumn()) { |
| ItemExpr *computedColumnExpr = NULL; |
| Parser parser(bindWA->currentCmpContext()); |
| |
| // parse the text stored in the NAColumn |
| computedColumnExpr = parser.getItemExprTree( |
| nac->getComputedColumnExprString(), |
| str_len(nac->getComputedColumnExprString()), |
| CharInfo::UTF8); |
| |
| if (computedColumnExpr) { |
| ParNameLocList *saveNameLocList = bindWA->getNameLocListPtr(); |
| bindWA->setNameLocListPtr(NULL); |
| bindWA->getCurrentScope()->context()->inComputedColumnExpr() = TRUE; |
| |
| computedColumnExpr = computedColumnExpr->bindNode(bindWA); |
| |
| bindWA->setNameLocListPtr(saveNameLocList); |
| bindWA->getCurrentScope()->context()->inComputedColumnExpr() = FALSE; |
| |
| if (bindWA->errStatus()) { |
| delete computedColumnExpr; |
| computedColumnExpr = NULL; |
| return NULL; |
| } |
| else { |
| // Store the expression tree in the base column |
| ((BaseColumn *) tdesc->getColumnList()[c].getItemExpr())-> |
| setComputedColumnExpr(computedColumnExpr->getValueId()); |
| } |
| } |
| } |
| } |
| |
| return tdesc; |
| |
| } // BindWA::createTableDesc() |
| |
| |
| // QSTUFF - helper for BindWA::bindView. |
| static void propagateDeleteAndStream(RelExpr *re, GroupAttributes *ga) |
| { |
| if (ga->isEmbeddedUpdateOrDelete()) |
| re->getGroupAttr()->setEmbeddedIUD( |
| ga->getEmbeddedIUD()); |
| |
| if (ga->isStream()) |
| re->getGroupAttr()->setStream(TRUE); |
| |
| if (ga->isSkipInitialScan()) |
| re->getGroupAttr()->setSkipInitialScan(TRUE); |
| |
| Int32 arity = re->getArity(); |
| for (Int32 i = 0; i < arity; i++) { |
| if (re->child(i)) |
| propagateDeleteAndStream(re->child(i), ga); |
| } |
| } |
| |
| RelExpr *BindWA::bindView(const CorrName &viewName, |
| const NATable *naTable, |
| const StmtLevelAccessOptions &accessOptions, |
| ItemExpr *predicate, |
| GroupAttributes *groupAttrs, |
| NABoolean catmanCollectUsages) |
| { |
| BindWA *bindWA = this; // for coding convenience |
| |
| CMPASSERT(viewName.getQualifiedNameObj() == naTable->getTableName()); |
| |
| NABoolean inViewExpansion = bindWA->setInViewExpansion(TRUE); // QSTUFF |
| |
| // set a flag for overrride_schema |
| //if (overrideSchemaEnabled()) |
| bindWA->getCurrentScope()->setInViewExpansion(TRUE); |
| |
| if (!bindWA->getCurrentScope()->context()->inAnyConstraint()) |
| bindWA->tableViewUsageList().insert(new (bindWA->wHeap()) |
| TableViewUsage( |
| viewName.getQualifiedNameObj(), |
| viewName.getSpecialType(), |
| TRUE/*isView*/, |
| bindWA->viewCount())); |
| |
| // save the current parserflags setting |
| ULng32 savedParserFlags = Get_SqlParser_Flags (0xFFFFFFFF); |
| |
| // allow funny characters in the tablenames used in the select list. |
| // This enables views to be created on 'internal' secret table |
| // so they could be accessed. |
| // At view creation time, the caller still need to set this |
| // parserflag from the sql interface(mxci, etc) otherwise the view |
| // creation will fail. Since parserflags can only be set by super |
| // users, the view with special tablenames could only have been created |
| // by a super user. |
| Set_SqlParser_Flags(ALLOW_FUNNY_IDENTIFIER); |
| |
| // Parse the view text. |
| // |
| // isolation level and order by are allowed in create view, if |
| // the corresponding cqds are set. |
| // These cqds are only valid during 'create view' time. Once the views |
| // are created, we don't need to look at them. |
| // During view expansion when we reach this method, turn the cqds on if |
| // they are not already on, so parser doesn't return an error. |
| // Reset them back, if they were set here. |
| NABoolean allowIsolationLevelWasSet = FALSE; |
| NABoolean allowOrderByWasSet = FALSE; |
| if (CmpCommon::getDefault(ALLOW_ISOLATION_LEVEL_IN_CREATE_VIEW) == DF_OFF) |
| { |
| allowIsolationLevelWasSet = TRUE; |
| |
| NAString op("ON"); |
| ActiveSchemaDB()->getDefaults().validateAndInsert |
| ("ALLOW_ISOLATION_LEVEL_IN_CREATE_VIEW", op, FALSE); |
| } |
| if (CmpCommon::getDefault(ALLOW_ORDER_BY_IN_CREATE_VIEW) == DF_OFF) |
| { |
| allowOrderByWasSet = TRUE; |
| |
| NAString op("ON"); |
| ActiveSchemaDB()->getDefaults().validateAndInsert |
| ("ALLOW_ORDER_BY_IN_CREATE_VIEW", op, FALSE); |
| } |
| |
| Parser parser(bindWA->currentCmpContext()); |
| ExprNode *viewTree = parser.parseDML(naTable->getViewText(), |
| naTable->getViewLen(), |
| naTable->getViewTextCharSet()); |
| |
| // Restore parser flags settings to what they originally were |
| Set_SqlParser_Flags (savedParserFlags); |
| |
| if (allowIsolationLevelWasSet) |
| { |
| NAString op("OFF"); |
| ActiveSchemaDB()->getDefaults().validateAndInsert |
| ("ALLOW_ISOLATION_LEVEL_IN_CREATE_VIEW", op, FALSE); |
| } |
| if (allowOrderByWasSet) |
| { |
| NAString op("OFF"); |
| ActiveSchemaDB()->getDefaults().validateAndInsert |
| ("ALLOW_ORDER_BY_IN_CREATE_VIEW", op, FALSE); |
| } |
| |
| if (NOT viewTree) { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // Remove the StmtQuery node. |
| // Clear the root flag in the RelRoot node since this not the topmost |
| // RelRoot in the query tree. |
| // |
| CMPASSERT(viewTree->getOperatorType() == STM_QUERY); |
| RelExpr *queryTree = viewTree->castToStatementExpr()->getQueryExpression(); |
| CMPASSERT(queryTree->getOperatorType() == REL_ROOT); |
| ((RelRoot *)queryTree)->setRootFlag(FALSE); |
| |
| CMPASSERT(queryTree->getChild(0)->getOperatorType() == REL_DDL); |
| StmtDDLCreateView *createViewTree = ((DDLExpr *)(queryTree->getChild(0)))-> |
| getDDLNode()->castToStmtDDLNode()->castToStmtDDLCreateView(); |
| CMPASSERT(createViewTree); |
| queryTree = createViewTree->getQueryExpression(); |
| CMPASSERT(queryTree->getOperatorType() == REL_ROOT); |
| ((RelRoot *)queryTree)->setRootFlag(FALSE); |
| |
| RelRoot *viewRoot = (RelRoot *)queryTree; // save for add'l binding below |
| ParNameLocList *saveNameLocList = bindWA->getNameLocListPtr(); |
| |
| // This was put here for Genesis 10-980217-0467. |
| // Now with the fix for 10-980408-5149, we even more strongly need to bypass |
| // or ignore any accessOpts from the view, for a consistent access model. |
| |
| if ((CmpCommon::getDefault(ALLOW_ISOLATION_LEVEL_IN_CREATE_VIEW) == DF_OFF) || |
| (viewRoot->accessOptions().accessType() == ACCESS_TYPE_NOT_SPECIFIED_)) |
| { |
| // if cqd is set and view options were explicitely specified, |
| // then do not overwrite it with accessOptions. |
| viewRoot->accessOptions() = accessOptions; |
| } |
| |
| // Set the WCO context (Genesis 10-971112-7028 + 10-990518-8420): |
| // If this view is WITH CHECK OPTION, then all views below it acquire |
| // check-option-ness, per Ansi 11.19 GR 9-11a |
| // (we implement only CASCADED -- see further notes later on in this func); |
| // if some view above this one is WCO, then this view effectively is too, |
| // regardless of its getViewCheck() value. |
| // Genesis 10-990518-8420 fix in particular: |
| // with-check-option views of the form |
| // SELECT..FROM(SELECT..WHERE p1)REN WHERE p2 |
| // were emitting a bind error on pred p1, and ignoring pred p2! |
| // |
| NABoolean topmostViewWithCheckOption = FALSE; |
| if (naTable->getViewCheck() && |
| bindWA->getCurrentScope()->context()->inUpdateOrInsert() && |
| !bindWA->inViewWithCheckOption()) { |
| topmostViewWithCheckOption = TRUE; |
| bindWA->inViewWithCheckOption() = naTable; |
| } |
| |
| // QSTUFF |
| // Give the new query tree the pubsub group attrs before |
| // binding, so that binder checks are applied to the new tree. |
| if ((groupAttrs) && |
| (groupAttrs->isEmbeddedUpdateOrDelete() || groupAttrs->isStream())) |
| propagateDeleteAndStream(queryTree,groupAttrs); |
| |
| // ************ THE FIRST OF TWO BINDNODE'S ************ |
| // Bind the basic queryTree first (before Rename), for stoi_ security stuff. |
| // Cascade the WCO-ness down to RelExpr::bindSelf which captures predicates. |
| // On this bind, unconditionally we never collect usages. |
| // |
| bindWA->viewCount()++; |
| |
| bindWA->setNameLocListPtr(NULL); // do not collect usages for catman |
| |
| queryTree = queryTree->bindNode(bindWA); |
| |
| if (bindWA->errStatus()) |
| return NULL; |
| bindWA->setNameLocListPtr(saveNameLocList); |
| |
| bindWA->viewCount()--; |
| |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| // if RelRoot has an order by, insert a Logical Sort node below it |
| // and move the order by expr from view root to this sort node. |
| // The view root node is eliminated during transformation/normalization |
| // and the sortlogical node provides a place to 'hold' the order by expr. |
| // During transformation, this sort key is moved from the sortlogical node |
| // to the root node of the query, if there is no explicit order by |
| // specified as part of the query. |
| // SortLogical node is a shortlived node and is eliminated during |
| // the normalization phase. |
| if (viewRoot->hasOrderBy()) |
| { |
| RelExpr * sortNode = new (bindWA->wHeap()) |
| SortLogical(queryTree->child(0)->castToRelExpr(), |
| viewRoot->reqdOrder(), |
| bindWA->wHeap()); |
| sortNode = sortNode->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| viewRoot->removeOrderByTree(); |
| viewRoot->reqdOrder().clear(); |
| |
| viewRoot->setChild(0, sortNode); |
| } |
| |
| // Insert a RenameTable node above the view tree. |
| // |
| const NAColumnArray &columns = naTable->getNAColumnArray(); |
| ItemExpr *columnList = new (bindWA->wHeap()) |
| RenameCol(NULL, new (bindWA->wHeap()) |
| ColRefName(columns[0]->getColName(), bindWA->wHeap())); |
| // |
| CollIndex i = 1; |
| for (i = 1; i < naTable->getColumnCount(); i++) |
| columnList = new (bindWA->wHeap()) |
| ItemList(columnList, new (bindWA->wHeap()) |
| RenameCol(NULL, new (bindWA->wHeap()) |
| ColRefName(columns[i]->getColName(), bindWA->wHeap()))); |
| // |
| |
| queryTree = new (bindWA->wHeap()) |
| RenameTable(TRUE/*copy tableName as is*/, |
| queryTree->castToRelExpr(), |
| viewName, |
| columnList, |
| bindWA->wHeap(), |
| TRUE/*isView*/); |
| if (predicate) queryTree->addSelPredTree(predicate); |
| ((RenameTable *) queryTree)->setViewNATable(naTable); |
| |
| // this query used this view |
| appendViewName |
| (viewName.getQualifiedNameObj().getQualifiedNameAsAnsiString().data()); |
| |
| // set a flag for overrride_schema |
| // with the call to bindNode below, only the Rename node will be bound. |
| // Since the view has already been expanded we reset the viewExpansion flag here. |
| //if (overrideSchemaEnabled()) |
| bindWA->getCurrentScope()->setInViewExpansion(inViewExpansion); |
| |
| // ************ THE SECOND OF TWO BINDNODE'S ************ |
| // Bind the view tree whose top is this new RenameTable. |
| // If we are the topmost WCO, then do NOT cascade the incoming predicate! |
| // Collect usages only if CatMan caller requested it. |
| // |
| if (topmostViewWithCheckOption) bindWA->inViewWithCheckOption() = NULL; |
| if (!catmanCollectUsages) bindWA->setNameLocListPtr(NULL); |
| queryTree = queryTree->bindNode(bindWA); |
| bindWA->setNameLocListPtr(saveNameLocList); |
| if (bindWA->errStatus()) return NULL; |
| |
| ((RenameTable *) queryTree)->setViewNATable(NULL); |
| |
| // Genesis 10-980126-5495: |
| // Now that we have the RenameTable's RETDesc, set its view column headings. |
| // We know that the NATable and the RenameTable column lists are in lockstep. |
| // |
| const ColumnDescList &columnsRET = *queryTree->getRETDesc()->getColumnList(); |
| CMPASSERT(columns.entries() == naTable->getColumnCount() && |
| columns.entries() == columnsRET.entries()); |
| for (i = 0; i < naTable->getColumnCount(); i++) |
| { |
| columnsRET[i]->setHeading(columns[i]->getHeading()); |
| } |
| |
| |
| // If it's a view that is WITH CHECK OPTION, and this is an UPDATE/INSERT, |
| // bind/transform/normalize the view predicate and place it as a constraint |
| // on the base table's TableDesc. This is equivalent to the default kind |
| // of check clause, WITH CASCADED CHECK OPTION, which is all we need provide |
| // up through Intermediate-Level SQL'92. |
| // |
| // (ANSI says that all CHECK OPTION views must be updatable (11.19 SR12) |
| // which means it references exactly one updatable view or, at bottom, |
| // exactly one base table (7.9 SR12). |
| // MP guarantees that all CHECK OPTION views must be protection views, and |
| // all pviews reference exactly one base table.) |
| // |
| // Notice that since (Genesis 10-990518-8420) we now bind and collect the |
| // view preds in bindSelf -- i.e. pushed down below here -- |
| // only this topmost WCO can set up the constraint(s). |
| // Thus we have lost the nice, but not mandated by Ansi, ability to specify |
| // which cascaded-down-to view causes which exact pred violation -- |
| // i.e. error EXE_CHECK_OPTION_VIOLATION_CASCADED (8104) |
| // no longer appears, only EXE_CHECK_OPTION_VIOLATION (8105). |
| |
| if (topmostViewWithCheckOption) { |
| |
| CheckConstraint *constraint = NULL; |
| ItemExpr *viewCheckPred = NULL; |
| |
| if (bindWA->predsOfViewWithCheckOption().entries()) { |
| constraint = new (bindWA->wHeap()) |
| CheckConstraint(viewName.getQualifiedNameObj(), // this view name |
| naTable->getTableName(), // no parsing needed |
| bindWA->wHeap()); |
| viewCheckPred = bindWA->predsOfViewWithCheckOption().rebuildExprTree(); |
| } |
| |
| // if at least one predicate exists in the view or what underlies it |
| if (constraint) { |
| |
| RelExpr *underlyingTableOrView = viewRoot->child(0); |
| RETDesc *saveRETDesc = bindWA->getCurrentScope()->getRETDesc(); |
| RETDesc *underlyingRETDesc = underlyingTableOrView->getRETDesc(); |
| bindWA->getCurrentScope()->setRETDesc(underlyingRETDesc); |
| CMPASSERT(underlyingTableOrView); |
| CMPASSERT(underlyingTableOrView->getOperatorType() == REL_RENAME_TABLE || |
| underlyingTableOrView->getOperatorType() == REL_SCAN); |
| |
| ItemExpr *constraintPred = |
| bindCheckConstraint(bindWA, |
| constraint, |
| naTable, |
| catmanCollectUsages, |
| viewCheckPred); |
| if (constraintPred) |
| queryTree->getScanNode()->getTableDesc()->addCheckConstraint( |
| bindWA, |
| naTable, // topmost WCO view |
| constraint, // this view name |
| constraintPred); |
| |
| bindWA->getCurrentScope()->setRETDesc(saveRETDesc); |
| |
| } // at least one predicate exists |
| |
| bindWA->inViewWithCheckOption() = NULL; |
| bindWA->predsOfViewWithCheckOption().clear(); |
| |
| } // topmost WCO view |
| |
| // QSTUFF |
| bindWA->setInViewExpansion(inViewExpansion); |
| bindWA->getUpdateToScanValueIds().clear(); |
| // QSTUFF |
| |
| return queryTree; |
| |
| } // BindWA::bindView() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class RelExpr |
| // ----------------------------------------------------------------------- |
| |
| void RelExpr::bindChildren(BindWA *bindWA) |
| { |
| // Increment the trigger recursion counter. |
| if (getInliningInfo().isTriggerRoot()) |
| getInliningInfo().getTriggerObject()->incRecursionCounter(); |
| |
| // TSJ's flow their data from left child to right child; |
| // some can also share binding scope column info from left to right. |
| Int32 arity = getArity(); |
| for (Int32 i = 0; i < arity; i++) { |
| if (child(i)) { |
| |
| // If doing a non-first child and the operator is |
| // NOT one in which values/names can flow from one scope |
| // the sibling scope, then we must clear the current RETDesc |
| // (so as to disallow the illegal query in the Binder internals document, |
| // section 1.5.3, also in TEST028). |
| // |
| if (i && !getOperator().match(REL_ANY_TSJ)) |
| bindWA->getCurrentScope()->setRETDesc(NULL); |
| |
| child(i) = child(i)->bindNode(bindWA); |
| if (bindWA->errStatus()) return; |
| } |
| } |
| |
| synthPropForBindChecks(); // QSTUFF |
| |
| // Decrement the trigger recursion counter. |
| if (getInliningInfo().isTriggerRoot()) |
| getInliningInfo().getTriggerObject()->decRecursionCounter(); |
| |
| } // RelExpr::bindChildren() |
| |
| void RelExpr::synthPropForBindChecks() // QSTUFF |
| { |
| // synthesis of delete and stream properties to |
| // allow for binder checks. We assume that all |
| // operators are rejected when binding the respective node |
| // -- except UNIONS -- in which more than one child has |
| // has any of those attributes. If both attributes are |
| // specified both must be specified for the same |
| // result-set/base table. |
| |
| for (Int32 j = 0; j < getArity(); j++) { |
| |
| if (child(j)) { |
| |
| if (child(j)->getGroupAttr()->isStream()) |
| { |
| getGroupAttr()->setStream(TRUE); |
| |
| if (child(j)->getGroupAttr()->isSkipInitialScan()) |
| getGroupAttr()->setSkipInitialScan(TRUE); |
| } |
| |
| if (child(j)->getGroupAttr()->isEmbeddedUpdateOrDelete() || |
| child(j)->getGroupAttr()->isEmbeddedInsert()) |
| getGroupAttr()->setEmbeddedIUD( |
| child(j)->getGroupAttr()->getEmbeddedIUD()); |
| |
| if (child(j)->getGroupAttr()->reorderNeeded()) |
| getGroupAttr()->setReorderNeeded(TRUE); |
| } |
| } |
| } |
| |
| RelExpr *RelExpr::bindSelf(BindWA *bindWA) |
| { |
| // create the group attributes |
| // |
| if (NOT getGroupAttr()) |
| setGroupAttr(new (bindWA->wHeap()) GroupAttributes); |
| |
| // |
| // Detach the item expression tree for the predicate, bind it, convert it to |
| // a ValueIdSet, and attach it to the RelExpr node. |
| // |
| ItemExpr *predTree = removeSelPredTree(); |
| if (predTree) { |
| bindWA->getCurrentScope()->context()->inWhereClause() = TRUE; |
| predTree->convertToValueIdSet(selectionPred(), bindWA, ITM_AND); |
| bindWA->getCurrentScope()->context()->inWhereClause() = FALSE; |
| |
| if (bindWA->errStatus()) return this; |
| |
| // If this is an embedded insert, then subquery predicates are not |
| // allowed. |
| // For example: To handle this query and issue an error stating |
| // subqueries are not allowed in embedded inserts |
| // |
| // select a from (insert into t901t01 values(22,22,222))t(a,b,c) |
| // where t.a IN (select m from t901t03 where t901t03.m = 77); |
| |
| if (getGroupAttr()->isEmbeddedInsert()) |
| { |
| if (!selectionPred().isEmpty() && selectionPred().containsSubquery()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4337); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // Genesis 10-990518-8420. |
| if (bindWA->inViewWithCheckOption()) |
| bindWA->predsOfViewWithCheckOption() += selectionPred(); |
| } |
| |
| // ++MV |
| // Bind the uniqueColumnsTree expression. |
| // |
| ItemExpr *uniqueColumnsTree = removeUniqueColumnsTree(); |
| if (uniqueColumnsTree) |
| { |
| uniqueColumnsTree-> |
| convertToValueIdSet(getUniqueColumns(), bindWA, ITM_ITEM_LIST); |
| if (bindWA->errStatus()) return this; |
| } |
| // --MV |
| |
| // set flag here if an Insert/Update/Delete operation is below this node |
| if( bindWA->isBindingIUD() ) |
| { |
| setSeenIUD(); |
| } |
| |
| // |
| // This mechanism is used to set InliningInfo flags on an entire subtree. |
| getInliningInfo().setFlags(bindWA->getInliningInfoFlagsToSetRecursivly()); |
| |
| // |
| // Add the values in the Outer References Set as the input values |
| // that must be supplied to this RelExpr. |
| // |
| getGroupAttr()->addCharacteristicInputs(bindWA->getCurrentScope()->getOuterRefs()); |
| markAsBound(); |
| return this; |
| } // RelExpr::bindSelf() |
| |
| RelExpr *RelExpr::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| return bindSelf(bindWA); |
| } |
| |
| RETDesc *RelExpr::getRETDesc() const |
| { |
| if (RETDesc_) |
| return RETDesc_; |
| if (getArity() == 1) |
| return child(0)->getRETDesc(); |
| else |
| return NULL; |
| } |
| |
| // When there is a view atop a view atop a ... atop a single base table, |
| // this will follow the chain of RenameTable-RelRoot-... down till it finds |
| // the bottom, the single base table's Scan node. |
| // |
| // This method does check to ensure exactly one single base table. |
| // |
| Scan *RelExpr::getScanNode(NABoolean assertExactlyOneScanNode) const |
| { |
| RelExpr *result = (RelExpr *)this; // cast away constness, big whoop |
| |
| while (result) { |
| if ((result->getOperatorType() == REL_SCAN) || |
| (result->getOperatorType() == REL_HBASE_ACCESS)) |
| break; |
| if (result->getArity() > 1) { |
| if (assertExactlyOneScanNode) |
| { |
| CMPASSERT(result->getArity() <= 1); |
| } |
| else return NULL; |
| } |
| result = result->child(0); |
| } |
| |
| if (assertExactlyOneScanNode) { CMPASSERT(result); } |
| return (Scan *)result; |
| } |
| |
| |
| Scan *RelExpr::getLeftmostScanNode() const |
| { |
| RelExpr *result = (RelExpr *)this; // cast away constness, big whoop |
| |
| while (result) { |
| if (result->getOperatorType() == REL_SCAN) break; |
| result = result->child(0); |
| } |
| |
| return (Scan *)result; |
| } |
| |
| |
| |
| Join * RelExpr::getLeftJoinChild() const |
| { |
| RelExpr *result = (RelExpr *)this; |
| |
| while(result) |
| { |
| if (result->getOperatorType() == REL_LEFT_JOIN) |
| break; |
| result = result->child(0); |
| } |
| return (Join *)result; |
| } |
| |
| RelSequence* RelExpr::getOlapChild() const |
| { |
| RelExpr *result = (RelExpr *)this; |
| |
| while(result) |
| { |
| if (result->getOperatorType() == REL_SEQUENCE) |
| break; |
| result = result->child(0); |
| } |
| return (RelSequence *)result; |
| } |
| |
| // QSTUFF |
| // We use this method for finding the scan node of an updatable view. |
| // This may either be a base table scan or a RenameTable node inserted |
| // by a previous index expansion. |
| RelExpr *RelExpr::getViewScanNode(NABoolean isTopLevelUpdateInView) const |
| { |
| RelExpr *result = (RelExpr *)this; // cast away constness, big whoop |
| |
| while (result) { |
| if (result->getOperatorType() == REL_SCAN) break; |
| |
| if (result->getOperatorType() == REL_RENAME_TABLE && |
| ((RenameTable *)result)->isView()) break; |
| |
| result = result->child(0); |
| } |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // getFirstIUDNode |
| // |
| // Return the first node that is an insert, update, or delete. |
| // Only search down left side from the starting point (currentNode) |
| // |
| // If an IUD node is not found, return NULL |
| // ----------------------------------------------------------------------- |
| GenericUpdate * Join::getFirstIUDNode(RelExpr *currentNode) |
| { |
| while(currentNode) |
| { |
| if( currentNode->getOperator().match(REL_ANY_GEN_UPDATE)) |
| { |
| break; |
| } |
| currentNode = currentNode->child(0); |
| } |
| return (GenericUpdate*)currentNode; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Join |
| // |
| // When we implement "JOIN USING (column list)", we need to: ## |
| // - disallow both NATURAL and USING in the same query (syntax err in Parser?) |
| // - ensure that the named USING cols are indeed common cols |
| // - coalesce common cols for USING just as we do for NATURAL, |
| // including ensuring that common cols are marked as referenced |
| // (as done in joinCommonColumns) |
| // ----------------------------------------------------------------------- |
| |
| RelExpr *Join::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // Do not support for general NEO users. |
| if ( (getOperatorType() == REL_FULL_JOIN) && |
| (CmpCommon::getDefault(COMP_BOOL_192) == DF_ON) ) { |
| |
| RelExpr *leftJoin = this; |
| leftJoin->setOperatorType(REL_LEFT_JOIN); |
| |
| Join *antiJoin = static_cast<Join *>(leftJoin->copyTree(bindWA->wHeap())); |
| antiJoin->setOperatorType(REL_RIGHT_JOIN); |
| |
| NAString leftName("ALJ", bindWA->wHeap()); |
| |
| // Make it unique. |
| // |
| leftName += bindWA->fabricateUniqueName(); |
| |
| RelExpr *rename = new (bindWA->wHeap()) |
| RenameTable(antiJoin, leftName); |
| |
| RelExpr *unionAll = new (bindWA->wHeap()) Union(leftJoin, rename); |
| |
| unionAll->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| ItemExpr *nullCheck = antiJoin->addNullInstIndicatorVar(bindWA).getItemExpr(); |
| |
| CMPASSERT(nullCheck); |
| |
| ItemExpr *filter = new (bindWA->wHeap()) |
| UnLogic(ITM_IS_NULL, nullCheck ); |
| |
| filter->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // Add filter to Join |
| // |
| antiJoin->selectionPred() += filter->getValueId(); |
| |
| return unionAll; |
| } |
| |
| Join *saveInJ = bindWA->getCurrentScope()->context()->inJoin(); |
| bindWA->getCurrentScope()->context()->inJoin() = this; |
| NABoolean savedPrivSetting = FALSE; |
| |
| // Bind the child nodes. |
| // |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // MV logging push-down |
| if( getInliningInfo().isDrivingMvLogInsert() ) |
| { |
| GenericUpdate *rightSideIUD = getFirstIUDNode(this->child(1)); |
| |
| if( NULL != rightSideIUD ) |
| { |
| TableDesc *tdesc = rightSideIUD->getTableDesc(); |
| |
| CMPASSERT(tdesc); |
| |
| const NATable *table = tdesc->getNATable(); |
| |
| // only for MV logs |
| if( ExtendedQualName::IUD_LOG_TABLE == table->getSpecialType() ) |
| { |
| updateTableDesc_ = tdesc; |
| updateSelectValueIdMap_ = new (bindWA->wHeap()) |
| ValueIdMap(rightSideIUD->updateToSelectMap()); |
| } |
| } |
| } |
| |
| |
| // Controlled availability of Full Outer Join support |
| // The COMP_BOOL_199 must be removed when full outer join |
| // becomes general availability. |
| |
| // Full outer joins are not currently supported. |
| // But can enabled by setting COMP_BOOL_199 to ON. |
| |
| if ((getOperatorType() == REL_FULL_JOIN && |
| (CmpCommon::getDefault(COMP_BOOL_199) == DF_OFF)) |
| || //OR |
| (getOperatorType() == REL_UNION_JOIN )){ |
| // 3022 Feature not yet supported |
| *CmpCommon::diags() << DgSqlCode(-3022) |
| << DgString0( |
| (getOperatorType() == REL_FULL_JOIN) ? |
| "FULL OUTER JOIN" : "UNION JOIN"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // |
| // Bind the ON clause of the join. |
| // |
| RelExpr *leftRelExpr = child(0).getPtr(); |
| RelExpr *rightRelExpr = child(1).getPtr(); |
| RETDesc *leftTable = child(0)->getRETDesc(); |
| RETDesc *rightTable = child(1)->getRETDesc(); |
| |
| ItemExpr *joinPredx; |
| if (isNaturalJoin()) { |
| // since the common column references need fetch histograms, the where |
| // flag is set here so that when we call markAsReferencedColumn() |
| // in the joinCommoncolumns() method it would set the common |
| // columns as refenced by looking a the inWhereCaluse_ flag. |
| NABoolean orig = bindWA->getCurrentScope()->context()->inWhereClause(); |
| bindWA->getCurrentScope()->context()->inWhereClause() = TRUE; |
| joinPredx = joinCommonColumns(leftRelExpr, rightRelExpr, bindWA); |
| bindWA->getCurrentScope()->context()->inWhereClause() = orig; |
| } |
| else |
| joinPredx = removeJoinPredTree(); |
| |
| if (joinPredx) { |
| ItemExpr *saveInJP = bindWA->getCurrentScope()->context()->inJoinPred(); |
| bindWA->getCurrentScope()->context()->inJoinPred() = joinPredx; |
| RETDesc preJoinResult; |
| preJoinResult.addColumns(bindWA, *leftTable); |
| preJoinResult.addColumns(bindWA, *rightTable); |
| bindWA->getCurrentScope()->setRETDesc(&preJoinResult); |
| joinPredx->convertToValueIdSet(joinPred(), bindWA, ITM_AND); |
| bindWA->getCurrentScope()->context()->inJoinPred() = saveInJP; |
| if (bindWA->errStatus()) return this; |
| } |
| // |
| // Create the output list. |
| // The TRUE's in the nullInstantiate() force a Cast expression to be set up, |
| // as required by the Normalizer. |
| // |
| NABoolean newTables = TRUE; |
| ValueIdList &nullOutputList = nullInstantiatedOutput(); |
| ValueIdList &nullOutputForRightJoinList = nullInstantiatedForRightJoinOutput(); |
| |
| switch(getOperatorType()) { |
| case REL_LEFT_JOIN: |
| leftTable = new (bindWA->wHeap()) RETDesc(bindWA, *leftTable); |
| rightTable = rightTable->nullInstantiate(bindWA, TRUE, nullOutputList); |
| break; |
| case REL_RIGHT_JOIN: |
| leftTable = leftTable->nullInstantiate(bindWA, TRUE, nullOutputList); |
| rightTable = new (bindWA->wHeap()) RETDesc(bindWA, *rightTable); |
| break; |
| case REL_FULL_JOIN: |
| case REL_UNION_JOIN: |
| { |
| leftTable = leftTable->nullInstantiate(bindWA, TRUE, nullOutputForRightJoinList); |
| |
| rightTable = rightTable->nullInstantiate(bindWA, TRUE, nullOutputList); |
| |
| |
| // comp_bool_198 = 'on' enables FullOuter transformation |
| // inner, left or right |
| if (CmpCommon::getDefault(COMP_BOOL_198) == DF_OFF) //don't enable FOJ transformation |
| { |
| ItemExpr * instNull = NULL; |
| CollIndex index = 0; |
| |
| // disable the FOJ Transformation. |
| for (index = 0; index < nullInstantiatedOutput().entries(); index++) |
| { |
| instNull = nullInstantiatedOutput()[index].getItemExpr(); |
| CMPASSERT(instNull->getOperatorType() == ITM_INSTANTIATE_NULL); |
| ((InstantiateNull *)instNull)->NoCheckforLeftToInnerJoin = TRUE; |
| } // endfor |
| |
| instNull = NULL; |
| |
| for (index = 0; |
| index < nullInstantiatedForRightJoinOutput().entries(); index++) |
| { |
| instNull = nullInstantiatedForRightJoinOutput()[index].getItemExpr(); |
| CMPASSERT(instNull->getOperatorType() == ITM_INSTANTIATE_NULL); |
| ((InstantiateNull *)instNull)->NoCheckforLeftToInnerJoin = TRUE; |
| } // endfor |
| } // env "ENABLE_FOJ_TRANSFORMATION" |
| break; |
| } |
| case REL_JOIN: |
| default: |
| newTables = FALSE; |
| break; |
| } |
| RETDesc *resultTable = new (bindWA->wHeap()) RETDesc(bindWA); |
| |
| Int32 rowSet = (child(0)->getOperatorType() == REL_RENAME_TABLE) && |
| (child(0)->child(0)->getOperatorType() == REL_UNPACKROWS) && |
| (child(1)->getOperatorType() == REL_ROOT); |
| |
| if (NOT isNaturalJoin()) { |
| if ((!rowSet) && |
| (getOperatorType() != REL_TSJ_FLOW)) { |
| resultTable->addColumns(bindWA, *leftTable); |
| } |
| // ++MV -- bug fixing for semi-joins |
| if (!isSemiJoin()) |
| { |
| resultTable->addColumns(bindWA, *rightTable); |
| } |
| // --MV -- bug fixing for semi-joins |
| } else { |
| coalesceCommonColumns(bindWA, |
| getOperatorType(), |
| *leftTable, |
| *rightTable, |
| *resultTable); |
| if (bindWA->errStatus()) return this; |
| } |
| setRETDesc(resultTable); |
| bindWA->getCurrentScope()->setRETDesc(resultTable); |
| |
| // QSTUFF |
| NAString fmtdList(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList(bindWA->wHeap()); |
| |
| bindWA->getTablesInScope(xtnmList, &fmtdList); |
| |
| if ((child(0)->getGroupAttr()->isStream()) && |
| (child(1)->getGroupAttr()->isStream())){ |
| bindWA->getTablesInScope(xtnmList, &fmtdList); |
| *CmpCommon::diags() << DgSqlCode(-4158) |
| << DgString0(fmtdList); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Disallowing joins for EMBEDDED...INSERT |
| // |
| if (getGroupAttr()->isEmbeddedInsert() && |
| !isTSJForWrite() // the tsjForWrite flag is set for |
| // those joins which are created by |
| // the Binder during inlining (eg. IndexMaintanence) |
| // Here we only want to disable user specified joins |
| // and not joins introduced as part of inlining. |
| ){ |
| *CmpCommon::diags() << DgSqlCode(-4336) |
| << DgString0(fmtdList) |
| << DgString1(getGroupAttr()->getOperationWithinGroup()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| |
| if ( ((child(0)->getGroupAttr()->isEmbeddedUpdateOrDelete()) && |
| (child(1)->getGroupAttr()->isEmbeddedUpdateOrDelete())) || |
| ((child(0)->getGroupAttr()->isEmbeddedInsert()) && |
| (child(1)->getGroupAttr()->isEmbeddedInsert())) || |
| (bindWA->isEmbeddedIUDStatement()) ) { |
| NAString type0,type1; |
| if (child(0)->getGroupAttr()->isEmbeddedUpdate()) |
| type0 = "UPDATE"; |
| else |
| { |
| if (child(0)->getGroupAttr()->isEmbeddedInsert()) |
| type0 = "INSERT"; |
| else |
| type0 = "DELETE"; |
| } |
| if (child(1)->getGroupAttr()->isEmbeddedUpdate()) |
| type1 = "UPDATE"; |
| else |
| { |
| if (child(1)->getGroupAttr()->isEmbeddedInsert()) |
| type1 = "INSERT"; |
| else |
| type1 = "DELETE"; |
| } |
| *CmpCommon::diags() << DgSqlCode(-4175) |
| << DgString0(fmtdList) |
| << DgString1(type0) |
| << DgString2(type1); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if ((child(0)->getGroupAttr()->isEmbeddedUpdateOrDelete() || |
| child(0)->getGroupAttr()->isStream()) && |
| (child(1)->getGroupAttr()->isEmbeddedUpdateOrDelete() || |
| child(1)->getGroupAttr()->isStream())){ |
| *CmpCommon::diags() << DgSqlCode(-4176) |
| << DgString0(fmtdList) |
| << (getGroupAttr()->isEmbeddedUpdate() ? |
| DgString1("UPDATE"):DgString1("DELETE")); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (getOperatorType() == REL_LEFT_JOIN){ |
| if (child(1)->getGroupAttr()->isEmbeddedUpdateOrDelete()){ |
| *CmpCommon::diags() << DgSqlCode(-4156) |
| << DgString0(fmtdList) |
| << (child(1)->getGroupAttr()->isEmbeddedUpdate() ? |
| DgString1("UPDATE"):DgString1("DELETE")); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (child(1)->getGroupAttr()->isStream()){ |
| *CmpCommon::diags() << DgSqlCode(-4157) |
| << DgString0(fmtdList); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| if (getOperatorType() == REL_RIGHT_JOIN){ |
| if (child(0)->getGroupAttr()->isEmbeddedUpdateOrDelete()){ |
| *CmpCommon::diags() << DgSqlCode(-4164) |
| << DgString0(fmtdList) |
| << (child(0)->getGroupAttr()->isEmbeddedUpdate() ? |
| DgString1("UPDATE"):DgString1("DELETE")); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (child(0)->getGroupAttr()->isStream()){ |
| *CmpCommon::diags() << DgSqlCode(-4165) |
| << DgString0(fmtdList); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // we need to move stream and nested updates to the |
| // left to ensure correct execution. This causes the statement |
| // to be rejected if the user specified join_order_by_user and |
| // the query must be reordered |
| |
| if (child(1)->getGroupAttr()->isStream() || |
| child(1)->getGroupAttr()->isEmbeddedUpdateOrDelete()){ |
| getGroupAttr()->setReorderNeeded(TRUE); |
| } |
| // QSTUFF |
| |
| if (newTables) { |
| delete leftTable; |
| delete rightTable; |
| } |
| |
| bindWA->getCurrentScope()->context()->inJoin() = saveInJ; |
| |
| if (getOperatorType() == REL_TSJ){ |
| //Using rowsets in a predicate with embedded update/delete results |
| //in a NestedJoin subtree after Normalization.This NestedJoin subtree |
| //has embedded update/delete as the right child, which is not allowed |
| //during optimization. Here we try to disallow this usage at Binding |
| //when a REL_TSJ subtree has rowsets as the left child and embedded |
| //update/delete as the right child. An error message[4123] is signaled. |
| if (rowSet && getGroupAttr()->isEmbeddedUpdateOrDelete()){ |
| *CmpCommon::diags() << DgSqlCode(-4213); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // transfer rowsetRowCountArraySize from HostArrayWA to this node. |
| if (bindWA->getHostArraysArea() && isRowsetIterator()) |
| setRowsetRowCountArraySize(bindWA->getHostArraysArea()->getRowsetRowCountArraySize()); |
| |
| // Bind the base class. |
| // |
| return bindSelf(bindWA); |
| } // Join::bindNode() |
| |
| ValueId Join::addNullInstIndicatorVar(BindWA *bindWA, |
| ItemExpr *indicatorVal) |
| { |
| // Add an indicator variable that can tell us whether |
| // a left join found a match in the right child table |
| // or not. The returned ValueId will have the value 1 |
| // if a match was found, and NULL if no match was found. |
| |
| ItemExpr *cval = indicatorVal; |
| |
| if (!cval) |
| cval = new (bindWA->wHeap()) SystemLiteral(1); |
| cval = cval->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL_VALUE_ID; |
| |
| // Null instantiate the value. |
| ValueId niCval = cval->getValueId().nullInstantiate(bindWA, TRUE); |
| |
| // Add it to the RETDesc of the Join. |
| ColRefName cvalName("", bindWA->wHeap()); |
| getRETDesc()->addColumn(bindWA, cvalName , niCval, USER_COLUMN); |
| |
| // Add it to the list of null instantiated outputs. |
| nullInstantiatedOutput().insert(niCval); |
| |
| return niCval; |
| } |
| |
| //++MV |
| // This function builds the BalueIdMap that is used for translating the required |
| // sort key to the right child sort key and backwards |
| void Join::BuildRightChildMapForLeftJoin() |
| { |
| ValueIdMap &map = rightChildMapForLeftJoin(); |
| |
| for (CollIndex j = 0; j < nullInstantiatedOutput().entries(); j++) |
| { |
| ValueId instNullId, rightChildId; |
| |
| instNullId = nullInstantiatedOutput_[j]; |
| |
| assert(instNullId.getItemExpr()->getOperatorType() == ITM_INSTANTIATE_NULL); |
| |
| // Access the operand of the InstantiateNull |
| rightChildId = (((InstantiateNull *)(instNullId.getItemExpr()))->getExpr()->getValueId()); |
| |
| map.addMapEntry(instNullId, rightChildId); |
| } |
| } |
| //--MV |
| |
| //++MV |
| // This function builds the ValueIdMap that is used for translating the |
| // required |
| // sort key to the left child sort key and backwards |
| void Join::BuildLeftChildMapForRightJoin() |
| { |
| ValueIdMap &map = leftChildMapForRightJoin(); |
| |
| for (CollIndex j = 0; j < nullInstantiatedForRightJoinOutput().entries(); j++) |
| { |
| ValueId instNullId, leftChildId; |
| |
| instNullId = nullInstantiatedForRightJoinOutput_[j]; |
| |
| assert(instNullId.getItemExpr()->getOperatorType() == ITM_INSTANTIATE_NULL); |
| |
| // Access the operand of the InstantiateNull |
| leftChildId = (((InstantiateNull *)(instNullId.getItemExpr()))->getExpr()->getValueId()); |
| |
| map.addMapEntry(instNullId, leftChildId); |
| } |
| } |
| //--MV |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Intersect |
| // ----------------------------------------------------------------------- |
| |
| // LCOV_EXCL_START - cnu |
| RelExpr *Intersect::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // Bind the child nodes. |
| // |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // Check that there are an equal number of select items on both sides. |
| // |
| const RETDesc &leftTable = *child(0)->getRETDesc(); |
| const RETDesc &rightTable = *child(1)->getRETDesc(); |
| if (leftTable.getDegree() != rightTable.getDegree()) { |
| // 4014 The operands of an intersect must be of equal degree. |
| *CmpCommon::diags() << DgSqlCode(-4014); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Join the columns of both sides. |
| // |
| if(CmpCommon::getDefault(MODE_SPECIAL_4) != DF_ON) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3022) // ## INTERSECT not yet supported , not fully tested |
| << DgString0("INTERSECT"); // ## |
| bindWA->setErrStatus(); // ## |
| if (bindWA->errStatus()) return NULL; // ## |
| } |
| // |
| ItemExpr *predicate = intersectColumns(leftTable, rightTable, bindWA); |
| RelExpr *join = new (bindWA->wHeap()) |
| Join(child(0)->castToRelExpr(), |
| child(1)->castToRelExpr(), |
| REL_JOIN, |
| predicate); |
| |
| // Bind the join. |
| // |
| join = join->bindNode(bindWA)->castToRelExpr(); |
| if (bindWA->errStatus()) return join; |
| |
| // Change the output of the join to just the left side. |
| // |
| delete join->getRETDesc(); |
| join->setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA, leftTable)); |
| bindWA->getCurrentScope()->setRETDesc(join->getRETDesc()); |
| |
| // QSTUFF |
| NAString fmtdList1(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList1(bindWA->wHeap()); |
| NAString fmtdList2(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList2(bindWA->wHeap()); |
| |
| leftTable.getTableList(xtnmList1, &fmtdList1); |
| rightTable.getTableList(xtnmList2, &fmtdList2); |
| |
| if (child(0)->getGroupAttr()->isStream() && |
| child(1)->getGroupAttr()->isStream()){ |
| *CmpCommon::diags() << DgSqlCode(-4159) |
| << DgString0(fmtdList1) << DgString1(fmtdList2); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Needs to be removed when supporting get_next for INTERSECT |
| if (getGroupAttr()->isEmbeddedUpdateOrDelete()) { |
| *CmpCommon::diags() << DgSqlCode(-4160) |
| << DgString0(fmtdList1) |
| << DgString1(fmtdList2) |
| << (child(0)->getGroupAttr()->isEmbeddedUpdate() ? |
| DgString2("UPDATE"):DgString2("DELETE")) |
| << (child(1)->getGroupAttr()->isEmbeddedUpdate() ? |
| DgString3("UPDATE"):DgString3("DELETE")); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // QSTUFF |
| |
| return join; |
| } // Intersect::bindNode() |
| // LCOV_EXCL_STOP |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Except |
| // ----------------------------------------------------------------------- |
| |
| // LCOV_EXCL_START - cnu |
| RelExpr *Except::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // Bind the child nodes. |
| // |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // Check that there are an equal number of select items on both sides. |
| // |
| const RETDesc &leftTable = *child(0)->getRETDesc(); |
| const RETDesc &rightTable = *child(1)->getRETDesc(); |
| if (leftTable.getDegree() != rightTable.getDegree()) { |
| // 4014 The operands of an intersect must be of equal degree. |
| *CmpCommon::diags() << DgSqlCode(-4014); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Join the columns of both sides. |
| // |
| if(CmpCommon::getDefault(MODE_SPECIAL_4) != DF_ON) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3022) // ## EXCEPT not yet supported: not fully tested |
| << DgString0("EXCEPT"); // ## |
| bindWA->setErrStatus(); // ## |
| if (bindWA->errStatus()) return NULL; // ## |
| } |
| // |
| ItemExpr *predicate = intersectColumns(leftTable, rightTable, bindWA); |
| RelExpr *join = new (bindWA->wHeap()) |
| Join(child(0)->castToRelExpr(), |
| child(1)->castToRelExpr(), |
| REL_ANTI_SEMIJOIN, |
| predicate); |
| |
| // Bind the join. |
| // |
| join = join->bindNode(bindWA)->castToRelExpr(); |
| if (bindWA->errStatus()) return join; |
| |
| // Change the output of the join to just the left side. |
| // |
| delete join->getRETDesc(); |
| join->setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA, leftTable)); |
| bindWA->getCurrentScope()->setRETDesc(join->getRETDesc()); |
| |
| // QSTUFF |
| NAString fmtdList1(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList1(bindWA->wHeap()); |
| NAString fmtdList2(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList2(bindWA->wHeap()); |
| |
| leftTable.getTableList(xtnmList1, &fmtdList1); |
| rightTable.getTableList(xtnmList2, &fmtdList2); |
| |
| if (child(0)->getGroupAttr()->isStream() && |
| child(1)->getGroupAttr()->isStream()){ |
| *CmpCommon::diags() << DgSqlCode(-4159) |
| << DgString0(fmtdList1) << DgString1(fmtdList2); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Needs to be removed when supporting get_next for EXCEPT |
| if (getGroupAttr()->isEmbeddedUpdateOrDelete()) { |
| *CmpCommon::diags() << DgSqlCode(-4160) |
| << DgString0(fmtdList1) |
| << DgString1(fmtdList2) |
| << (child(0)->getGroupAttr()->isEmbeddedUpdate() ? |
| DgString2("UPDATE"):DgString2("DELETE")) |
| << (child(1)->getGroupAttr()->isEmbeddedUpdate() ? |
| DgString3("UPDATE"):DgString3("DELETE")); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // QSTUFF |
| |
| return join; |
| } // Excpet::bindNode() |
| // LCOV_EXCL_STOP |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Union |
| // ----------------------------------------------------------------------- |
| |
| RelExpr *Union::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // |
| // Bind the conditional expression. |
| // |
| ItemExpr *condExprTree = removeCondExprTree(); |
| if (condExprTree) |
| { |
| condExprTree->convertToValueIdList(condExpr(), bindWA, ITM_ITEM_LIST); |
| if (bindWA->errStatus()) { |
| return NULL; |
| } |
| } |
| |
| // |
| // Bind the triggered action exception expression. |
| // |
| ItemExpr *trigExprTree = removeTrigExceptExprTree(); |
| if (trigExprTree) |
| { |
| // the assumption in the binder (in Union::addValueIdUnion) is that |
| // unionMap_ count is always less than or equal to one but triggers |
| // code might increment this number during binding because of |
| // recursive triggers or triggers that are used more than once |
| // in the statement. This check fixes the unionMap_ for triggers. |
| if ((unionMap_ != NULL) && (unionMap_->count_ > 1)) |
| { |
| unionMap_->count_--; |
| unionMap_ = new (CmpCommon::statementHeap()) UnionMap; |
| } |
| |
| trigExprTree->convertToValueIdList(trigExceptExpr(), bindWA, ITM_ITEM_LIST); |
| if (bindWA->errStatus()) { |
| return NULL; |
| } |
| } |
| |
| AssignmentStArea *assignArea = NULL; |
| // We store a pointer to this Union node in the assignment statements area. |
| // This is needed for compound statements project, in particular when we have |
| // assignment statements within an IF statement |
| if (getUnionForIF()) { |
| assignArea = bindWA->getAssignmentStArea(); |
| setPreviousIF(assignArea->getCurrentIF()); |
| assignArea->setCurrentIF(this); |
| } |
| |
| // |
| // Bind the child nodes. |
| // |
| bindWA->getCurrentScope()->context()->inUnion() = TRUE; |
| |
| currentChild() = 0; |
| child(0) = child(0)->bindNode(bindWA); |
| |
| if (bindWA->errStatus()) return this; |
| |
| // If we have assignment statements of compound statements, we need to get rid |
| // of the value ids generated while binding the first child. Also, we create a |
| // list of the value ids of the variables that are on the left side of a SET |
| // statement |
| if (getUnionForIF() && leftList() && assignArea) { |
| assignArea->removeLastValueIds(leftList(), this); |
| } |
| |
| if (getCondUnary()) { |
| CollIndex leftDegree = child(0)->getRETDesc()->getDegree(); |
| |
| ItemExpr *tupleExpr = new (bindWA->wHeap()) ConstValue(); |
| |
| for (CollIndex i=0; i+1<leftDegree; i++) { |
| ItemExpr *con = new (bindWA->wHeap()) ConstValue(); |
| |
| ItemList *list = new (bindWA->wHeap()) ItemList(con, tupleExpr); |
| |
| tupleExpr = list; |
| } |
| |
| RelExpr *tuple = new (bindWA->wHeap()) Tuple(tupleExpr); |
| |
| // create the selection predicate (1=0) for the Tuple node |
| ItemExpr *predicate = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, |
| new (bindWA->wHeap()) ConstValue(1), |
| new (bindWA->wHeap()) ConstValue(0)); |
| tuple->addSelPredTree(predicate); |
| |
| RelExpr *tupleRoot = new (bindWA->wHeap()) RelRoot(tuple); |
| |
| setChild (1, tupleRoot); |
| } |
| |
| if (child(1)) { |
| if (!(child(1)->getOperator().match(REL_ANY_TSJ))) { |
| bindWA->getCurrentScope()->setRETDesc(NULL); |
| } |
| |
| currentChild() = 1; |
| child(1) = child(1)->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // If we have assignment statements of compound statements, |
| // we need to get rid of the value ids generated while binding |
| // the second child |
| if (getUnionForIF() && rightList() && assignArea) { |
| assignArea->removeLastValueIds(rightList(), this); |
| } |
| } |
| |
| // check for & warn against UNIONs that have inconsistent access/lock modes. |
| // flag "select * from t1 union select * from t2 for <access> mode" |
| // with a warning that t1 and t2 may have inconsistent access/lock modes. |
| checkAccessLockModes(); |
| |
| //Copies the leftlist and rightlist this conditional union to the appropriate list of the |
| //conditional union node pointed to by the previousIF argument. |
| Union * previousIF = getPreviousIF(); |
| if (previousIF && getUnionForIF()) { |
| copyLeftRightListsToPreviousIF(previousIF, bindWA); |
| } |
| |
| synthPropForBindChecks(); |
| // QSTUFF |
| |
| bindWA->getCurrentScope()->context()->inUnion() = FALSE; |
| |
| // |
| // Check that there are an equal number of select items on both sides. |
| // |
| const RETDesc &leftTable = *child(0)->getRETDesc(); |
| const RETDesc &rightTable = *child(1)->getRETDesc(); |
| RETDesc *resultTable = NULL; |
| |
| RelRoot * root = bindWA->getTopRoot() ; |
| if (root) { |
| if (getGroupAttr()->isStream() && root->hasOrderBy()){ |
| NAString fmtdList1(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList1(bindWA->wHeap()); |
| NAString fmtdList2(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList2(bindWA->wHeap()); |
| leftTable.getTableList(xtnmList1, &fmtdList1); |
| rightTable.getTableList(xtnmList2, &fmtdList2); |
| *CmpCommon::diags() << DgSqlCode(-4166) |
| << DgString0(fmtdList1) |
| << DgString1(fmtdList2) ; |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| if (leftTable.getDegree() != rightTable.getDegree()) { |
| |
| #ifndef NDEBUG |
| dumpChildrensRETDescs(leftTable, rightTable); |
| #endif |
| |
| if ( (!getUnionForIF()) && |
| (!getCondUnary()) //for triggers |
| ) { |
| // 4126 The row-value-ctors of a VALUES must be of equal degree. |
| // 4066 The operands of a union must be of equal degree. |
| // This is not necessary if we are in an assignment stmt. |
| Lng32 sqlcode = bindWA->getCurrentScope()->context()->inTupleList() ? |
| -4126 : -4066; |
| *CmpCommon::diags() << DgSqlCode(sqlcode); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // |
| // For each select item on both sides, create a ValueIdUnion and insert its |
| // ValueId into the select list for the union. |
| // |
| |
| // We check to see if there were assignments on either side |
| if ( !getUnionForIF() ) { |
| resultTable = new (bindWA->wHeap()) RETDesc(bindWA); |
| for (CollIndex i = 0; i < leftTable.getDegree(); i++) { |
| ValueIdUnion *vidUnion = new (bindWA->wHeap()) |
| ValueIdUnion(leftTable.getValueId(i), |
| rightTable.getValueId(i), |
| NULL_VALUE_ID, |
| #pragma nowarn(1506) // warning elimination |
| getUnionFlags()); |
| #pragma warn(1506) // warning elimination |
| vidUnion->setIsTrueUnion(TRUE); |
| vidUnion->bindNode(bindWA); |
| if (bindWA->errStatus()) { |
| delete vidUnion; |
| delete resultTable; |
| return this; |
| } |
| ValueId valId = vidUnion->getValueId(); |
| addValueIdUnion(valId, bindWA->wHeap()); |
| resultTable->addColumn(bindWA, leftTable.getColRefNameObj(i), valId); |
| } |
| } |
| else { |
| // Case in which we have asignment statements below this node. |
| // We have to carefuly match the valueids in the IF and ELSE part. |
| // For instance, if SET :a = ... occurs in both branches or only in one. |
| if (getUnionForIF() && assignArea) { |
| resultTable = createReturnTable(assignArea, bindWA); |
| } |
| } |
| |
| |
| setRETDesc(resultTable); |
| bindWA->getCurrentScope()->setRETDesc(resultTable); |
| // |
| // Bind the base class. |
| // |
| |
| // We are done binding this node. The current IF node is now the closest |
| // IF node that is also an ancestor of this node |
| if (getUnionForIF() && assignArea) { |
| assignArea->setCurrentIF(getPreviousIF()); |
| } |
| |
| // QSTUFF |
| // this is not a hard restriction. Once the get_next protocol supports unions |
| // similar to the split-top operator, this check can be removed. |
| if (getGroupAttr()->isEmbeddedUpdateOrDelete() || |
| (getGroupAttr()->isEmbeddedInsert() && !isSystemGenerated_) || |
| (bindWA->isEmbeddedIUDStatement())) { |
| if (getUnionForIF()) { |
| *CmpCommon::diags() << DgSqlCode(-4210); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| NAString fmtdList1(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList1(bindWA->wHeap()); |
| NAString fmtdList2(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList2(bindWA->wHeap()); |
| |
| leftTable.getTableList(xtnmList1, &fmtdList1); |
| rightTable.getTableList(xtnmList2, &fmtdList2); |
| |
| // Fix for Solution 10-070117-1834. |
| // Error Message for -4161 - assumes that both sides |
| // of the UNION is an embedded operation. For a |
| // query such as, |
| // select * from (delete from t709t1)as x union all (select * from t709t1) |
| // the right side of the UNION is not an embedded operation. |
| // Hence, changing the text for 4161 to a more generic one so |
| // that all cases are covered in this one text message. |
| |
| |
| *CmpCommon::diags() << DgSqlCode(-4161) |
| << DgString0(fmtdList1) |
| << DgString1(fmtdList2); |
| |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // QSTUFF |
| |
| |
| // ++MV |
| // Bind the alternateRightChildOrderExprTree expression. |
| // |
| ItemExpr *alternateRightChildOrderExprTree = removeAlternateRightChildOrderExprTree(); |
| if (alternateRightChildOrderExprTree) |
| { |
| alternateRightChildOrderExprTree-> |
| convertToValueIdList(alternateRightChildOrderExpr(), bindWA, ITM_ITEM_LIST); |
| if (bindWA->errStatus()) { |
| return NULL; |
| } |
| } |
| // --MV |
| |
| // Bind the base class. |
| // |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) { |
| delete resultTable; |
| return boundExpr; |
| } |
| return boundExpr; |
| } // Union::bindNode() |
| |
| // check for & warn against UNIONs that have inconsistent access/lock modes |
| void Union::checkAccessLockModes() |
| { |
| Scan *left = child(0)->getAnyScanNode(); |
| Scan *right = child(1)->getAnyScanNode(); |
| |
| if (!left || !right) return; // no-op. |
| |
| // UNION is user-specified as opposed to system-generated (eg, by |
| // triggers/RI in GenericUpdate::inlinePipelineActions, etc) |
| if (isSystemGenerated_) { |
| return; |
| } |
| |
| Lng32 lockFlagSession = CmpCommon::transMode()->getDP2LockFlags().getValue(); |
| StmtLevelAccessOptions optionsLeft = left->accessOptions(); |
| StmtLevelAccessOptions optionsRight = right->accessOptions(); |
| |
| Lng32 lockFlagLeft = lockFlagSession; |
| Lng32 lockFlagRight = lockFlagSession; |
| |
| if (optionsLeft.userSpecified()) { |
| lockFlagLeft = optionsLeft.getDP2LockFlags().getValue(); |
| } |
| if (optionsRight.userSpecified()) { |
| lockFlagRight = optionsRight.getDP2LockFlags().getValue(); |
| } |
| if (lockFlagLeft != lockFlagRight) { |
| *CmpCommon::diags() |
| << DgSqlCode(3192) |
| << DgString0(left->getTableName().getQualifiedNameAsString()) |
| << DgString1(right->getTableName().getQualifiedNameAsString()); |
| } |
| } // Union::checkAccessLockModes() |
| |
| void Union::copyLeftRightListsToPreviousIF(Union * previousIF, BindWA * bindWA) |
| { |
| AssignmentStHostVars *thisLeftList = leftList(); |
| AssignmentStHostVars *thisRightList = rightList(); |
| |
| // If the previous IF node does not have a left list, we copy the left and right |
| // lists to that left list |
| if (previousIF->currentChild() == 0 && !(previousIF->leftList())) { |
| AssignmentStHostVars *leftListOfPreviousIF = previousIF->getCurrentList(bindWA); |
| // Copy the leftList of this node to the left list of the previous IF |
| leftListOfPreviousIF->addAllToListInIF(thisLeftList) ; |
| // Copy the rightList of this node to the left list of the previous IF |
| leftListOfPreviousIF->addAllToListInIF(thisRightList) ; |
| } |
| |
| // If the previous IF node does not have a right list, we copy the left and right |
| // lists to that left list |
| if (previousIF->currentChild() == 1 && !(previousIF->rightList())) { |
| AssignmentStHostVars *rightListOfPreviousIF = previousIF->getCurrentList(bindWA); |
| // Copy the leftList of this node to the right list of the previous IF |
| rightListOfPreviousIF->addAllToListInIF(thisLeftList) ; |
| // Copy the rightList of this node to the right list of the previous IF |
| rightListOfPreviousIF->addAllToListInIF(thisRightList) ; |
| } |
| } // Union::copyLeftRightListsToPreviousIF |
| |
| // ----------------------------------------------------------------------- |
| // MV -- |
| // A debugging method for dumping the columns in the RETDesc of both |
| // children when they do not match. |
| void Union::dumpChildrensRETDescs(const RETDesc& leftTable, |
| const RETDesc& rightTable) |
| { |
| // turn this code on when you need it by changing the #if below |
| #if 0 |
| // -- MVs. Debugging code !!!!! TBD |
| fprintf(stdout, " # Left Right\n"); |
| CollIndex maxIndex, minIndex; |
| NABoolean leftIsBigger; |
| if (leftTable.getDegree() > rightTable.getDegree()) |
| { |
| maxIndex = leftTable.getDegree(); |
| minIndex = rightTable.getDegree(); |
| leftIsBigger = TRUE; |
| } |
| else |
| { |
| maxIndex = rightTable.getDegree(); |
| minIndex = leftTable.getDegree(); |
| leftIsBigger = FALSE; |
| } |
| |
| for (CollIndex i=0; i<minIndex; i++) |
| { |
| ColumnDesc *leftColDesc = leftTable.getColumnList()->at(i); |
| ColumnDesc *rightColDesc = rightTable.getColumnList()->at(i); |
| NAString leftCol (leftColDesc->getColRefNameObj().getColRefAsString()); |
| NAString rightCol(rightColDesc->getColRefNameObj().getColRefAsString()); |
| fprintf(stdout, " %3d %-55s %-55s \n", |
| i, leftCol.data(), rightCol.data()); |
| } |
| |
| if (leftIsBigger) |
| { |
| for (CollIndex j=minIndex; j<maxIndex; j++) |
| { |
| ColumnDesc *leftColDesc = leftTable.getColumnList()->at(j); |
| NAString leftCol(leftColDesc->getColRefNameObj().getColRefAsString()); |
| fprintf(stdout, " %3d %-35s\n", |
| j, leftCol.data()); |
| } |
| } |
| else |
| { |
| for (CollIndex k=minIndex; k<maxIndex; k++) |
| { |
| ColumnDesc *rightColDesc = rightTable.getColumnList()->at(k); |
| NAString rightCol(rightColDesc->getColRefNameObj().getColRefAsString()); |
| fprintf(stdout, " %3d %-35s \n", |
| k, rightCol.data()); |
| } |
| } |
| #endif |
| } |
| |
| |
| // ---------------------------------------------------------------------- |
| // static helper functions for classes RelRoot and GroupByAgg |
| // ---------------------------------------------------------------------- |
| |
| static NABoolean containsGenericUpdate(const RelExpr *re) |
| { |
| if (re->getOperator().match(REL_ANY_GEN_UPDATE)) return TRUE; |
| for (Int32 i = 0; i < re->getArity(); ++i ) { |
| if (re->child(i) && containsGenericUpdate(re->child(i))) return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static NABoolean containsUpdateOrDelete(const RelExpr *re) |
| { |
| if (re->getOperator().match(REL_ANY_UPDATE_DELETE)) |
| return TRUE; |
| for (Int32 i = 0; i < re->getArity(); ++i ) { |
| if (re->child(i) && containsUpdateOrDelete(re->child(i))) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| // QSTUFF |
| |
| static GenericUpdate *getGenericUpdate(RelExpr *re) |
| { |
| if (re) { |
| if (re->getOperatorType() == REL_UNARY_UPDATE || |
| re->getOperatorType() == REL_UNARY_DELETE) |
| return (GenericUpdate *)re; |
| |
| for (Int32 i = 0; i < re->getArity(); ++i) { // check all children (both sides) |
| GenericUpdate *gu = getGenericUpdate(re->child(i)); |
| if (gu) return gu; |
| } |
| } |
| return NULL; |
| } |
| |
| static NABoolean checkUnresolvedAggregates(BindWA *bindWA) |
| { |
| const ValueIdSet &aggs = bindWA->getCurrentScope()->getUnresolvedAggregates(); |
| if (aggs.isEmpty()) return FALSE; // no error |
| |
| NAString unparsed(bindWA->wHeap()); |
| for (ValueId vid = aggs.init(); aggs.next(vid); aggs.advance(vid)) { |
| |
| const ItemExpr *ie = vid.getItemExpr(); |
| CMPASSERT(ie->isAnAggregate()); |
| Aggregate *agg = (Aggregate *)ie; |
| |
| // Don't display COUNT() part of SUM()/COUNTxxx(), our implementation of AVG() |
| // Display only the COUNT_NONULL() our implementation of VARIANCE and STDDEV |
| // This is to avoid printing the aggregate functions more than once. |
| |
| if((agg->origOpType() != ITM_AVG || agg->getOperatorType() == ITM_SUM) && |
| (!(agg->origOpType() == ITM_STDDEV || agg->origOpType() == ITM_VARIANCE) |
| || agg->getOperatorType() == ITM_COUNT_NONULL)){ |
| |
| unparsed += ", "; |
| if (agg->origOpType() == ITM_COUNT_STAR__ORIGINALLY) |
| unparsed += "COUNT(*)"; |
| else |
| agg->unparse(unparsed, DEFAULT_PHASE, USER_FORMAT_DELUXE); |
| } |
| } |
| unparsed.remove(0,2); // remove initial ", " |
| |
| // 4015 Aggregate functions placed incorrectly. |
| *CmpCommon::diags() << DgSqlCode(-4015) << DgString0(unparsed); |
| bindWA->setErrStatus(); |
| return TRUE; |
| } // checkUnresolvedAggregates() |
| |
| // ---------------------------------------------------------------------- |
| // member functions for class RelRoot |
| // ---------------------------------------------------------------------- |
| |
| |
| static NABoolean isRenamedColInSelList(BindWA * bindWA, ItemExpr * col, |
| ItemExprList &origSelectList, |
| CollIndex &indx, |
| RETDesc * childRETDesc) |
| { |
| if (col->getOperatorType() != ITM_REFERENCE) |
| return FALSE; |
| |
| ColReference * havingColReference = (ColReference*)col; |
| |
| CollIndex j = 0; |
| |
| NABoolean found = FALSE; |
| while (j < origSelectList.entries()) |
| { |
| ItemExpr * selectListEntry = origSelectList[j]; |
| |
| if (selectListEntry->getOperatorType() == ITM_RENAME_COL) |
| { |
| const ColRefName &selectListColRefName = |
| *((RenameCol *)selectListEntry)->getNewColRefName(); |
| |
| if (havingColReference->getColRefNameObj() == selectListColRefName) |
| { |
| if (found) |
| { |
| // multiple entries with the same name. Error. |
| *CmpCommon::diags() << DgSqlCode(-4195) |
| << DgString0(selectListColRefName.getColName()); |
| bindWA->setErrStatus(); |
| return FALSE; |
| } |
| |
| ColumnNameMap *baseColExpr = NULL; |
| if (childRETDesc) |
| baseColExpr = childRETDesc->findColumn(selectListColRefName); |
| if ( NOT baseColExpr) |
| { |
| found = TRUE; |
| indx = j; |
| } |
| } |
| } // rename col |
| |
| j++; |
| } // while |
| return found; |
| } |
| |
| static short replaceRenamedColInHavingWithSelIndex( |
| BindWA * bindWA, |
| ItemExpr * expr, |
| ItemExprList &origSelectList, |
| NABoolean &replaced, |
| NABoolean ¬AllowedWithSelIndexInHaving, |
| RETDesc * childRETDesc) |
| { |
| if (((expr->getOperatorType() >= ITM_ROW_SUBQUERY) && |
| (expr->getOperatorType() <= ITM_GREATER_EQ_ANY)) || |
| ((expr->getOperatorType() >= ITM_AVG) && |
| (expr->getOperatorType() <= ITM_VARIANCE)) || |
| ((expr->getOperatorType() >= ITM_DIFF1) && |
| (expr->getOperatorType() <= ITM_NOT_THIS))) |
| { |
| notAllowedWithSelIndexInHaving = TRUE; |
| return 0; |
| } |
| |
| for (Int32 i = 0; i < expr->getArity(); i++) |
| { |
| CollIndex j = 0; |
| if (isRenamedColInSelList(bindWA, expr->child(i), origSelectList, |
| j, childRETDesc)) |
| { |
| SelIndex * selIndex = new(bindWA->wHeap()) SelIndex(j+1); |
| expr->setChild(i, selIndex); |
| replaced = TRUE; |
| } |
| else if (bindWA->errStatus()) |
| return -1; |
| else if (replaceRenamedColInHavingWithSelIndex( |
| bindWA, expr->child(i), origSelectList, replaced, |
| notAllowedWithSelIndexInHaving, childRETDesc)) |
| return -1; |
| } |
| return 0; |
| } |
| |
| static short setValueIdForRenamedColsInHaving(BindWA * bindWA, |
| ItemExpr * expr, |
| ValueIdList &compExpr) |
| { |
| if (((expr->getOperatorType() >= ITM_ROW_SUBQUERY) && |
| (expr->getOperatorType() <= ITM_GREATER_EQ_ANY)) || |
| ((expr->getOperatorType() >= ITM_AVG) && |
| (expr->getOperatorType() <= ITM_VARIANCE)) || |
| ((expr->getOperatorType() >= ITM_DIFF1) && |
| (expr->getOperatorType() <= ITM_NOT_THIS))) |
| { |
| return 0; |
| } |
| |
| for (Int32 i = 0; i < expr->getArity(); i++) |
| { |
| if (expr->child(i)->getOperatorType() == ITM_SEL_INDEX) |
| { |
| SelIndex * si = (SelIndex*)expr->child(i)->castToItemExpr(); |
| si->setValueId(compExpr[si->getSelIndex()-1]); |
| } |
| else |
| setValueIdForRenamedColsInHaving(bindWA, expr->child(i), compExpr); |
| } |
| return 0; |
| } |
| |
| // Method to update the selIndecies after we have gone through a |
| // selectList expansion due to MVFs or Subqueries with degree > 1 |
| // used to update the orderByTree |
| // |
| // Returns a list of SelIndecies that were updated. |
| static void fixUpSelectIndecies(ItemExpr * expr, ValueIdSet &updatedIndecies, |
| CollIndex idx, CollIndex offset) |
| { |
| |
| if (expr == NULL ) return; |
| |
| for (Int32 i = 0; i < expr->getArity(); i++) |
| { |
| // Only update ones that we haven't already done. |
| if ((expr->child(i)->getOperatorType() == ITM_SEL_INDEX) && |
| !updatedIndecies.contains(expr->child(i)->getValueId())) |
| { |
| SelIndex * si = (SelIndex*)expr->child(i)->castToItemExpr(); |
| if (si->getSelIndex() > idx) |
| { |
| si->setSelIndex(si->getSelIndex() + offset); |
| updatedIndecies += si->getValueId(); |
| } |
| } |
| else |
| fixUpSelectIndecies(expr->child(i), updatedIndecies, idx, offset); |
| } |
| |
| // Now check myself.. |
| // Only update ones that we haven't already done. |
| if ((expr->getOperatorType() == ITM_SEL_INDEX) && |
| !updatedIndecies.contains(expr->getValueId())) |
| { |
| SelIndex * si = (SelIndex*)expr->castToItemExpr(); |
| if (si->getSelIndex() > idx) |
| { |
| si->setSelIndex(si->getSelIndex() + offset); |
| updatedIndecies += si->getValueId(); |
| } |
| } |
| } |
| |
| // Method to update the selIndecies after we have gone through a |
| // selectList expansion due to MVFs or Subqueries with degree > 1 |
| // used to update the GroupByList |
| // |
| // Returns a list of SelIndecies that were updated. |
| static void fixUpSelectIndeciesInSet(ValueIdSet & expr, |
| ValueIdSet &updatedIndecies, |
| CollIndex idx, |
| CollIndex offset) |
| { |
| |
| for (ValueId vid = expr.init(); expr.next(vid); expr.advance(vid)) |
| { |
| // Only update ones that we haven't already done. |
| if (((ItemExpr *)vid.getItemExpr())->getOperatorType() == ITM_SEL_INDEX && |
| !updatedIndecies.contains(vid)) |
| { |
| SelIndex * si = (SelIndex*) vid.getItemExpr(); |
| if (si->getSelIndex() > idx) |
| { |
| si->setSelIndex(si->getSelIndex() + offset); |
| updatedIndecies += si->getValueId(); |
| } |
| } |
| } |
| } |
| |
| RelRoot * RelRoot::transformOrderByWithExpr(BindWA *bindWA) |
| { |
| NABoolean specialMode = (CmpCommon::getDefault(GROUP_OR_ORDER_BY_EXPR) == DF_ON); |
| if (NOT specialMode) |
| return this; |
| ItemExprList origSelectList(bindWA->wHeap()); |
| ItemExprList origOrderByList(bindWA->wHeap()); |
| |
| CollIndex origSelectListCount ; |
| if ((getCompExprTree() == NULL) && |
| (child(0)->getOperatorType() != REL_GROUPBY)) |
| { |
| return this; |
| } |
| |
| ItemExpr *orderByTree = getOrderByTree(); |
| if (!orderByTree) |
| return this; |
| |
| if (orderByTree) |
| { |
| origOrderByList.insertTree(orderByTree); |
| } |
| |
| if (getCompExprTree()) |
| origSelectList.insertTree(getCompExprTree()); |
| else if (child(0)->getOperatorType() == REL_GROUPBY) |
| { |
| // this is the case: select distinct <expr> from t order by <expr> |
| GroupByAgg * grby = (GroupByAgg *)(child(0)->castToRelExpr()); |
| if (grby->child(0) && grby->child(0)->getOperatorType() == REL_ROOT) |
| { |
| RelRoot * selRoot = (RelRoot*)grby->child(0)->castToRelExpr(); |
| if (selRoot->getCompExprTree()) |
| origSelectList.insertTree(selRoot->getCompExprTree()); |
| } |
| } |
| |
| Lng32 selListCount = origSelectList.entries(); |
| |
| // if there is an expression in the order by list and this expression matches |
| // a select list expression, then replace it with the index of that select list item. |
| ItemExprList newOrderByList((Lng32)origOrderByList.entries(), bindWA->wHeap()); |
| NABoolean orderByExprFound = FALSE; |
| for (Lng32 i = 0; i < origOrderByList.entries(); i++) |
| { |
| ItemExpr * currOrderByItemExpr = origOrderByList[i]; |
| |
| NABoolean isDesc = FALSE; |
| if (currOrderByItemExpr->getOperatorType() == ITM_INVERSE) |
| { |
| currOrderByItemExpr = currOrderByItemExpr->child(0)->castToItemExpr(); |
| isDesc = TRUE; |
| } |
| |
| if (NOT ((currOrderByItemExpr->getOperatorType() == ITM_SEL_INDEX) || |
| (currOrderByItemExpr->getOperatorType() == ITM_REFERENCE) || |
| (currOrderByItemExpr->getOperatorType() == ITM_CONSTANT))) |
| { |
| NABoolean found = FALSE; |
| Lng32 selListIndex = 0; |
| ItemExpr * selItem = NULL; |
| ItemExpr * renameColEntry = NULL; |
| while ((NOT found) && (selListIndex < selListCount)) |
| { |
| selItem = origSelectList[selListIndex]; |
| |
| if (selItem->getOperatorType() == ITM_RENAME_COL) |
| { |
| renameColEntry = selItem; |
| selItem = selItem->child(0); |
| } |
| |
| found = currOrderByItemExpr->duplicateMatch(*selItem); |
| if (NOT found) |
| selListIndex++; |
| } |
| |
| if (NOT found) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4197) |
| << DgString0("ORDER BY"); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| selItem->setInOrderByOrdinal(TRUE); |
| currOrderByItemExpr = new(bindWA->wHeap()) SelIndex(selListIndex+1); |
| if (isDesc) |
| { |
| currOrderByItemExpr = new(bindWA->wHeap()) InverseOrder(currOrderByItemExpr); |
| } |
| |
| orderByExprFound = TRUE; |
| } // if order by expr |
| |
| newOrderByList.insert(currOrderByItemExpr); |
| } |
| |
| if ((orderByExprFound) && |
| (newOrderByList.entries() > 0)) |
| { |
| removeOrderByTree(); |
| addOrderByTree(newOrderByList.convertToItemExpr()); |
| } |
| |
| return this; |
| } |
| |
| ////////////////////////////////////////////////////////////////////// |
| // GROUPING functions returns a 1 or 0 depending on whether a null |
| // value was moved as a rollup group or not. |
| // |
| // GROUPING_ID(a,b,c) returns a value corresponding to the bit vector |
| // where each bit entry represents the GROUPING result for the argument |
| // of GROUPING_ID function. |
| // |
| // For ex: GROUPING_ID(a,b,c) will have 3 bit entries, |
| // and is equivalent to: |
| // GROUPING(a)*4 + GROUPING(b)*2 + GROUPING(c)*1 |
| ////////////////////////////////////////////////////////////////////// |
| ItemExpr * RelRoot::processGroupingID(ItemExpr * ie, BindWA *bindWA) |
| { |
| if (ie->getOperatorType() != ITM_GROUPING_ID) |
| return ie; |
| |
| ItemExpr * groupingIdExpr = NULL; |
| |
| ItemExprList childExprList(bindWA->wHeap()); |
| childExprList.insertTree(ie->child(0)->castToItemExpr()); |
| |
| Int64 multiplier = (Int64)pow(2, (childExprList.entries()-1)); |
| SQLLargeInt * li = |
| new(bindWA->wHeap()) SQLLargeInt(FALSE, FALSE); // +ve value, no nulls |
| for (CollIndex i = 0; i < (CollIndex)childExprList.entries(); i++) |
| { |
| ItemExpr * currChildIE = |
| ((ItemExpr *) childExprList[i])->castToItemExpr(); |
| |
| ItemExpr * groupingClause = |
| new(bindWA->wHeap()) Aggregate(ITM_GROUPING, currChildIE, FALSE); |
| |
| ItemExpr * multiplierClause = new(bindWA->wHeap()) |
| ConstValue(li, (void*)&multiplier, sizeof(Int64)); |
| ItemExpr * groupingExpr = new(bindWA->wHeap()) |
| BiArith(ITM_TIMES, groupingClause, multiplierClause); |
| |
| if (i == 0) |
| { |
| groupingIdExpr = groupingExpr; |
| } |
| else |
| { |
| groupingIdExpr = new(bindWA->wHeap()) |
| BiArith(ITM_PLUS, groupingIdExpr, groupingExpr); |
| } |
| |
| multiplier = multiplier / 2; |
| } |
| |
| groupingIdExpr = new(bindWA->wHeap()) Cast(groupingIdExpr, li); |
| |
| return groupingIdExpr; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////// |
| // |
| // This methods performs the following in this order: |
| // |
| // If groupby name refers to a renamed col name in the select list, |
| // replace group by entry with ordinal position of that sel list entry. |
| // |
| // If groupby ordinal exceeds the number of select list elements, |
| // return error. |
| // |
| // If groupby ordinal referes to a '*', return error. |
| // |
| // If groupby ordinal refers to a column(ITM_REFERENCE) or a renamed |
| // col name(ITM_RENAME_COL) whose child is a column(ITM_REFERENCE), |
| // replace ordinal with actual col name. |
| // |
| // If there are ordinals in group by list, mark RelRoot indicating |
| // phase2 transformation is needed. |
| // |
| // Mark all select list item exprs which are referened as an ordinal to |
| // indicate that groupby check to validate grouping columns is not needed |
| // for the subtree rooted below that select list item. |
| // |
| /////////////////////////////////////////////////////////////////////////// |
| RelRoot * RelRoot::transformGroupByWithOrdinalPhase1(BindWA *bindWA) |
| { |
| NABoolean specialMode = |
| (CmpCommon::getDefault(MODE_SPECIAL_1) == DF_ON); |
| |
| // make sure child of root is a groupby node.or a sequence node |
| // whose child is a group by node |
| if (child(0)->getOperatorType() != REL_GROUPBY && |
| (child(0)->getOperatorType() != REL_SEQUENCE || |
| (child(0)->child(0) && child(0)->child(0)->getOperatorType()!=REL_GROUPBY))) |
| return this; |
| |
| NABoolean compExprTreeIsNull = FALSE; |
| CollIndex origSelectListCount ; |
| if (getCompExprTree() == NULL) |
| { |
| compExprTreeIsNull = TRUE; |
| origSelectListCount = 0; |
| // return this; |
| } |
| |
| GroupByAgg * grby; |
| |
| if (child(0)->getOperatorType() == REL_GROUPBY) |
| { |
| grby = (GroupByAgg *)(child(0)->castToRelExpr()); |
| } |
| else |
| {// sequence node above group by |
| grby = (GroupByAgg *)(child(0)->child(0)->castToRelExpr()); |
| } |
| |
| DCMPASSERT(grby != NULL); |
| |
| if ((NOT specialMode) && |
| (grby->getGroupExprTree() == NULL)) |
| return this; |
| |
| ItemExpr * groupExprTree = grby->getGroupExprTree(); |
| ItemExprList origSelectList(bindWA->wHeap()); |
| ItemExprList origGrbyList(bindWA->wHeap()); |
| |
| if (groupExprTree) |
| { |
| origGrbyList.insertTree(groupExprTree); |
| } |
| |
| if (NOT compExprTreeIsNull) |
| { |
| // expand GROUPING_ID in terms of GROUPING aggregates |
| if (grby->isRollup()) |
| { |
| NABoolean groupingIDfound = FALSE; |
| |
| ItemExprList selList(getCompExprTree(), bindWA->wHeap()); |
| ItemExprList newSelList(bindWA->wHeap()); |
| for (CollIndex ii = 0; ii < selList.entries(); ii++) |
| { |
| ItemExpr * ie = selList[ii]; |
| if (ie->getOperatorType() == ITM_GROUPING_ID) |
| { |
| ItemExpr * newIE = processGroupingID(ie, bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| groupingIDfound = TRUE; |
| newSelList.insert(newIE); |
| } |
| else |
| newSelList.insert(ie); |
| } // for |
| |
| if (groupingIDfound) |
| { |
| ItemExpr * newCompExprTree = newSelList.convertToItemExpr(); |
| removeCompExprTree(); |
| addCompExprTree(newCompExprTree); |
| } |
| } |
| |
| origSelectList.insertTree(getCompExprTree()); |
| origSelectListCount = origSelectList.entries(); |
| } |
| |
| ItemExprList newGroupByList((Lng32)origGrbyList.entries(), bindWA->wHeap()); |
| |
| NABoolean foundSelIndex = FALSE; |
| |
| NABoolean lookForRenamedCols = TRUE; |
| if ((CmpCommon::getDefault(GROUP_OR_ORDER_BY_EXPR) == DF_OFF) && |
| (NOT specialMode)) |
| lookForRenamedCols = FALSE; |
| |
| NABoolean lookForExprInGroupByClause = TRUE; |
| if (CmpCommon::getDefault(COMP_BOOL_92) == DF_ON) |
| lookForExprInGroupByClause = FALSE; |
| |
| // See if UDF_SUBQ_IN_AGGS_AND_GBYS is enabled. It is enabled if the |
| // default is ON, or if the default is SYSTEM and ALLOW_UDF is ON. |
| NABoolean udfSubqInAggGrby_Enabled = FALSE; |
| DefaultToken udfSubqTok = CmpCommon::getDefault(UDF_SUBQ_IN_AGGS_AND_GBYS); |
| if ((udfSubqTok == DF_ON) || |
| (udfSubqTok == DF_SYSTEM)) |
| udfSubqInAggGrby_Enabled = TRUE; |
| |
| // This list will store duplicate expression specified in select list and |
| // GroupBy clause. It helps with specifying select Index as well as |
| // mark InGroupByOrdinal flag correctly (Gen Sol:10-100129-7836) |
| NAList<CollIndex> listOfExpressions(CmpCommon::statementHeap()); |
| |
| for (CollIndex i = 0; (i < (CollIndex) origGrbyList.entries());i++) |
| { |
| ItemExpr * currGroupByItemExpr = |
| ((ItemExpr *) origGrbyList[i])->castToItemExpr(); |
| ItemExpr * newGroupByItemExpr = NULL; |
| |
| NABoolean selIndexError = FALSE; |
| Int64 selIndex = -1; |
| if (currGroupByItemExpr->getOperatorType() == ITM_CONSTANT) |
| { |
| ConstValue * cv = (ConstValue*)currGroupByItemExpr; |
| if ((cv->canGetExactNumericValue()) && |
| (cv->getType()->getScale() == 0)) |
| { |
| selIndex = cv->getExactNumericValue(); |
| if ((selIndex >= 0) && (selIndex < MAX_COMSINT32)) |
| { |
| if (selIndex == 0 || selIndex > origSelectListCount) |
| { |
| // remember that this select index is in error. |
| // Look for this constant in the select list. |
| // If it is not found, then this const will be |
| // treated as a select index and an error will |
| // returned. If it is found in the select list, |
| // then it will be treated as a group by expression. |
| selIndexError = TRUE; |
| } |
| else |
| currGroupByItemExpr = |
| new(bindWA->wHeap()) SelIndex((Lng32)selIndex); |
| } |
| } |
| } |
| |
| NABoolean found = FALSE; |
| if ((currGroupByItemExpr->getOperatorType() != ITM_REFERENCE) && |
| (currGroupByItemExpr->getOperatorType() != ITM_SEL_INDEX) && |
| (lookForExprInGroupByClause)) |
| { |
| Int32 selListIndex = -1, lastMatch = -1; |
| CollIndex j = 0; |
| while ((NOT found) && (j < origSelectListCount)) |
| { |
| ItemExpr * selectListEntry = origSelectList[j]; |
| |
| if ((selectListEntry->getOperatorType() != ITM_REFERENCE) && |
| ((selectListEntry->getOperatorType() != ITM_RENAME_COL) || |
| ((selectListEntry->child(0)) && |
| (selectListEntry->child(0)->getOperatorType() != ITM_REFERENCE)))) |
| { |
| ItemExpr * renameColEntry = NULL; |
| if (selectListEntry->getOperatorType() == ITM_RENAME_COL) |
| { |
| renameColEntry = selectListEntry; |
| selectListEntry = selectListEntry->child(0); |
| } |
| |
| found = |
| currGroupByItemExpr->duplicateMatch(*selectListEntry); |
| if (found) |
| { |
| lastMatch = j; |
| if(!listOfExpressions.contains(j)) |
| { |
| selListIndex = j; |
| listOfExpressions.insert(j); |
| |
| selectListEntry->setInGroupByOrdinal(TRUE); |
| selectListEntry->setIsGroupByExpr(TRUE); |
| |
| if (renameColEntry) |
| renameColEntry->setInGroupByOrdinal(TRUE); |
| } |
| else |
| found = FALSE; |
| } |
| } |
| j++; |
| } // while |
| |
| if(lastMatch != -1) |
| { |
| found = TRUE; |
| |
| if(selListIndex == -1) |
| selListIndex = lastMatch; |
| |
| if (bindWA->inViewDefinition()) |
| currGroupByItemExpr = |
| new(bindWA->wHeap()) SelIndex(selListIndex+1, |
| currGroupByItemExpr); |
| else |
| currGroupByItemExpr = new(bindWA->wHeap()) SelIndex(selListIndex+1); |
| } |
| } // expr in group by clause |
| |
| if ((NOT found) && |
| (selIndexError) && |
| (selIndex > 0)) |
| { |
| // this const was not found in the select list and it was |
| // not a valid select index. |
| // Return an error. |
| *CmpCommon::diags() << DgSqlCode(-4007) |
| << DgInt0((Lng32)selIndex) |
| << DgInt1((Lng32)origSelectList.entries()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| if (compExprTreeIsNull) |
| return this; |
| |
| if (currGroupByItemExpr->getOperatorType() == ITM_SEL_INDEX) |
| { |
| SelIndex * si = (SelIndex*)currGroupByItemExpr; |
| if (si->getSelIndex() > origSelectList.entries()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4007) |
| << DgInt0((Lng32)si->getSelIndex()) |
| << DgInt1((Lng32)origSelectList.entries()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| ItemExpr * selectListEntry = origSelectList[si->getSelIndex()-1]; |
| if ((selectListEntry->getOperatorType() == ITM_RENAME_COL) && |
| (selectListEntry->child(0)->getOperatorType() == ITM_REFERENCE)) |
| { |
| // make a copy of this entry's child |
| newGroupByItemExpr = |
| selectListEntry->child(0)-> |
| castToItemExpr()->copyTopNode(NULL, bindWA->wHeap()); |
| } |
| else if (selectListEntry->getOperatorType() == ITM_REFERENCE) |
| { |
| if (((ColReference*)selectListEntry)-> getColRefNameObj().isStar()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4185) ; |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // make a copy of this entry |
| newGroupByItemExpr = |
| selectListEntry->copyTopNode(NULL, bindWA->wHeap()); |
| } |
| else |
| { |
| selectListEntry->setInGroupByOrdinal(TRUE); |
| newGroupByItemExpr = currGroupByItemExpr; |
| } |
| |
| foundSelIndex = TRUE; |
| } // group by ordinal |
| else if (currGroupByItemExpr->getOperatorType() == ITM_REFERENCE) |
| { |
| ColReference * groupByColReference = |
| (ColReference*)currGroupByItemExpr; |
| |
| // find out if this ColReference name is a renamed col in the |
| // select list. |
| if (lookForRenamedCols && |
| groupByColReference->getCorrNameObj().getQualifiedNameObj().getObjectName().length() == 0) |
| { |
| |
| NABoolean renamedColsInSelectList = FALSE; |
| CollIndex j = 0; |
| NABoolean found = FALSE; |
| while (j < origSelectList.entries()) |
| { |
| ItemExpr * selectListEntry = origSelectList[j]; |
| |
| if (selectListEntry->getOperatorType() == ITM_RENAME_COL) |
| { |
| renamedColsInSelectList = TRUE; |
| |
| const ColRefName &selectListColRefName = |
| *((RenameCol *)selectListEntry)->getNewColRefName(); |
| |
| if (groupByColReference->getColRefNameObj().getColName() |
| == selectListColRefName.getColName()) |
| { |
| if (found) |
| { |
| // multiple entries with the same name. Error. |
| *CmpCommon::diags() << DgSqlCode(-4195) |
| << DgString0(selectListColRefName.getColName()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| foundSelIndex = TRUE; |
| |
| selectListEntry->setInGroupByOrdinal(TRUE); |
| |
| newGroupByItemExpr = |
| new(bindWA->wHeap()) SelIndex(j+1); |
| ((SelIndex *) newGroupByItemExpr)-> |
| setRenamedColNameInGrbyClause(TRUE); |
| |
| |
| found = TRUE; |
| } |
| } // rename col |
| |
| j++; |
| } // while |
| |
| if ((NOT renamedColsInSelectList) && |
| (j == origSelectList.entries())) |
| lookForRenamedCols = FALSE; |
| } // lookForRenamedCols |
| |
| if (! newGroupByItemExpr) |
| newGroupByItemExpr = currGroupByItemExpr; |
| } // else foundSelIndex |
| else if ((currGroupByItemExpr->getOperatorType() == ITM_USER_DEF_FUNCTION) && |
| (udfSubqInAggGrby_Enabled)) |
| newGroupByItemExpr = currGroupByItemExpr; |
| else if ((currGroupByItemExpr->getOperatorType() == ITM_ROW_SUBQUERY) && |
| (udfSubqInAggGrby_Enabled)) |
| newGroupByItemExpr = currGroupByItemExpr; |
| else |
| { |
| *CmpCommon::diags() << DgSqlCode(-4197) |
| << DgString0("GROUP BY"); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| newGroupByList.insert(newGroupByItemExpr); |
| |
| } // for |
| |
| if ((foundSelIndex) && |
| (newGroupByList.entries() > 0)) |
| { |
| grby->removeGroupExprTree(); |
| grby->addGroupExprTree(newGroupByList.convertToItemExpr()); |
| } |
| |
| grby->setParentRootSelectList(getCompExprTree()); |
| |
| // if order by and group by are specified, check to see that |
| // all columns specified in the order by clause are also present |
| // in the group by clause. |
| allOrderByRefsInGby_ = FALSE; |
| if ( |
| (getOrderByTree()) && |
| (grby->getGroupExprTree() != NULL)) |
| { |
| ItemExpr *orderByTree = getOrderByTree(); |
| |
| ItemExprList orderByList(orderByTree, bindWA->wHeap()); |
| ItemExprList groupByList(grby->getGroupExprTree(), bindWA->wHeap()); |
| |
| allOrderByRefsInGby_ = TRUE; |
| for (CollIndex ii = 0; ii < orderByList.entries(); ii++) |
| { |
| ItemExpr * colRef = orderByList[ii]; |
| if (colRef->getOperatorType() == ITM_INVERSE) |
| colRef = colRef->child(0)->castToItemExpr(); |
| if (colRef && colRef->getOperatorType() == ITM_REFERENCE) |
| { |
| ColReference * obyColRef = (ColReference*)colRef; |
| |
| NABoolean found = FALSE; |
| for (CollIndex j = 0; j < groupByList.entries(); j++) |
| { |
| ItemExpr * gbyExpr = groupByList[j]; |
| if (gbyExpr->getOperatorType() == ITM_REFERENCE) |
| { |
| ColReference * gbyColRef = (ColReference*)gbyExpr; |
| if (obyColRef->getColRefNameObj().getColName() == |
| gbyColRef->getColRefNameObj().getColName()) |
| { |
| found = TRUE; |
| break; |
| } |
| } // if |
| } // for |
| |
| if (NOT found) |
| { |
| allOrderByRefsInGby_ = FALSE; |
| break; |
| } |
| } // if |
| } // for |
| } // if |
| |
| return this; |
| } |
| |
| RelRoot * RelRoot::transformGroupByWithOrdinalPhase2(BindWA *bindWA) |
| { |
| NABoolean specialMode = |
| (CmpCommon::getDefault(MODE_SPECIAL_1) == DF_ON); |
| |
| // make sure child of root is a groupby node or a sequence node |
| // whose child is a group by node |
| if (child(0)->getOperatorType() != REL_GROUPBY && |
| (child(0)->getOperatorType() != REL_SEQUENCE || |
| (child(0)->child(0) && child(0)->child(0)->getOperatorType()!=REL_GROUPBY))) |
| return this; |
| |
| GroupByAgg * grby; |
| RelSequence * seqNode=NULL; |
| |
| if (child(0)->getOperatorType() == REL_GROUPBY ) |
| { |
| grby=(GroupByAgg *)(child(0)->castToRelExpr()); |
| } |
| else |
| {//sequence node above group by |
| grby=(GroupByAgg *)(child(0)->child(0)->castToRelExpr()); |
| seqNode=(RelSequence *)(child(0)->castToRelExpr()); |
| } |
| |
| DCMPASSERT(grby != NULL); |
| |
| if (grby->isRollup()) |
| { |
| if (grby->groupExpr().entries() != grby->rollupGroupExprList().entries()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4384) |
| << DgString0("Cannot have duplicate entries."); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| for (ValueId valId = grby->aggregateExpr().init(); |
| grby->aggregateExpr().next(valId); |
| grby->aggregateExpr().advance(valId)) |
| { |
| ItemExpr * ae = valId.getItemExpr(); |
| |
| // right now, only support groupby rollup on min/max/sum/avg/count |
| if (NOT ((ae->getOperatorType() == ITM_MIN) || |
| (ae->getOperatorType() == ITM_MAX) || |
| (ae->getOperatorType() == ITM_SUM) || |
| (ae->getOperatorType() == ITM_AVG) || |
| (ae->getOperatorType() == ITM_COUNT) || |
| (ae->getOperatorType() == ITM_COUNT_NONULL) || |
| (ae->getOperatorType() == ITM_GROUPING))) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4384) |
| << DgString0("Unsupported rollup aggregate function."); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // right now, only support groupby rollup on non-distinct aggrs |
| Aggregate * ag = (Aggregate*)ae; |
| if (ag->isDistinct()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4384) |
| << DgString0("Distinct rollup aggregates not supported."); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // if grouping aggr, find the rollup group it corresponds to. |
| if (ae->getOperatorType() == ITM_GROUPING) |
| { |
| NABoolean found = FALSE; |
| ItemExpr * aggrChild = ae->child(0); |
| int i = 0; |
| while ((NOT found) and (i < grby->rollupGroupExprList().entries())) |
| { |
| ValueId vid = grby->rollupGroupExprList()[i]; |
| if (vid.getItemExpr()->getOperatorType() == ITM_SEL_INDEX) |
| { |
| SelIndex * si = (SelIndex*)vid.getItemExpr(); |
| vid = compExpr()[si->getSelIndex()-1]; |
| } |
| found = aggrChild->duplicateMatch(*vid.getItemExpr()); |
| if (found) |
| ag->setRollupGroupIndex(i); |
| i++; |
| } // while |
| |
| if (NOT found) |
| { |
| // must find it. |
| *CmpCommon::diags() << DgSqlCode(-4384) |
| << DgString0("GROUPING function can only be specified on a GROUP BY ROLLUP entry."); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| } // for |
| } |
| else |
| { |
| // not groupby rollup |
| for (ValueId valId = grby->aggregateExpr().init(); |
| grby->aggregateExpr().next(valId); |
| grby->aggregateExpr().advance(valId)) |
| { |
| ItemExpr * ae = valId.getItemExpr(); |
| |
| // grouping can only be specified with 'groupby rollup' clause |
| if (ae->getOperatorType() == ITM_GROUPING) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3242) |
| << DgString0("GROUPING function can only be specified with GROUP BY ROLLUP clause."); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| } // for |
| } |
| |
| ValueIdSet &groupExpr = grby->groupExpr(); |
| // copy of groupExpr used to identify the changed |
| // value ids |
| ValueIdSet groupExprCpy(grby->groupExpr()); |
| |
| // When we encounter subqueries or MVFs in the select list |
| // these gets expanded at bind time, and so the select index have to |
| // be offset with the expansion number since the sel_index number |
| // reflects the select list at parse time. |
| for (ValueId vid = groupExpr.init(); |
| groupExpr.next(vid); |
| groupExpr.advance(vid)) |
| { |
| if (vid.getItemExpr()->getOperatorType() == ITM_SEL_INDEX) |
| { |
| CollIndex selIndexExpansionOffset = 0; |
| SelIndex * si = (SelIndex*)(vid.getItemExpr()); |
| ValueId grpById = |
| compExpr()[si->getSelIndex() -1]; |
| si->setValueId(grpById); |
| if (child(0)->getOperatorType() != REL_SEQUENCE) |
| { |
| groupExprCpy.remove(vid); |
| groupExprCpy.insert(grpById); |
| if (grby->isRollup()) |
| { |
| CollIndex idx = grby->rollupGroupExprList().index(vid); |
| grby->rollupGroupExprList()[idx] = grpById; |
| } |
| } |
| else |
| { //sequence |
| CMPASSERT(seqNode); |
| const ValueIdSet seqCols = ((const RelSequence*)seqNode)->sequencedColumns(); |
| |
| ItemExpr * ie = grpById.getItemExpr(); |
| ItemExpr::removeNotCoveredFromExprTree(ie,seqCols); |
| |
| //ie = ie->copyTree(bindWA->wHeap()); |
| //ie = ie->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| groupExprCpy.remove(vid); |
| groupExprCpy.insert(ie->getValueId()); |
| |
| ie = new (bindWA->wHeap()) NotCovered(ie); |
| ie->synthTypeAndValueId(); |
| |
| compExpr()[si->getSelIndex()-1] = ie->getValueId(); |
| seqNode->addSequencedColumn(ie->getValueId()); |
| |
| } |
| |
| switch (grpById.getItemExpr()->getOperatorType()) |
| { |
| case ITM_VALUEID_PROXY: |
| { |
| ValueId derivedId = |
| (( ValueIdProxy *)(grpById.getItemExpr()))->isDerivedFrom(); |
| |
| // If this is not the ValueIdProxy that represents the MVF or Subq |
| // skip the expansion. |
| if ((( ValueIdProxy *)(grpById.getItemExpr()))-> |
| needToTransformChild() != TRUE) break; |
| |
| ValueIdList outputs; |
| |
| switch (derivedId.getItemExpr()->getOperatorType()) |
| { |
| case ITM_USER_DEF_FUNCTION: |
| { |
| // When we reference a UDF in the groupBy clause, |
| // if the UDF is a MVF(has multiple outputs), we need to add |
| // the other elements from the MVF's outputs. |
| |
| // These elements have already been expanded into the |
| // select list, so all we need to do is to add them to the |
| // groupby expression. |
| |
| // By default, we associate the valueId of the MVF with |
| // its first output, so we just need to copy the rest of the |
| // outputs. |
| UDFunction *udf = (UDFunction *) derivedId.getItemExpr(); |
| const RoutineDesc *rDesc = udf->getRoutineDesc(); |
| outputs = rDesc->getOutputColumnList(); |
| break; |
| } |
| |
| case ITM_ROW_SUBQUERY: |
| { |
| // When we reference a subquery in the groupBy clause, |
| // if the subquery has a degree > 1, we need to add the other |
| // elements from the subquery's select list. |
| Subquery *subq = (Subquery *) derivedId.getItemExpr(); |
| RelRoot *subqRoot = (RelRoot *) subq->getSubquery(); |
| outputs = subqRoot->compExpr(); |
| break; |
| } |
| default: |
| CMPASSERT(0); // we don't support anything else |
| } |
| |
| |
| // Add in the other outputs from the MVF/Subquery |
| for (CollIndex i=1; i < outputs.entries(); i++) |
| { |
| selIndexExpansionOffset ++; |
| groupExprCpy.insert(outputs[i]); |
| } |
| |
| // Need to check the groupBy and orderBy lists |
| // for selIndexes with an index greater than this one, |
| // If we find one, bump its index into the select list by |
| // the expansion. |
| |
| ValueIdSet fixedUpIndecies; |
| |
| fixUpSelectIndeciesInSet(grby->groupExpr(),fixedUpIndecies, |
| si->getSelIndex(), |
| selIndexExpansionOffset); |
| |
| fixUpSelectIndecies(getOrderByTree(), fixedUpIndecies, |
| si->getSelIndex(), |
| selIndexExpansionOffset); |
| break; |
| } |
| } |
| |
| // Now that we have swapped the vid list from grouping |
| // expression to the corresponding one from select list |
| // go thru each expression, collect the base columns |
| // and mark each column as referenced for histogram. |
| // Since this is only for group by, we will get only single |
| // interval histograms - 10-081015-6557 |
| ValueIdSet columns; |
| grpById.getItemExpr()->findAll(ITM_BASECOLUMN, columns, TRUE, TRUE); |
| for (ValueId id = columns.init(); |
| columns.next(id); |
| columns.advance(id)) |
| { |
| NAColumn *nacol = id.getNAColumn(); |
| if (nacol->isReferencedForHistogram()) |
| continue; |
| nacol->setReferencedForSingleIntHist(); |
| } |
| } // found Sel Index |
| } |
| |
| ValueId valId; |
| if (grby->isRollup()) |
| { |
| for (CollIndex i = 0; i < grby->rollupGroupExprList().entries(); i++) |
| { |
| valId = grby->rollupGroupExprList()[i]; |
| |
| if (NOT valId.getType().supportsSQLnull()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4384) |
| << DgString0("Grouped columns must be nullable."); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| } |
| |
| //looking for extra order requirement, currently, aggregate function PIVOT_GROUP will need extra order |
| //so loop through the aggregation expression and check if there is PIVOT_GROUP and it needs explicit order |
| //if found, populate the extraOrderExpr for the GroupAggBy |
| //so later optimizer can add correct sort key |
| ValueIdSet &groupAggExpr = grby->aggregateExpr(); |
| |
| for (ValueId vid = groupAggExpr.init(); |
| groupAggExpr.next(vid); |
| groupAggExpr.advance(vid)) |
| { |
| if (vid.getItemExpr()->getOperatorType() == ITM_PIVOT_GROUP) |
| { |
| if( ((PivotGroup*)vid.getItemExpr())->orderBy() ) |
| { |
| grby->setExtraGrpOrderby(((PivotGroup*)vid.getItemExpr())->getOrderbyItemExpr()); |
| ValueIdList tmpList; |
| grby->getExtraGrpOrderby()->convertToValueIdList(tmpList, bindWA, ITM_ITEM_LIST); |
| grby->setExtraOrderExpr(tmpList); |
| } |
| } |
| } |
| // recreate the groupExpr expression after updating the value ids |
| grby->setGroupExpr (groupExprCpy); |
| |
| if ((grby->selPredTree()) && |
| (grby->selIndexInHaving())) |
| { |
| setValueIdForRenamedColsInHaving(bindWA, grby->selPredTree(), |
| compExpr()); |
| |
| BindScope *currScope = bindWA->getCurrentScope(); |
| |
| ItemExpr *havingPred = grby->removeSelPredTree(); |
| currScope->context()->inHavingClause() = TRUE; |
| havingPred->convertToValueIdSet(grby->selectionPred(), |
| bindWA, ITM_AND); |
| currScope->context()->inHavingClause() = FALSE; |
| if (bindWA->errStatus()) |
| return this; |
| } |
| |
| if (orderByTree_ && seqNode && grby) |
| { |
| ItemExprList origOrderByList(bindWA->wHeap()); |
| |
| origOrderByList.insertTree(orderByTree_); |
| |
| ItemExprList newOrderByList((Lng32)origOrderByList.entries(), bindWA->wHeap()); |
| |
| for (CollIndex i = 0; (i < (CollIndex) origOrderByList.entries());i++) |
| { |
| ItemExpr * currOrderByItemExpr = |
| ((ItemExpr *) origOrderByList[i])->castToItemExpr(); |
| ItemExpr * newOrderByItemExpr = currOrderByItemExpr; |
| |
| if (currOrderByItemExpr->getOperatorType() == ITM_SEL_INDEX) |
| { |
| SelIndex * si = (SelIndex*)(currOrderByItemExpr); |
| if (compExpr()[si->getSelIndex()-1].getItemExpr()->getOperatorType() != ITM_BASECOLUMN) |
| { |
| newOrderByItemExpr = compExpr()[si->getSelIndex()-1].getItemExpr(); |
| } |
| } |
| newOrderByList.insert(newOrderByItemExpr); |
| } |
| orderByTree_ = newOrderByList.convertToItemExpr(); |
| } |
| |
| return this; |
| } |
| |
| void RelRoot::transformTDPartitionOrdinals(BindWA *bindWA) |
| { |
| if(!getHasTDFunctions()) |
| return ; |
| |
| if (getCompExprTree() == NULL) |
| return ; |
| |
| BindScope *currScope = bindWA->getCurrentScope(); |
| |
| RelExpr * realChildNode = NULL; |
| |
| if (child(0)->getOperatorType() == REL_FIRST_N) |
| { |
| realChildNode = child(0)->child(0); |
| } |
| else |
| { |
| realChildNode = child(0); |
| } |
| |
| if(realChildNode->getOperatorType() != REL_SEQUENCE ) |
| { |
| return; |
| } |
| |
| RelSequence * seqNode = (RelSequence *)realChildNode; |
| |
| if (!seqNode->getPartitionBy()) |
| { |
| return; |
| } |
| |
| ItemExpr * partitionBy = seqNode->getPartitionBy()->copyTree(bindWA->wHeap()); |
| |
| ItemExprList origSelectList(getCompExprTree(), bindWA->wHeap()); |
| |
| ItemExprList origPartitionByList(bindWA->wHeap()); |
| if (partitionBy) |
| { |
| origPartitionByList.insertTree(partitionBy); |
| } |
| |
| for (CollIndex i = 0; (i < (CollIndex) origPartitionByList.entries());i++) |
| { |
| ItemExpr * currPartitionByItemExpr = |
| ((ItemExpr *) origPartitionByList[i])->castToItemExpr(); |
| |
| NABoolean selIndexError = FALSE; |
| Int64 selIndex = -1; |
| if (currPartitionByItemExpr->getOperatorType() == ITM_CONSTANT) |
| { |
| ConstValue * cv = (ConstValue*)currPartitionByItemExpr; |
| if ((cv->canGetExactNumericValue()) && |
| (cv->getType()->getScale() == 0)) |
| { |
| selIndex = cv->getExactNumericValue(); |
| |
| if (selIndex <= 0 || selIndex > origSelectList.entries()) |
| { //index in error -- produce error message |
| //in TD mode group by <constant> -- constant is purely positional |
| //selIndexError = TRUE; |
| *CmpCommon::diags() << DgSqlCode(-4366); |
| bindWA->setErrStatus(); |
| return; |
| } |
| else |
| { |
| origPartitionByList.usedEntry( i )= |
| origSelectList.usedEntry((CollIndex)selIndex-1)->copyTree(bindWA->wHeap()); |
| } |
| } |
| } |
| } |
| seqNode->setPartitionBy(origPartitionByList.convertToItemExpr()); |
| } |
| // resolveAggregates - |
| // If aggregate functions have been found in the select list, then |
| // either attach the aggregate functions to the existing GroupBy below |
| // this RelRoot, or if there is no GroupBy create a GroupBy with an |
| // empty groupby list (scalar) and attach the aggregate functions to |
| // this GroupBy. |
| // |
| void RelRoot::resolveAggregates(BindWA *bindWA) |
| { |
| BindScope *currScope = bindWA->getCurrentScope(); |
| |
| if (NOT currScope->getUnresolvedAggregates().isEmpty()) { |
| if (getHasTDFunctions()) |
| { //Using rank function and aggregate functions in the same scope is not supported. |
| *CmpCommon::diags() << DgSqlCode(-4365); |
| bindWA->setErrStatus(); |
| return; |
| } |
| |
| RelExpr *sequence = currScope->getSequenceNode(); |
| |
| // The aggregates were used without a GROUP BY or HAVING |
| // clause, i.e. an implicit aggregation is performed |
| // (with a NULL result for an empty input table). |
| NABoolean implicitGrouping = (child(0)->getOperatorType() != REL_GROUPBY); |
| |
| if(getHasOlapFunctions()) { |
| implicitGrouping = (sequence->child(0)->getOperatorType() != REL_GROUPBY); |
| } |
| GroupByAgg *groupByAgg = NULL; |
| if (implicitGrouping) { |
| RelExpr * realChildNode = NULL; |
| |
| // if my child is a FIRST_N node, then add the GroupByAgg below it. |
| // Otherwise, add the GroupByAgg below me. |
| if (child(0)->getOperatorType() == REL_FIRST_N) |
| { |
| realChildNode = child(0)->child(0); |
| } |
| else |
| realChildNode = child(0); |
| |
| if(getHasOlapFunctions()) { |
| |
| realChildNode = sequence->child(0); |
| } |
| |
| groupByAgg = |
| new (bindWA->wHeap()) GroupByAgg(realChildNode,REL_GROUPBY); |
| realChildNode->setBlockStmt(isinBlockStmt()); |
| |
| if(getHasOlapFunctions()) |
| sequence->setChild(0, groupByAgg); |
| else if (child(0)->getOperatorType() == REL_FIRST_N) |
| child(0)->setChild(0, groupByAgg); |
| else |
| setChild(0, groupByAgg); |
| |
| groupByAgg->setBlockStmt(isinBlockStmt()); |
| } |
| else { |
| if(getHasOlapFunctions()) { |
| groupByAgg = (GroupByAgg *)sequence->child(0).getPtr(); |
| } else { |
| groupByAgg = (GroupByAgg *)child(0).getPtr(); |
| } |
| } |
| |
| NAString colName(bindWA->wHeap()); |
| Lng32 sqlCode = 0; |
| ValueId valId = NULL_VALUE_ID; |
| |
| if (currScope->context()->unaggColRefInSelectList()) { |
| sqlCode = -4021; |
| valId = currScope->context()->unaggColRefInSelectList()->getValueId(); |
| } |
| else if (implicitGrouping) { |
| // Genesis 10-000414-9410: "SELECT SUM(A),* FROM T; --no GROUP BY" |
| // cannot be flagged with err 4012 in ColReference::bindNode |
| // because table not marked "grouped" yet. |
| // |
| const ColumnDescList &cols = *currScope->getRETDesc()->getColumnList(); |
| CollIndex i, n = cols.entries(); |
| for (i=0; i<n; i++) { |
| const ColumnDesc *col = cols[i]; |
| if (!col->isGrouped()) |
| if (col->getColRefNameObj().isStar() || |
| col->getValueId().getNAColumn(TRUE/*okIfNotColumn*/)) { |
| sqlCode = -4012; |
| valId = col->getValueId(); |
| colName = col->getColRefNameObj().getColRefAsAnsiString(); |
| break; |
| } |
| } |
| } |
| |
| // Table has no GROUP BY (so no grouping columns exist at all) |
| // but is grouped by dint of a column reference within an aggregate, |
| // making any unaggregated column references illegal, by ANSI 7.9 SR 7. |
| if (sqlCode) { |
| |
| if (colName.isNull()) { |
| const NAColumn *nacol = valId.getNAColumn(TRUE/*okIfNotColumn*/); |
| if (nacol) |
| colName = nacol->getFullColRefNameAsAnsiString(); |
| else |
| colName = "_unnamed_column_"; |
| } |
| |
| // 4012 Col ref must be grouping or aggregated -- no star ref allowed! |
| // 4021 The select list contains a non-grouping non-aggregated column. |
| *CmpCommon::diags() << DgSqlCode(sqlCode) << DgColumnName(colName); |
| bindWA->setErrStatus(); |
| return; |
| } |
| |
| // Move the unresolved aggregates into the groupby node and bind |
| // (simply returns if "groupByAgg" isn't new). |
| groupByAgg->aggregateExpr() += currScope->getUnresolvedAggregates(); |
| currScope->getUnresolvedAggregates().clear(); |
| groupByAgg->bindNode(bindWA); |
| } |
| } |
| |
| // resolveSequenceFunctions - |
| // Add the unresolvedSequenceFunctions to the Sequence node for this |
| // scope. If there are sequence functions, but no sequence node, it |
| // is an error. Also if there is a sequence node, but no sequence |
| // functions, it is an error. |
| // |
| // |
| void RelRoot::resolveSequenceFunctions(BindWA *bindWA) |
| { |
| BindScope *currScope = bindWA->getCurrentScope(); |
| |
| // If we have a Sequence Node associated with the RelRoot node, |
| // |
| RelSequence *sequenceNode = (RelSequence *)currScope->getSequenceNode(); |
| currScope->getSequenceNode() = NULL; |
| |
| if (sequenceNode) { |
| if (getHasTDFunctions() && sequenceNode->child(0)->getOperatorType() == REL_GROUPBY) |
| { //Using rank function and group by clause in the same scope is not supported. |
| *CmpCommon::diags() << DgSqlCode(-4366); |
| bindWA->setErrStatus(); |
| return; |
| } |
| |
| CMPASSERT(sequenceNode->getOperatorType() == REL_SEQUENCE); |
| |
| // Do not allow sequence functions or OLAP Window functions |
| // with Embedded Updates. |
| // |
| if (getGroupAttr()->isEmbeddedUpdateOrDelete()){ |
| *CmpCommon::diags() << DgSqlCode(-4202) |
| << (getGroupAttr()->isEmbeddedUpdate() ? |
| DgString0("UPDATE"):DgString0("DELETE")); |
| bindWA->setErrStatus(); |
| return; |
| } |
| |
| // If there are some sequence functions that have not been attached |
| // to the Sequence node, do so now. These were found when binding |
| // the select list. |
| // |
| sequenceNode-> |
| addUnResolvedSeqFunctions(currScope->getUnresolvedSequenceFunctions(), |
| bindWA); |
| |
| currScope->getUnresolvedSequenceFunctions().clear(); |
| currScope->getAllSequenceFunctions().clear(); |
| if (bindWA->errStatus()) return; |
| |
| // Make sure the sequence function has some work to do. |
| // The cast is needed since the compiler will attempt to pick the |
| // protected (writable) version of 'sequenceFunctions()'. (Is this |
| // a compiler bug) |
| // |
| if ((((const RelSequence *)sequenceNode)->sequenceFunctions().isEmpty() ) |
| && |
| ( !getHasOlapFunctions() && |
| ((const RelSequence *)sequenceNode)->requiredOrder().entries() != 0 )) { |
| // Can't have a sequence by clause without |
| // sequence functions. |
| // |
| *CmpCommon::diags() << DgSqlCode(-4111); |
| bindWA->setErrStatus(); |
| return; |
| } |
| } else if (! currScope->getUnresolvedSequenceFunctions().isEmpty()) { |
| |
| // Can't have sequence functions without a |
| // sequence by clause. |
| // First, loop through the list of functions. |
| // |
| ValueIdSet &unresolved = currScope->getUnresolvedSequenceFunctions(); |
| NAString unparsed(bindWA->wHeap()); |
| for (ValueId vid = unresolved.init(); unresolved.next(vid); unresolved.advance(vid)) { |
| ItemExpr *ie = vid.getItemExpr(); |
| CMPASSERT(ie->isASequenceFunction()); |
| unparsed += ", "; |
| ie->unparse(unparsed, DEFAULT_PHASE, USER_FORMAT_DELUXE); |
| } |
| unparsed.remove(0,2); // remove initial ", " |
| |
| *CmpCommon::diags() << DgSqlCode(-4110) << DgString0(unparsed); |
| bindWA->setErrStatus(); |
| return; |
| } |
| } |
| |
| // if a where pred is specified on an immediate child scan or rename node, |
| // and it contains an 'and'ed rownum() predicate of the form: |
| // rownum < val, or rownum <= val, or rownum = val |
| // then get the val and make it the firstN value. |
| // Also, remove this predicate from selPredTree. |
| void RelRoot::processRownum(BindWA * bindWA) |
| { |
| NABoolean specialMode = (CmpCommon::getDefault(MODE_SPECIAL_4) == DF_ON); |
| if (NOT specialMode) |
| return; |
| |
| if (! child(0)) |
| return; |
| |
| if ((child(0)->getOperatorType() != REL_SCAN) && |
| (child(0)->getOperatorType() != REL_RENAME_TABLE)) |
| return; |
| |
| if (! child(0)->selPredTree()) |
| return; |
| |
| ItemExpr * wherePred = child(0)->selPredTree(); |
| ItemExprList iel(wherePred, bindWA->wHeap(), ITM_AND, FALSE, FALSE); |
| |
| NABoolean found = FALSE; |
| for (Lng32 i = 0; ((NOT found) && (i < iel.entries())); i++) |
| { |
| ItemExpr * ie = iel[i]; |
| if (ie->getArity() != 2) |
| continue; |
| |
| if (NOT ((ie->getOperatorType() == ITM_LESS) || |
| (ie->getOperatorType() == ITM_EQUAL) || |
| (ie->getOperatorType() == ITM_LESS_EQ))) |
| continue; |
| |
| ItemExpr * child0 = ie->child(0)->castToItemExpr(); |
| ItemExpr * child1 = ie->child(1)->castToItemExpr(); |
| |
| if (NOT ((child0->getOperatorType() == ITM_REFERENCE) && |
| (child1->getOperatorType() == ITM_CONSTANT))) |
| continue; |
| |
| ColReference * col = (ColReference*)child0; |
| ColRefName &colRefName = col->getColRefNameObj(); |
| CorrName &cn = col->getCorrNameObj(); |
| |
| const NAString &catName = cn.getQualifiedNameObj().getCatalogName(); |
| const NAString &schName = cn.getQualifiedNameObj().getSchemaName(); |
| const NAString &objName = cn.getQualifiedNameObj().getObjectName(); |
| const NAString &colName = colRefName.getColName(); |
| |
| if (NOT ((catName.isNull()) && |
| (schName.isNull()) && |
| (objName.isNull()) && |
| (colName == "ROWNUM"))) |
| continue; |
| |
| ConstValue * cv = (ConstValue*)child1; |
| if (NOT cv->canGetExactNumericValue()) |
| continue; |
| |
| Int64 val = cv->getExactNumericValue(); |
| if (val < 0) |
| continue; |
| |
| if ((ie->getOperatorType() == ITM_EQUAL) && |
| (val != 1)) |
| continue; |
| |
| if ((ie->getOperatorType() == ITM_LESS) && |
| (val > 0)) |
| val--; |
| |
| setFirstNRows(val); |
| |
| // remove this pred from the list |
| iel.removeAt(i); |
| |
| found = TRUE; |
| } |
| |
| if (found) |
| { |
| // convert the list back to selection pred. |
| ItemExpr * ie = iel.convertToItemExpr(); |
| child(0)->removeSelPredTree(); |
| child(0)->addSelPredTree(ie); |
| } |
| |
| return; |
| } |
| |
| RelExpr *RelRoot::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| if (isTrueRoot()) |
| { |
| // if this is simple scalar aggregate on a seabase table |
| // (of the form: select count(*), sum(a) from t; ) |
| // then transform it so it could be evaluated using hbase co-processor. |
| if ((CmpCommon::getDefault(HBASE_COPROCESSORS) == DF_ON) && |
| (child(0) && child(0)->getOperatorType() == REL_SCAN)) |
| { |
| Scan * scan = (Scan*)child(0)->castToRelExpr(); |
| |
| if ((getCompExprTree()) && |
| (NOT hasOrderBy()) && |
| (! getSelPredTree()) && |
| (! scan->getSelPredTree()) && |
| (scan->selectionPred().isEmpty()) && |
| ((scan->getTableName().getSpecialType() == ExtendedQualName::NORMAL_TABLE) || |
| (scan->getTableName().getSpecialType() == ExtendedQualName::INDEX_TABLE)) && |
| !scan->getTableName().isPartitionNameSpecified() && |
| !scan->getTableName().isPartitionRangeSpecified() && |
| (NOT bindWA->inViewDefinition())) |
| { |
| ItemExprList selList(bindWA->wHeap()); |
| selList.insertTree(getCompExprTree()); |
| // for now, only count(*) can be co-proc'd |
| if ((selList.entries() == 1) && |
| (selList[0]->getOperatorType() == ITM_COUNT) && |
| (selList[0]->origOpType() == ITM_COUNT_STAR__ORIGINALLY)) |
| { |
| NATable *naTable = bindWA->getNATable(scan->getTableName()); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (((naTable->getObjectType() == COM_BASE_TABLE_OBJECT) || |
| (naTable->getObjectType() == COM_INDEX_OBJECT)) && |
| ((naTable->isSeabaseTable()) || |
| ((naTable->isHiveTable()) && |
| (naTable->getClusteringIndex()->getHHDFSTableStats()->isOrcFile())))) |
| { |
| Aggregate * agg = |
| new(bindWA->wHeap()) Aggregate(ITM_COUNT, |
| new (bindWA->wHeap()) SystemLiteral(1), |
| FALSE /*i.e. not distinct*/, |
| ITM_COUNT_STAR__ORIGINALLY, |
| '!'); |
| agg->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| { |
| return this; |
| } |
| |
| ValueIdSet aggrSet; |
| aggrSet.insert(agg->getValueId()); |
| ExeUtilExpr * eue = NULL; |
| |
| if (naTable->isSeabaseTable()) |
| eue = |
| new(CmpCommon::statementHeap()) |
| ExeUtilHbaseCoProcAggr(scan->getTableName(), |
| aggrSet); |
| else |
| eue = |
| new(CmpCommon::statementHeap()) |
| ExeUtilOrcFastAggr(scan->getTableName(), |
| aggrSet); |
| |
| eue->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| { |
| return this; |
| } |
| |
| setChild(0, eue); |
| |
| removeCompExprTree(); |
| addCompExprTree(agg); |
| |
| } // if seabaseTable |
| } // count aggr |
| } |
| } // coproc on |
| |
| |
| if (child(0) && |
| ((child(0)->getOperatorType() == REL_INSERT) || |
| (child(0)->getOperatorType() == REL_UNARY_INSERT) || |
| (child(0)->getOperatorType() == REL_LEAF_INSERT))) |
| { |
| Insert * ins = (Insert*)child(0)->castToRelExpr(); |
| if (ins->isNoRollback()) |
| { |
| if ((CmpCommon::getDefault(AQR_WNR) |
| != DF_OFF) && |
| (CmpCommon::getDefault(AQR_WNR_INSERT_CLEANUP) |
| != DF_OFF)) |
| ins->enableAqrWnrEmpty() = TRUE; |
| } |
| if (CmpCommon::transMode()->anyNoRollback()) |
| { |
| // tbd - may need to integrate these two. |
| if ((CmpCommon::getDefault(AQR_WNR) |
| != DF_OFF) && |
| (CmpCommon::getDefault(AQR_WNR_INSERT_CLEANUP) |
| != DF_OFF)) |
| ins->enableAqrWnrEmpty() = TRUE; |
| } |
| } |
| |
| // if lob is being extracted as chunks of string, then only one |
| // such expression could be specified in the select list. |
| // If this is the case, then insert ExeUtilLobExtract operator. |
| // This operator reads lob contents and returns them to caller as |
| // multiple rows. |
| // This lobextract function could only be used in the outermost select |
| // list and must be converted at this point. |
| // It is not evaluated on its own. |
| if (getCompExprTree()) |
| { |
| ItemExprList selList(bindWA->wHeap()); |
| selList.insertTree(getCompExprTree()); |
| if ((selList.entries() == 1) && |
| (selList[0]->getOperatorType() == ITM_LOBEXTRACT)) |
| { |
| LOBextract * lef = (LOBextract*)selList[0]; |
| |
| ExeUtilLobExtract * le = |
| new (PARSERHEAP()) ExeUtilLobExtract |
| (lef, ExeUtilLobExtract::TO_STRING_, |
| NULL, NULL, lef->getTgtSize(), 0, |
| NULL, NULL, NULL, child(0), PARSERHEAP()); |
| le->setHandleInStringFormat(FALSE); |
| setChild(0, le); |
| } |
| } |
| |
| processRownum(bindWA); |
| |
| } // isTrueRoot |
| |
| if (getHasTDFunctions()) |
| { |
| transformTDPartitionOrdinals(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| } |
| |
| RelRoot * returnedRoot = |
| transformGroupByWithOrdinalPhase1(bindWA); |
| if (! returnedRoot) |
| return NULL; |
| |
| returnedRoot = |
| transformOrderByWithExpr(bindWA); |
| if (! returnedRoot) |
| return NULL; |
| |
| if (bindWA->getCurrentScope()->context()->inTableCheckConstraint()) { |
| // See ANSI 11.9 Leveling Rule 1a (Intermediate Sql). |
| // 4089 A check constraint cannot contain a subquery. |
| *CmpCommon::diags() << DgSqlCode(-4089) |
| << DgConstraintName( |
| bindWA->getCurrentScope()->context()->inCheckConstraint()-> |
| getConstraintName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| } |
| |
| if (isTrueRoot()) |
| bindWA->setTopRoot(this); |
| |
| bindWA->setBindTrueRoot(isTrueRoot()); |
| |
| if (!bindWA->getAssignmentStArea()) { |
| bindWA->getAssignmentStArea() = |
| new (bindWA->wHeap()) AssignmentStArea(bindWA); |
| bindWA->getAssignmentStArea()->getAssignmentStHostVars() = |
| new (bindWA->wHeap()) AssignmentStHostVars(bindWA); |
| } |
| |
| |
| // If there are one or more output rowset variables, then we introduce |
| // a RowsetInto node below this Root node. The RowsetInto node will |
| // create a Pack node later on when it is binded, so that we can |
| // insert values into the rowset output variables. |
| // We don't do this transformation if we are inside a compound statement. |
| // |
| if (isTrueRoot() && assignmentStTree()) { |
| ItemExpr *outputVar = getOutputVarTree(); |
| if (outputVar) { |
| |
| CMPASSERT(outputVar->getChild(0)->getOperatorType() == ITM_HOSTVAR); |
| HostVar *hostVar = (HostVar *) outputVar->getChild(0); |
| |
| if (hostVar->getType()->getTypeQualifier() == NA_ROWSET_TYPE) { |
| |
| ItemExpr *outputVar = removeOutputVarTree(); |
| assignmentStTree() = NULL; |
| |
| // Get the output size expression. It may be a constant or a variable. |
| ItemExpr * sizeExpr = getHostArraysArea()->outputSize(); |
| |
| // set the SelectIntoRowsets flag |
| getHostArraysArea()->setHasSelectIntoRowsets(TRUE); |
| |
| // Create INTO node. Its child is the current root |
| RelExpr *intoNode = |
| new (bindWA->wHeap()) RowsetInto(this, outputVar, sizeExpr); |
| |
| //If case of first N with ORDER BY generator introduces the FIRST N |
| //operator. For rowsets FIRST N node need to be introduced below the |
| //PACK node and not below the top root. So set first N rows for INTO |
| //node and not the top root. |
| if (hasOrderBy()) { |
| intoNode->setFirstNRows(getFirstNRows()); |
| setFirstNRows(-1); |
| } |
| |
| // Create a new root node that will go above the RowsetInto node |
| setRootFlag(FALSE); |
| RelRoot *newRoot = new (bindWA->wHeap()) RelRoot(intoNode); |
| newRoot->setRootFlag(TRUE); |
| // copy the display flag from this true Root to the new root. |
| // newRoot->setDisplayTree(getDisplayTree()); |
| newRoot->setDisplayTree(TRUE); |
| newRoot->addInputVarTree(removeInputVarTree()); |
| newRoot->outputVarCnt() = outputVarCnt(); |
| |
| NABoolean defaultSortedRows = newRoot->needFirstSortedRows(); |
| //Int64 defaultFirstNRows = newRoot->getFirstNRows(); |
| newRoot->needFirstSortedRows() = needFirstSortedRows(); |
| //newRoot->setFirstNRows(getFirstNRows()); |
| needFirstSortedRows() = defaultSortedRows; |
| // setFirstNRows(defaultFirstNRows); |
| |
| newRoot->rollbackOnError() = rollbackOnError(); |
| |
| // migrate hostArraysArea to newroot, and tell bindWA about it |
| newRoot->setHostArraysArea(getHostArraysArea()); |
| bindWA->setHostArraysArea(getHostArraysArea()); |
| setSubRoot(FALSE); // old root is no longer the root |
| newRoot->setSubRoot(TRUE); // newRoot becomes the root |
| |
| return newRoot->bindNode(bindWA); |
| } |
| } |
| } |
| |
| if (assignmentStTree() && child(0)->getOperatorType() != REL_ROWSET_INTO) { |
| AssignmentStHostVars *ptr = |
| new (bindWA->wHeap()) AssignmentStHostVars(bindWA); |
| |
| if (ptr->containsRowsets(assignmentStTree())) { |
| ItemExpr *outputSizeExpr = NULL; |
| |
| // The user may have used the ROWSET FOR OUTPUT SIZE construct |
| // set the SelectIntoRowsets flag. |
| if (getHostArraysArea()) { |
| outputSizeExpr = getHostArraysArea()->outputSize(); |
| getHostArraysArea()->setHasSelectIntoRowsets(TRUE); |
| } |
| |
| // Create RowsetInto node. Its child is the current root |
| RelExpr *intoNode = new (bindWA->wHeap()) |
| RowsetInto(this, assignmentStTree(), outputSizeExpr); |
| |
| //If case of first N with ORDER BY generator introduces the FIRST N |
| //operator. For rowsets FIRST N node need to be introduced below the |
| //PACK node and not below the top root. So set first N rows for INTO |
| //node and not the top root. |
| if (hasOrderBy()) { |
| intoNode->setFirstNRows(getFirstNRows()); |
| setFirstNRows(-1); |
| } |
| |
| RelRoot *newRoot = new (bindWA->wHeap()) RelRoot(*this); |
| |
| newRoot->child(0) = intoNode; |
| newRoot->removeCompExprTree(); |
| setRootFlag(FALSE); |
| removeInputVarTree(); |
| assignmentStTree() = NULL; |
| |
| return newRoot->bindNode(bindWA); |
| } |
| } |
| |
| // Create a new scope. |
| // |
| if (!isDontOpenNewScope()) // -- Triggers. |
| { |
| bindWA->initNewScope(); |
| |
| // MV -- |
| if(TRUE == hasMvBindContext()) |
| { |
| // Copy the MvBindContext object from the RelRoot node to the |
| // current BindContext. |
| bindWA->markScopeWithMvBindContext(getMvBindContext()); |
| } |
| |
| if (getInliningInfo().isTriggerRoot()) |
| { |
| CMPASSERT(getInliningInfo().getTriggerObject() != NULL); |
| bindWA->getCurrentScope()->context()->triggerObj() = |
| getInliningInfo().getTriggerObject()->getCreateTriggerNode(); |
| } |
| |
| if (getInliningInfo().isActionOfRI()) |
| bindWA->getCurrentScope()->context()->inRIConstraint() = TRUE; |
| } |
| |
| // Save whether the user specified SQL/MP-style access options in the query |
| // (this is always true for the LOCK stmt, which we must maximize). |
| // |
| if (child(0)->getOperatorType() == REL_LOCK) { |
| accessOptions().updateAccessOptions( |
| TransMode::ILtoAT(TransMode::READ_COMMITTED_), |
| ((RelLock *)child(0).getPtr())->getLockMode()); |
| accessOptions().updateAccessOptions( |
| TransMode::ILtoAT(CmpCommon::transMode()->getIsolationLevel())); |
| } |
| |
| // QSTUFF: the updateOrDelete flag is set to ensure that scans done as |
| // part of a generic update cause an exclusive lock to be set to ensure |
| // a consistent completion of the following update or delete. |
| if (containsUpdateOrDelete(this)) |
| { |
| accessOptions().setUpdateOrDelete(TRUE); |
| } |
| else if (isTrueRoot()) |
| { |
| // if the query does not contain any Generic Update nodes, mark it |
| // as read only query. In that case, we have freedom not to include |
| // some indexes in the indexes list. |
| bindWA->setReadOnlyQuery(); |
| } |
| |
| |
| // This block of code used to be in RelRoot::propagateAccessOptions() which |
| // used to be called from here. We've since replaced this old 'push' call |
| // with the 'pull' of BindWA->findUserSpecifiedAccessOption() calls from |
| // RelRoot, Scan, and GenericUpdate. |
| // QSTUFF |
| // We decided to stick with READ COMMITTED as the default access |
| // (even for streams). However, if we change our mind again, this is |
| // the place to do it. |
| // if (getGroupAttr()->isStream() && |
| // (accessOptions().accessType() == ACCESS_TYPE_NOT_SPECIFIED_)) |
| // accessOptions().accessType() = SKIP_CONFLICT_; |
| |
| // Set the flag to indicate to DP2 that this executes an |
| // embedded update or delete. |
| if (getGroupAttr()->isEmbeddedUpdateOrDelete()) |
| accessOptions().setUpdateOrDelete(TRUE); |
| // QSTUFF |
| |
| if (accessOptions().userSpecified()) |
| bindWA->getCurrentScope()->context()->setStmtLevelAccessOptions(accessOptions()); |
| |
| if (isSubRoot() && getHostArraysArea()) |
| getHostArraysArea()->setRoot(this); |
| |
| if (isTrueRoot()) { |
| |
| // If this were false, then SynthType's ValueDesc::create() |
| // would use a DIFFERENT SchemaDB than BindItemExpr's createValueDesc() |
| // -- wrong! Assert this only once per query. |
| CMPASSERT(ActiveSchemaDB() == bindWA->getSchemaDB()); |
| |
| // set the upDateCurrentOf_ attribute for the root if possible |
| if (child(0)->getOperatorType() == REL_UNARY_UPDATE || |
| child(0)->getOperatorType() == REL_UNARY_DELETE) { |
| GenericUpdate *gu = (GenericUpdate *)child(0)->castToRelExpr(); |
| if (gu->updateCurrentOf()) { |
| updateCurrentOf() = gu->updateCurrentOf(); |
| currOfCursorName() = gu->currOfCursorName(); |
| } |
| } |
| |
| // If we are processing a rowset, |
| // then the child operator is a REL_TSJ. |
| // If this is the case, and the operation is |
| // an update or delete, we need to search |
| // further to deterine its correct child |
| // operator type. |
| |
| // Otherwise, the child operator type is correct. |
| |
| if (bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->hasHostArraysInWhereClause() && |
| bindWA->getHostArraysArea()->hasInputRowsetsInSelectPredicate() == HostArraysWA::NO_ && |
| NOT bindWA->getHostArraysArea()->hasHostArraysInTuple()) |
| // ensure that we don't flag rowset selects or insert selects with rowsets in the predicate |
| { |
| if (bindWA->getHostArraysArea()->hasHostArraysInSetClause()) // includes rowset merge statements too |
| childOperType() = REL_UNARY_UPDATE; |
| else |
| childOperType() = REL_UNARY_DELETE; |
| } |
| else |
| childOperType() = child(0)->getOperator(); |
| |
| // see if we can potentially optimize the buffer sizes for |
| // oltp queries. Done for update/delete/insert-values/select-unique. |
| |
| // if scan, olt opt is possible. |
| if (childOperType() == REL_SCAN) |
| oltOptInfo().setOltOpt(TRUE); |
| |
| /* |
| // For Denali release 1, compound statements are restricted |
| // to yield at most one row; so olt opt is possible for CS. |
| // If a compound statement is not pushed down to DP2, then |
| // OLT optimization will be turned off in generator. |
| // |
| // Turn it off for Compound statement as insertion with tuple list |
| // is possible in a CS. |
| */ |
| else if (childOperType() == REL_COMPOUND_STMT) |
| oltOptInfo().setOltOpt(TRUE); |
| |
| // if INSERT...VALUES, olt opt is possible. |
| else if ((childOperType() == REL_UNARY_INSERT) && |
| (NOT child(0)->child(0) || |
| child(0)->child(0)->getOperatorType() == REL_TUPLE)) |
| oltOptInfo().setOltOpt(TRUE); |
| |
| } // isTrueRoot |
| |
| else if (checkFirstNRowsNotAllowed(bindWA)) { |
| *CmpCommon::diags() << DgSqlCode(-4102); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| BindScope *currScope = bindWA->getCurrentScope(); |
| |
| // -- MVs |
| // Check for the Refresh node before binding, because after binding it |
| // will be gone. |
| if (child(0)->getOperatorType() == REL_REFRESH) |
| setRootOfInternalRefresh(); |
| |
| // set the currect host area in bindWA for non-root stmt. |
| // fix 10-031106-4430 (RG: mxcmp failed to compile INSERT |
| // statement with rowsets within IF statement) |
| HostArraysWA *tempWA = NULL; |
| if ( NOT isTrueRoot() && getHostArraysArea() ) |
| { |
| tempWA = bindWA->getHostArraysArea(); |
| bindWA->setHostArraysArea(getHostArraysArea()); |
| } |
| |
| bindWA->setBindTrueRoot(FALSE); |
| |
| // Bind the children here to determine if we need to rollback on error |
| // for embedded update/delete's. |
| // |
| bindChildren(bindWA); |
| |
| if ( tempWA ) |
| { |
| // Restore previous environment |
| bindWA->setHostArraysArea(tempWA); |
| } |
| |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| // For SPJ, store the spOutParams_ from the bindWA in RelRoot, |
| // We need it at codegen |
| if ( bindWA->getSpOutParams ().entries ()) |
| spOutParams_ = &( bindWA->getSpOutParams ()); |
| |
| if (isTrueRoot()) { |
| |
| if (child(0)->getGroupAttr()->isEmbeddedUpdateOrDelete()) { |
| // Olt optimization is now supported for embedded updates/deletes (pub/sub |
| // thingy) for now. |
| oltOptInfo().setOltOpt(TRUE); |
| if (getFirstNRows() != -1) { |
| // [FIRST/ANY n] syntax cannot be used with an embedded update or embedded delete. |
| *CmpCommon::diags() << DgSqlCode(-4216); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| |
| // If updateCurrentOf_ not set yet |
| // Check the tree for a GenericUpdate RelExpr (anywhere in the tree) |
| // so we can set the root node accordingly. |
| GenericUpdate *gu = getGenericUpdate(this); |
| if (!updateCurrentOf() && gu && gu->updateCurrentOf()) { |
| updateCurrentOf() = gu->updateCurrentOf(); |
| currOfCursorName() = gu->currOfCursorName(); |
| } |
| |
| // if standalone update/delete(no update where current of), |
| // olt opt is possible. |
| if (((childOperType() == REL_UNARY_UPDATE) || |
| (childOperType() == REL_UNARY_DELETE)) && |
| (NOT updateCurrentOf())) |
| oltOptInfo().setOltOpt(TRUE); |
| |
| // If transaction statement (begin/commit/rollback/set xn, |
| // olt opt is possible. |
| if (childOperType() == REL_TRANSACTION) |
| oltOptInfo().setOltOpt(TRUE); |
| |
| // Set indication whether transaction need to be aborted on error |
| // during an IUD query. |
| // Rollback will be done for a query that contains |
| // rowsets, or an insert which is |
| // not an 'insert...values' with a single value. |
| // |
| // There are more cases when a transaction will be rolled back on |
| // an IUD error. These are set in GenericUpdate::preCodeGen, |
| // and DP2(IUD)::preCodeGen. |
| // These include embedded update or delete, stream access, non-unique |
| // update or delete... See ::preCodeGen methods for details. |
| rollbackOnError() = FALSE; |
| if (childOperType().match(REL_ANY_GEN_UPDATE)) |
| { |
| if (bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->done()) // rowsets |
| rollbackOnError() = TRUE; |
| else if ((childOperType() == REL_UNARY_INSERT) && |
| (child(0)->child(0) && |
| child(0)->child(0)->getOperatorType() != REL_TUPLE)) |
| rollbackOnError() = TRUE; |
| } |
| if (bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| setTolerateNonFatalError(RelExpr::NOT_ATOMIC_); |
| } |
| } |
| |
| CMPASSERT(currScope == bindWA->getCurrentScope()); // sanity check |
| |
| // do not do olt qry optimization, if rowsets are present. |
| if (bindWA->getHostArraysArea() && bindWA->getHostArraysArea()->done()) |
| { |
| oltOptInfo().setOltOpt(FALSE); |
| |
| if (bindWA->getHostArraysArea()->getTolerateNonFatalError()) { |
| // we also cannot do dp2 level olt optimization if this is a non-atomic rowset insert |
| oltOptInfo().setOltEidOpt(FALSE); |
| } |
| else { |
| // but can do dp2 level olt optimization if this is "regular" rowset insert |
| oltOptInfo().setOltEidOpt(TRUE); |
| } |
| } |
| |
| // If unresolved aggregate functions have been found in the children of the |
| // root node, that would mean that we are referencing aggregates before |
| // the groupby operation is performed |
| |
| if (checkUnresolvedAggregates(bindWA)) return this; |
| |
| // A RelRoot does not have a select list for SQL update, delete, insert |
| // statements as well as when the query contains an SQL union. If a |
| // select list is absent, assign the select list of its child to it. |
| // This will propagate the selection lists of the children of the |
| // union up to the root. |
| // |
| // Detach the item expression tree for the select list and bind it. |
| // |
| ItemExpr *compExprTree = removeCompExprTree(); |
| if (NOT compExprTree) { |
| // -- for RI and Triggers |
| if (isEmptySelectList()) |
| setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA)); |
| else { |
| setRETDesc(child(0)->getRETDesc()); |
| getRETDesc()->getValueIdList(compExpr()); |
| } |
| } |
| else { |
| CMPASSERT(!currScope->context()->inSelectList()); |
| currScope->context()->inSelectList() = TRUE; |
| |
| // QSTUFF |
| // in case we are binding an embedded generic update within a view |
| // we have to rename column references using OLD or NEW as |
| // table names since we adopted the RETDesc/TableDesc of the |
| // scan node or the view scan node, i.e. the RenameTable node |
| // at the root of an expanded view. |
| |
| if (bindWA->renameToScanTable()){ |
| |
| ColReference * cr = NULL; |
| ItemExpr * itm = compExprTree; |
| NABoolean done = FALSE; |
| |
| const CorrName corr = |
| (getViewScanNode()->getOperatorType() == REL_RENAME_TABLE) ? |
| ((RenameTable *)getViewScanNode())->getTableName() : |
| ((Scan *)getViewScanNode())->getTableDesc()->getCorrNameObj(); |
| |
| while (NOT done){ |
| |
| if (itm->getOperatorType() == ITM_ITEM_LIST){ |
| cr = (ColReference *) itm->getChild(0); |
| itm = itm->getChild(1)->castToItemExpr(); |
| } |
| else { |
| cr = (ColReference *) itm; |
| done = TRUE; |
| } |
| |
| cr->getCorrNameObj().getQualifiedNameObj(). |
| setObjectName(corr.getQualifiedNameObj().getObjectName()); |
| |
| } |
| } |
| // QSTUFF |
| |
| RelRoot *viewQueryRoot = NULL; |
| StmtDDLCreateView *pCreateView = NULL; |
| if (bindWA->inViewDefinition()) { |
| pCreateView = bindWA->getCreateViewParseNode(); |
| if (pCreateView->getQueryExpression() == this) { |
| viewQueryRoot = this; |
| CMPASSERT(isTrueRoot()); |
| pCreateView->setCurViewColNum((CollIndex)0); |
| } |
| } |
| |
| // charset inference |
| compExprTree->setResolveIncompleteTypeStatus(TRUE); |
| |
| HostArraysWA * arrayWA = bindWA->getHostArraysArea() ; |
| if (arrayWA && arrayWA->hasHostArraysInTuple()) { |
| CollIndex counterRowVals = 0; |
| CMPASSERT(!bindWA->getCurrentScope()->context()->counterForRowValues()); |
| bindWA->getCurrentScope()->context()->counterForRowValues() = &counterRowVals; |
| |
| // If this query (scope) contains OLAP Window functions, then add |
| // a Sequence Operator just below the Root node. Also, if aggregates |
| // exist, resolve them now. |
| // |
| setRETDesc(bindRowValues(bindWA, compExprTree, compExpr(), this, isTrueRoot())); |
| bindWA->getCurrentScope()->context()->counterForRowValues() = NULL; |
| } |
| else { |
| setRETDesc(bindRowValues(bindWA, compExprTree, compExpr(), viewQueryRoot, isTrueRoot())); |
| } |
| if (bindWA->errStatus()) return NULL; |
| |
| if (viewQueryRoot) pCreateView->resetCurViewColNum(); |
| |
| currScope->context()->inSelectList() = FALSE; |
| } |
| |
| // MVs -- |
| if (bindWA->isPropagateOpAndSyskeyColumns() && |
| child(0)->getOperatorType()!=REL_GROUPBY && |
| child(0)->getOperatorType()!=REL_AGGREGATE && |
| currScope->getUnresolvedAggregates().isEmpty() && |
| !isEmptySelectList() && |
| !isTrueRoot()) |
| getRETDesc()->propagateOpAndSyskeyColumns(bindWA, TRUE); |
| |
| CMPASSERT(currScope == bindWA->getCurrentScope()); // sanity check |
| currScope->setRETDesc(getRETDesc()); |
| bindWA->setRenameToScanTable(FALSE); // QSTUFF |
| |
| // Genesis 10-980106-2038 + 10-990202-1098. |
| // |
| if (isTrueRoot()) { |
| castComputedColumnsToAnsiTypes(bindWA, getRETDesc(), compExpr()); |
| if (bindWA->errStatus()) return NULL; |
| } |
| |
| // Genesis 10-970822-2581. See finalize() in SqlParser.y. |
| // |
| // If we are in a compound statement (an IF's UNION), do not issue an error. |
| // |
| // Added condition for CALL StoredProcedures |
| // If we invoke a CALL statement, the #out params do not match the |
| // # columns, we make that check in the CallSP::bindNode, so ignore it |
| // for now. |
| if (isTrueRoot() && |
| (child(0)->getOperatorType() != REL_CALLSP && |
| (child(0)->getOperatorType() != REL_COMPOUND_STMT && |
| (child(0)->getOperatorType() != REL_TUPLE && |
| (Int32)getRETDesc()->getDegree() != 0))) && |
| (child(0)->getOperatorType() != REL_UNION || |
| (!((Union *) (RelExpr *) child(0))->getUnionForIF())) && |
| outputVarCntValid() && |
| outputVarCnt() != (Int32)getRETDesc()->getDegree() && |
| (outputVarCnt() || |
| CmpCommon::context()->GetMode() != STMT_DYNAMIC)) { |
| // 4093 The number of output parameters ($0) must equal the number of cols |
| // 4094 The number of output host vars ($0) must equal the number of cols |
| Lng32 sqlcode = (CmpCommon::context()->GetMode() == STMT_DYNAMIC) ? |
| -4093 : -4094; |
| *CmpCommon::diags() << DgSqlCode(sqlcode) |
| #pragma nowarn(1506) // warning elimination |
| << DgInt0(outputVarCnt()) << DgInt1(getRETDesc()->getDegree()); |
| #pragma warn(1506) // warning elimination |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| ItemExpr *inputVarTree = removeInputVarTree(); |
| if (inputVarTree) { |
| inputVarTree->convertToValueIdList(inputVars(), bindWA, ITM_ITEM_LIST); |
| if (bindWA->errStatus()) return NULL; |
| |
| // If DYNAMIC SQL compilation, then |
| // remove from the input var list (list of HostVars and DynamicParams) |
| // any env vars that were found to have a equivalence value which is |
| // valid (parseable) for the context it appears in |
| // (i.e., we've already bound the env var name's dynamic value, |
| // so we no longer need the env var name at all). |
| // Right now, this means that in sqlci you can say |
| // set envvar xyz cat.sch.tbl; |
| // select * from $xyz; |
| // |
| if (CmpCommon::context()->GetMode() == STMT_DYNAMIC) { |
| for (CollIndex i = inputVars().entries(); i--; ) { |
| HostVar *hostVar = (HostVar *)inputVars()[i].getItemExpr(); |
| if (hostVar->getOperatorType() == ITM_HOSTVAR && |
| hostVar->isPrototypeValid() && |
| (hostVar->isEnvVar() || |
| hostVar->isDefine())) |
| inputVars().removeAt(i); |
| } |
| } // STMT_DYNAMIC |
| } // inputVarTree |
| |
| // add to the inputVars, any user functions that are to be treated |
| // like input values, that is, evaluated once and used therafter. |
| // Do not insert duplicate value ids. |
| for (CollIndex i = 0; i < bindWA->inputFunction().entries(); i++ ) { |
| if (NOT inputVars().contains(bindWA->inputFunction()[i])) |
| inputVars().insert(bindWA->inputFunction()[i]); |
| } |
| |
| // If aggregate functions have been found in the select list, then |
| // create a groupby node with an empty groupby list, if the child is not |
| // already a groupby node. |
| // |
| resolveAggregates(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| |
| // Add the unresolvedSequenceFunctions to the Sequence node for this |
| // scope. If there are sequence functions, but no sequence node, it |
| // is an error. Also if there is a sequence node, but no sequence |
| // functions, it is an error. |
| // If OLAP Window functions exist for this scope, they will have been |
| // translated into sequence functions by this point and so will be added |
| // to the Sequence node here. |
| // |
| resolveSequenceFunctions(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| |
| BindScope *prevScope = bindWA->getPreviousScope(currScope); |
| NABoolean inRowSubquery = FALSE; |
| if (prevScope) |
| inRowSubquery = prevScope->context()->inRowSubquery(); |
| |
| if (inRowSubquery && (CmpCommon::getDefault(COMP_BOOL_137) == DF_OFF)) |
| addOneRowAggregates(bindWA); |
| |
| returnedRoot = |
| transformGroupByWithOrdinalPhase2(bindWA); |
| if (! returnedRoot) |
| return NULL; |
| |
| ItemExpr *orderByTree = removeOrderByTree(); |
| if (orderByTree) { |
| // |
| // Tandem extension to ANSI (done only if source table is not grouped!): |
| // Allow the ORDER BY clause to reference columns in the source table even |
| // if the columns are not referenced in the select list. Treat the extra |
| // columns as *system* columns so that they can be referenced by name |
| // (ORDER BY name) but not by position in select list (ORDER BY n). |
| // Thus, select-list columns have precedence, as they should since ANSI |
| // allows only them in ORDER BY to begin with! |
| // |
| // Add all source columns to system column list of temporary orderBy; |
| // remove select-list columns from this system column list; |
| // insert select-list columns into the *user* column list |
| // (these must be in separate loops to set up the orderBy XCNM correctly!). |
| // Then bind the temporary (convert to ValueId list), reset the RETDesc. |
| // |
| bindWA->getCurrentScope()->context()->inOrderBy() = TRUE; |
| CollIndex i; |
| RETDesc orderBy; |
| const RETDesc &select = *getRETDesc(); |
| const RETDesc &source = *child(0)->getRETDesc(); |
| |
| // if the source is grouped, then the ORDER BY columns must be in |
| // the select list. So, don't add any other columns that aren't |
| // in the select list... |
| if (source.isGrouped()) { |
| orderBy.setGroupedFlag(); |
| //10-031125-1549 -begin |
| //Since we are processing a groupby we should |
| //certainly have some node below it. Futher if |
| //that node is a REL_ROOT we will certainly have |
| //a child. So this rather unusual call sequence |
| //is safe. We are actually looking for a Pattern |
| //like REL_GROUPBY(REL_ROOT(*)) introduced to handle |
| //Distint qualifier. |
| //for example if we have a query like |
| //select distinct j as jcol from t1 order by j; |
| //the tree will look like |
| //REL_ROOT(REL_GROUPBY(REL_ROOT(REL_SCAN(t1)))) |
| //In this is a NON-ANSI query. To support queries like this |
| //we need to expose "J" as a system column. To do that we need |
| //to get hold of the RetDesc of the node below the REL_ROOT |
| //(not the actual REL_ROOT). |
| RETDesc *src = NULL; |
| if(child(0)->child(0)&& |
| child(0)->child(0)->getOperatorType() == REL_ROOT) |
| { |
| src = child(0)->child(0)->child(0)->getRETDesc(); |
| } |
| else |
| { |
| src = child(0)->getRETDesc(); |
| } |
| |
| const ColumnDescList &sysColList = *src->getSystemColumnList(); |
| const ColumnDescList &usrColList = *src->getColumnList(); |
| ValueId vid; |
| |
| for(i = 0; i < select.getDegree(); i++) { |
| vid = select.getValueId(i); |
| for(CollIndex j = 0; j < sysColList.entries(); j++){ |
| if( vid == sysColList[j]->getValueId()){ |
| orderBy.addColumn(bindWA, sysColList[j]->getColRefNameObj() |
| , sysColList[j]->getValueId() |
| , SYSTEM_COLUMN); |
| } |
| } |
| for(CollIndex k = 0; k < usrColList.entries(); k++){ |
| if(vid == usrColList[k]->getValueId()){ |
| orderBy.addColumn(bindWA, usrColList[k]->getColRefNameObj() |
| , usrColList[k]->getValueId() |
| , SYSTEM_COLUMN); |
| } |
| } |
| } |
| //10-031125-1549 -end |
| |
| NABoolean specialMode = TRUE; |
| |
| |
| // In specialMode, we want to support order by on columns |
| // which are not explicitely specified in the select list. |
| // Ex: select a+1 from t group by a order by a; |
| // Find all the column references in the orderByTree which are |
| // also in the group by list but are not explicitely specified |
| // in the select list. |
| // This code path is for cases when both GROUP BY and ORDER BY are |
| // specified. |
| // If order by is specified without the group by, then that case |
| // is already covered in the 'else' portion. |
| if ((specialMode) && |
| (child(0)->getOperatorType() == REL_GROUPBY) && |
| (allOrderByRefsInGby_)) // already validated that all order by cols |
| // are also in group by clause |
| { |
| ItemExprList orderByList(orderByTree, bindWA->wHeap()); |
| GroupByAgg * grby=(GroupByAgg *)(child(0)->castToRelExpr()); |
| for (CollIndex ii = 0; ii < orderByList.entries(); ii++) |
| { |
| ItemExpr * colRef = orderByList[ii]; |
| if (colRef->getOperatorType() == ITM_INVERSE) |
| colRef = colRef->child(0)->castToItemExpr(); |
| if (colRef && colRef->getOperatorType() == ITM_REFERENCE) |
| { |
| ColReference * obyColRef = (ColReference*)colRef; |
| |
| for (CollIndex k = 0; k < usrColList.entries(); k++) |
| { |
| if (obyColRef->getColRefNameObj().getColName() == |
| usrColList[k]->getColRefNameObj().getColName()) |
| { |
| orderBy.delColumn(bindWA, |
| usrColList[k]->getColRefNameObj(), |
| SYSTEM_COLUMN); |
| |
| orderBy.addColumn(bindWA, |
| usrColList[k]->getColRefNameObj(), |
| usrColList[k]->getValueId(), |
| SYSTEM_COLUMN); |
| break; |
| } // if |
| } // for |
| |
| } // if |
| } // for |
| } |
| |
| for (i = 0; i < select.getDegree(); i++) |
| orderBy.delColumn(bindWA, select.getColRefNameObj(i), SYSTEM_COLUMN); |
| } |
| else { |
| // add the potential ORDER BY columns... omitting the ones that will |
| // in the select list anyway. |
| orderBy.addColumns(bindWA, *source.getColumnList(), SYSTEM_COLUMN); |
| orderBy.addColumns(bindWA, *source.getSystemColumnList(), SYSTEM_COLUMN); |
| |
| for (i = 0; i < select.getDegree(); i++) |
| orderBy.delColumn(bindWA, select.getColRefNameObj(i), SYSTEM_COLUMN); |
| } |
| |
| for (i = 0; i < select.getDegree(); i++) |
| orderBy.addColumn(bindWA, select.getColRefNameObj(i), |
| select.getValueId(i), USER_COLUMN); |
| |
| bindWA->getCurrentScope()->setRETDesc(&orderBy); |
| |
| // fix for defect 10-010522-2978 |
| // If we need to move this OrderBy to the RelRoot above this one... |
| // move it to the rowsetReqdOrder_ of that RelRoot, otherwise keep |
| // it at this level... in the current RelRoot's reqdOrder_ |
| |
| ValueIdList & pRRO = getParentForRowsetReqdOrder() ? |
| getParentForRowsetReqdOrder()->rowsetReqdOrder_ : |
| reqdOrder(); |
| |
| // Replace any selIndexies in the orderByTree with what it refers to |
| // before we expand it. |
| // This is done so that we can deal with subqueries with degree > 1 |
| // and MVFs. |
| |
| ItemExpr *sPtr = orderByTree, *ePtr = orderByTree; |
| |
| Int32 childIdx = 0; |
| NABoolean onlyOneEntry(TRUE); |
| |
| CollIndex selListCount = compExpr().entries(); |
| |
| while (sPtr != NULL) |
| { |
| |
| if (sPtr->getOperatorType() == ITM_ITEM_LIST) |
| { |
| ePtr = sPtr; |
| sPtr = ePtr->child(0); |
| childIdx = 0; |
| onlyOneEntry = FALSE; |
| } |
| if (sPtr->getOperatorType() == ITM_SEL_INDEX) |
| { |
| SelIndex * si = (SelIndex*)(sPtr); |
| |
| CollIndex selIndex = si->getSelIndex(); |
| if(selIndex == 0 || selIndex > selListCount) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4007) |
| << DgInt0((Lng32)si->getSelIndex()) |
| << DgInt1(selListCount); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| ValueId orderById = compExpr()[si->getSelIndex()-1]; |
| if (ePtr->getOperatorType() == ITM_ITEM_LIST) |
| ePtr->child(childIdx) = orderById.getItemExpr(); |
| else |
| ePtr = orderById.getItemExpr(); |
| |
| orderById.getItemExpr()->setInOrderByOrdinal(TRUE); |
| } |
| if ((ePtr->getArity() == 2) && ePtr->child(1) != NULL && |
| ePtr->child(1)->getOperatorType() != ITM_ITEM_LIST && |
| childIdx != 1) |
| childIdx = 1; |
| else |
| childIdx = 0; |
| sPtr = (childIdx == 1) ? ePtr->child(1) : NULL; |
| } |
| |
| if (onlyOneEntry) |
| orderByTree = ePtr; |
| |
| // If we had any ordinal expressions expand them in case there |
| // are any UDFs or subquery of degree > 1. |
| // Also expand any directly referenced UDFs and subqueries of degree > 1. |
| ItemExprList origOrderByList(orderByTree, bindWA->wHeap()); |
| |
| origOrderByList.convertToItemExpr()-> |
| convertToValueIdList(pRRO, bindWA, ITM_ITEM_LIST); |
| |
| // end fix for defect 10-010522-2978 |
| |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| bindWA->getCurrentScope()->context()->inOrderBy() = FALSE; |
| } |
| |
| // validate that select list doesn't contain any expressions that cannot be |
| // grouped or ordered. |
| for (Lng32 selIndex = 0; selIndex < compExpr().entries(); selIndex++) |
| { |
| ItemExpr * ie = compExpr()[selIndex].getItemExpr(); |
| if ((ie->inGroupByOrdinal()) || (ie->inOrderByOrdinal())) |
| { |
| if (NOT ie->canBeUsedInGBorOB(TRUE)) |
| { |
| return NULL; |
| } |
| } |
| } |
| |
| if (hasPartitionBy()) |
| { |
| ItemExpr *partByTree = removePartitionByTree(); |
| partByTree->convertToValueIdSet(partArrangement_, bindWA, ITM_ITEM_LIST); |
| if (bindWA->errStatus()) return NULL; |
| } |
| |
| // fix for defect 10-010522-2978 |
| // If we're the upper level RelRoot, we must check to see if we have |
| // any entries that need to be added to reqdOrder() and add them if |
| // there are any... |
| |
| if ( rowsetReqdOrder_.entries() ) { |
| // We never expect for reqdOrder to contain any entries. But |
| // if it ever does, we want to be able to take a look at this |
| // code again to decide whether we should be appending to the |
| // reqdOrder list. Currently the code is written to append to |
| // the end of the reqdOrder list, which is likely to be the correct |
| // behavior even if there are entries in reqdOrder; we just think |
| // that someone should have the chance to rethink this in the event |
| // there are entries in reqdOrder and so we're making it fail here |
| // to allow/force someone to make the decision. |
| CMPASSERT(reqdOrder().entries() == 0); |
| |
| // note: NAList<ValueIdList>::insert(const NAList<ValueIdList> &) |
| // actually does an append to the END of the list (not an |
| // insert at the head or after the current position). |
| reqdOrder().insert( rowsetReqdOrder_ ); |
| } |
| |
| // end fix for defect 10-010522-2978 |
| |
| // Bind the update column specification of a cursor declaration. |
| // Don't remove the tree: leave it for possible error 4118 in NormRelExpr. |
| if (updateColTree_) { |
| updateColTree_->convertToValueIdList(updateCol(), bindWA, ITM_ITEM_LIST); |
| if (bindWA->errStatus()) { |
| if (CmpCommon::diags()->contains(-4001)) |
| *CmpCommon::diags() << DgSqlCode(-4117); |
| return NULL; |
| } |
| |
| if (getGroupAttr()->isEmbeddedDelete()) { // QSTUFF |
| *CmpCommon::diags() << DgSqlCode(-4169); |
| bindWA->setErrStatus() ; |
| return NULL; |
| } |
| } |
| |
| |
| // check whether a CONTROL QUERY SHAPE statement is in effect. |
| // Do not do if this is a control query statement. |
| if (ActiveControlDB()->getRequiredShape()) { |
| OperatorTypeEnum op = child(0)->getOperatorType(); |
| if (!child(0)->isAControlStatement() && |
| op != REL_DESCRIBE && |
| op != REL_EXPLAIN && |
| op != REL_DDL && |
| op != REL_LOCK && |
| op != REL_UNLOCK && |
| op != REL_SET_TIMEOUT && |
| op != REL_STATISTICS && |
| op != REL_TRANSACTION && |
| op != REL_EXE_UTIL) |
| reqdShape_ = ActiveControlDB()->getRequiredShape()->getShape(); |
| } |
| |
| // If this is a parallel extract producer query: |
| // * the number of requested streams must be greater than one and |
| // not more than the number of configured CPUs |
| // * force a shape with an ESP exchange node immediately below |
| // the root |
| ComUInt32 numExtractStreams = getNumExtractStreams(); |
| if (numExtractStreams_ > 0) |
| { |
| // Check the number of requested streams |
| NADefaults &defs = bindWA->getSchemaDB()->getDefaults(); |
| |
| NABoolean fakeEnv = FALSE; |
| ComUInt32 numConfiguredESPs = defs.getTotalNumOfESPsInCluster(fakeEnv); |
| |
| if ((numExtractStreams == 1) || (numExtractStreams > numConfiguredESPs)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4119) |
| << DgInt0((Lng32) numConfiguredESPs); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // Force the shape. There are three cases to consider: |
| // a. there is no required shape in the ControlDB |
| // b. there is a required shape and it is acceptable for this |
| // parallel extract. |
| // c. there is a required shape and it is not acceptable. |
| |
| if (reqdShape_ == NULL) |
| { |
| // Case a. |
| // Manufacture an esp_exchange(cut,N) shape |
| reqdShape_ = new (bindWA->wHeap()) |
| ExchangeForceWildCard(new (bindWA->wHeap()) CutOp(0), |
| ExchangeForceWildCard::FORCED_ESP_EXCHANGE, |
| ExchangeForceWildCard::ANY_LOGPART, |
| (Lng32) numExtractStreams_); |
| } |
| else |
| { |
| NABoolean reqdShapeIsOK = FALSE; |
| if (reqdShape_->getOperatorType() == REL_FORCE_EXCHANGE) |
| { |
| ExchangeForceWildCard *exch = (ExchangeForceWildCard *) reqdShape_; |
| ExchangeForceWildCard::forcedExchEnum whichType = exch->getWhich(); |
| Lng32 howMany = exch->getHowMany(); |
| if (whichType == ExchangeForceWildCard::FORCED_ESP_EXCHANGE && |
| howMany == (Lng32) numExtractStreams_) |
| { |
| reqdShapeIsOK = TRUE; |
| } |
| } |
| |
| if (reqdShapeIsOK) |
| { |
| // Case b. |
| // Do nothing |
| } |
| else |
| { |
| // Case c. |
| // Add an esp_exchange to the top of the required shape |
| RelExpr *child = reqdShape_; |
| reqdShape_ = new (bindWA->wHeap()) |
| ExchangeForceWildCard(child, |
| ExchangeForceWildCard::FORCED_ESP_EXCHANGE, |
| ExchangeForceWildCard::ANY_LOGPART, |
| (Lng32) numExtractStreams_); |
| } |
| |
| } // if (reqdShape_ == NULL) else ... |
| } // if (numExtractStreams_ > 0) |
| |
| // Bind the base class. |
| // |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) return boundExpr; |
| |
| // If we have dynamic rowsets, we want to replace |
| // dynamic parameters with available inputs. |
| if (isTrueRoot() && bindWA->hasDynamicRowsetsInQuery()) { |
| ValueIdSet inputs = getGroupAttr()->getCharacteristicInputs(); |
| UInt32 j = 0; |
| // this for loop is over the list of available inputs. We are replacing array |
| // parameters with hostvars introduced during HostArraysWA::processArrayHostVar |
| // The hostvars introduced in that method are contained in the inputs() list. |
| for (ValueId id = inputs.init(); inputs.next(id); inputs.advance(id)) { |
| if (id.getItemExpr()->getOperatorType() == ITM_DYN_PARAM) { |
| continue; |
| } |
| // We are assuming here that the hostvars introduced are in the same order as |
| // the parameter arrays in inputVars(), i.e. (hv_A, hv_B) corresponds to |
| // (?,?,?(as A), ?(as B)) |
| while (j < inputVars().entries()) { |
| ItemExpr *ie = inputVars()[j].getItemExpr() ; |
| OperatorTypeEnum ieType = ie->getOperatorType() ; |
| if (( ieType != ITM_DYN_PARAM) || |
| (((DynamicParam *) ie)->getRowsetSize() == 0)) |
| { |
| // if an ie is not a dynamicParam or it is a scalar dynamic Param do not remove |
| // it from inputVars_. From embedded SQL it is possible to have scalar and array |
| // dynamic params in the same statement. This is not possible from ODBC. |
| j++; |
| } |
| else |
| break ; |
| } |
| if (j < inputVars().entries()) { |
| inputVars().removeAt(j); |
| inputVars().insertAt(j, id); |
| j++; |
| } |
| } |
| } |
| |
| // RelRoot::codeGen() and Statement::execute() use TOPMOST root's accessOpts. |
| // |
| if (bindWA->getCurrentScope()->context()->stmtLevelAccessOptions()) |
| if (!accessOptions().userSpecified()) // seems redundant |
| accessOptions() = *bindWA->getCurrentScope()->context()->stmtLevelAccessOptions(); |
| |
| // Update operations currently require SERIALIZABLE (== MP REPEATABLE_) |
| // locking level -- the QSTUFF-enabled DP2 now does this, supporting a true |
| // READ_COMMITTED that is STABLE rather than merely CLEAN. |
| if (!containsGenericUpdate(this)) { |
| // Genesis 10-990114-6293: |
| // This flag tells RelRoot::codeGen to set a flagbit in the root-tdb which |
| // cli/Statement::execute + compareTransModes() will look at -- |
| // if set, then this "read-write" stmt will be allowed to execute |
| // in a run-time transmode of read-only W/O HAVING TO BE RECOMPILED. |
| readOnlyTransIsOK() = TRUE; |
| } |
| |
| if (isTrueRoot()) { |
| if (updateCurrentOf()) { |
| // add child genericupdate's primary key hostvars to pkeyList. |
| // The getLeftmostScanNode() method will return the leftmost Scan node |
| // as the original scan node may have moved due to the IM tree. |
| pkeyList().insert(child(0)->castToRelExpr()->getLeftmostScanNode()->pkeyHvarList()); |
| } |
| |
| for(Int32 st=0; st < (Int32)bindWA->getStoiList().entries(); st++) |
| { |
| if(bindWA->getStoiList()[st]->getStoi()->isView()) |
| viewStoiList_.insert(bindWA->getStoiList()[st]); |
| } |
| |
| if(bindWA->inDDL()) |
| ddlStoiList_.insert(bindWA->getStoiList()); |
| |
| // populate the list of all the routines open information of this query |
| stoiUdrList_.insert(bindWA->getUdrStoiList()); |
| |
| // populate the list of all the UDF information of this query |
| udfList_.insert(bindWA->getUDFList()); |
| |
| // check privileges |
| if (!checkPrivileges(bindWA)) |
| { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // store the trigger's list in the root |
| if (bindWA->getTriggersList()) |
| { |
| triggersList_ = |
| new (bindWA->wHeap()) LIST(ComTimestamp) |
| (bindWA->wHeap(), bindWA->getTriggersList()->entries()); |
| triggersList_->insert(*(bindWA->getTriggersList())); |
| |
| // Don't allow OLT optimization when triggers are involved. |
| oltOptInfo().setOltOpt(FALSE); |
| } |
| |
| |
| // store the uninitialized mv list if there are any |
| // entries |
| if( bindWA->getUninitializedMvList() ) |
| { |
| uninitializedMvList_ = new (bindWA->wHeap()) UninitializedMvNameList |
| (bindWA->wHeap(), bindWA->getUninitializedMvList()->entries()); |
| uninitializedMvList_->insert( *(bindWA->getUninitializedMvList()) ); |
| } |
| |
| |
| DBG( if (getenv("TVUSG_DEBUG")) bindWA->tableViewUsageList().display(); ) |
| } // isTrueRoot |
| |
| // Don't allow OLT optimization when ON STATEMENT MV refresh is involved. |
| if (bindWA->isBindingOnStatementMv()) |
| oltOptInfo().setOltOpt(FALSE); |
| |
| // disable esp parallelism for merge statements. |
| // See class RelRoot for details about this. |
| if ((isTrueRoot()) && |
| (bindWA->isMergeStatement())) |
| { |
| setDisableESPParallelism(TRUE); |
| } |
| |
| // Remove the current scope. |
| // |
| if (!isDontOpenNewScope()) // -- Triggers |
| bindWA->removeCurrentScope(); |
| |
| // In case we have a query of the form |
| // SET <host var list> = <select statement> |
| // we must update the value ids of the host variables in that list. |
| // See Assignment Statement Internal Spec (a project of Compound Statements). |
| if (assignmentStTree() && |
| bindWA->getAssignmentStArea() && |
| bindWA->getAssignmentStArea()->getAssignmentStHostVars() && |
| !bindWA->getAssignmentStArea()->getAssignmentStHostVars()-> |
| updateValueIds(compExpr(), assignmentStTree())) { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (getPredExprTree()) |
| { |
| CMPASSERT(isTrueRoot()); |
| |
| ItemExpr * ie = removePredExprTree(); |
| ie = ie->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| addPredExprTree(ie); |
| } |
| |
| if (getFirstNRowsParam()) |
| { |
| firstNRowsParam_ = firstNRowsParam_->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| const SQLInt si(FALSE, FALSE); |
| ValueId vid = firstNRowsParam_->castToItemExpr()->getValueId(); |
| vid.coerceType(si, NA_NUMERIC_TYPE); |
| |
| if (vid.getType().getTypeQualifier() != NA_NUMERIC_TYPE) |
| { |
| // 4045 must be numeric. |
| *CmpCommon::diags() << DgSqlCode(-4045) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| if ((NOT hasOrderBy()) && |
| ((getFirstNRows() != -1) || |
| (getFirstNRowsParam()))) |
| { |
| // create a firstN node to retrieve firstN rows. |
| FirstN * firstn = new(bindWA->wHeap()) |
| FirstN(child(0), getFirstNRows(), getFirstNRowsParam()); |
| firstn->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| setChild(0, firstn); |
| |
| // reset firstN indication in the root node. |
| setFirstNRows(-1); |
| setFirstNRowsParam(NULL); |
| } |
| |
| // if we have no user-specified access options then |
| // get it from nearest enclosing scope that has one (if any) |
| if (!accessOptions().userSpecified()) { |
| StmtLevelAccessOptions *axOpts = bindWA->findUserSpecifiedAccessOption(); |
| if (axOpts) { |
| accessOptions() = *axOpts; |
| } |
| } |
| |
| if (bindWA->getHoldableType() == SQLCLIDEV_ANSI_HOLDABLE) |
| { |
| if (accessOptions().accessType() != ACCESS_TYPE_NOT_SPECIFIED_) |
| { |
| if (accessOptions().accessType() == REPEATABLE_) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4381); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| else |
| { |
| TransMode::IsolationLevel il=CmpCommon::transMode()->getIsolationLevel(); |
| if (CmpCommon::transMode()->ILtoAT(il) == REPEATABLE_ ) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4381); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| } |
| |
| // The above code is in Scan::bindNode also. |
| // It would be nice to refactor this common code; someday. |
| |
| return boundExpr; |
| } // RelRoot::bindNode() |
| |
| // Present the select list as a tree of Item Expressions |
| ItemExpr *RelRoot::selectList() |
| { |
| return compExpr().rebuildExprTree(ITM_ITEM_LIST); |
| } // RelRoot::selectList() |
| |
| // Returns current place that assignmentStTree_ points to and |
| // sets that pointer to NULL |
| // LCOV_EXCL_START - cnu |
| ItemExpr * RelRoot::removeAssignmentStTree() |
| { |
| ItemExpr* tempTree = assignmentStTree_; |
| assignmentStTree_ = NULL; |
| return tempTree; |
| } |
| // LCOV_EXCL_STOP |
| |
| bool OptSqlTableOpenInfo::checkColPriv(const PrivType privType, |
| const PrivMgrUserPrivs *pPrivInfo) |
| { |
| CMPASSERT (pPrivInfo); |
| |
| NATable* table = getTable(); |
| NAString columns = ""; |
| |
| if (!isColumnPrivType(privType)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4481) |
| << DgString0(PrivMgrUserPrivs::convertPrivTypeToLiteral(privType).c_str()) |
| << DgString1(table->getTableName().getQualifiedNameAsAnsiString()) |
| << DgString2(columns); |
| return false; |
| } |
| |
| bool hasPriv = true; |
| |
| // initialize to something, gets set appropriately below |
| LIST (Lng32) * colList = NULL ; |
| switch (privType) |
| { |
| case INSERT_PRIV: |
| { |
| colList = (LIST (Lng32) *)&(getInsertColList()); |
| break; |
| } |
| case UPDATE_PRIV: |
| { |
| colList = (LIST (Lng32) *)&(getUpdateColList()); |
| break; |
| } |
| case SELECT_PRIV: |
| { |
| colList = (LIST (Lng32) *)&(getSelectColList()); |
| break; |
| } |
| default: |
| CMPASSERT(FALSE); // delete has no column privileges. |
| } |
| |
| bool collectColumnNames = false; |
| if (pPrivInfo->hasAnyColPriv(privType)) |
| { |
| collectColumnNames = true; |
| columns += "(columns:" ; |
| } |
| bool firstColumn = true; |
| for(size_t i = 0; i < colList->entries(); i++) |
| { |
| size_t columnNumber = (*colList)[i]; |
| if (!(pPrivInfo->hasColPriv(privType,columnNumber))) |
| { |
| hasPriv = false; |
| if (firstColumn && collectColumnNames) |
| { |
| columns += " "; |
| firstColumn = false; |
| } |
| else |
| if (collectColumnNames) |
| columns += ", "; |
| |
| if (collectColumnNames) |
| columns += table->getNAColumnArray()[columnNumber]->getColName(); |
| } |
| } |
| |
| if (collectColumnNames) |
| columns += ")" ; |
| |
| // (colList->entries() == 0) ==> we have a select count(*) type query or a |
| // select 1 from T type query. In other words the table needs to be accessed |
| // but no column has been explicitly referenced. |
| // For such queries if the user has privilege on any one column that is |
| // sufficient. collectColumnNames indicates whether the user has privilege |
| // on at least one column. The following if statement applies only to selects |
| // For update and insert we do not expect colList to be empty. |
| |
| if ((colList->entries() == 0)&& !collectColumnNames) |
| { |
| hasPriv = false; |
| columns = ""; |
| } |
| |
| if (!hasPriv) |
| *CmpCommon::diags() << DgSqlCode(-4481) |
| << DgString0(PrivMgrUserPrivs::convertPrivTypeToLiteral(privType).c_str()) |
| << DgString1(table->getTableName().getQualifiedNameAsAnsiString()) |
| << DgString2(columns); |
| |
| return hasPriv; |
| |
| } |
| |
| |
| NABoolean RelRoot::checkFirstNRowsNotAllowed(BindWA *bindWA) |
| { |
| // do not call this method on a true root. |
| CMPASSERT(NOT isTrueRoot()); |
| |
| //***************************************************************** |
| // FirstNRows >= 0 (for FirstN) |
| // == -2 For Last 0 |
| // == -3 For Last 1 |
| // These values are set in parser; see the code SqlParser.y under |
| // Non-Terminal querySpecification when fisrtN is specified |
| //****************************************************************** |
| |
| if ( (getFirstNRows() >= 0 || |
| getFirstNRows() == -2 || |
| getFirstNRows() == -3) && // this root has firstn |
| (!((getInliningInfo().isEnableFirstNRows()) || |
| (getHostArraysArea() && getHostArraysArea()->getHasSelectIntoRowsets()) || //firstn is allowed with a rowset subroot |
| (assignmentStTree())))) // first n is allowed in a CS. Presence of assignmentStTree |
| // on a non true root implies presence of select into statement |
| // within a cs |
| { |
| // 4102 The [FIRST/ANY n] syntax can only be used in an outermost SELECT statement. |
| if (CmpCommon::getDefault(ALLOW_FIRSTN_IN_SUBQUERIES) == DF_OFF) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // Method: checkPrivileges |
| // |
| // This method: |
| // - Verifies that the user executing the query has the necessary privileges |
| // - Adds security keys to RelRoot class that need to be checked when priv |
| // changes (revokes) are performed. Security keys are part of the Query |
| // Invalidation feature. |
| // - Also, removes any previously cached entries if the user has no priv |
| // |
| // Input: pointer to the binder work area |
| // Output: result of the check |
| // TRUE - user has priv |
| // FALSE - user does not have priv or unexpected error occurred |
| // |
| // The ComDiags area is populated with error details |
| // The BindWA flag setFailedForPrivileges is set to TRUE if priv check fails |
| // ---------------------------------------------------------------------------- |
| NABoolean RelRoot::checkPrivileges(BindWA* bindWA) |
| { |
| // If internal caller and not part of explain, then return |
| if (Get_SqlParser_Flags(INTERNAL_QUERY_FROM_EXEUTIL)) |
| return TRUE; |
| |
| // If qiPath (used for testing) is not 0, skip root user check |
| NAString qiPath = ""; |
| CmpCommon::getDefault(QI_PATH, qiPath, FALSE); |
| if (qiPath.length() == 0 && ComUser::isRootUserID()) |
| return TRUE; |
| |
| // See if there is anything to check |
| // StoiList contains any tables used in the query |
| // UdrStoiList contains any routines used in the query |
| // CoProcAggrList contains any queries using the aggregate co-processor |
| // SeqValList contains any sequences |
| if (bindWA->getStoiList().entries() == 0 && |
| bindWA->getUdrStoiList().entries() == 0 && |
| bindWA->getCoProcAggrList().entries() == 0 && |
| bindWA->getSeqValList().entries() == 0) |
| return TRUE; |
| |
| // If authorization is not enabled, then return TRUE |
| if (!CmpCommon::context()->isAuthorizationEnabled()) |
| return TRUE; |
| |
| ComBoolean QI_enabled = (CmpCommon::getDefault(CAT_ENABLE_QUERY_INVALIDATION) == DF_ON); |
| NABoolean RemoveNATableEntryFromCache = FALSE ; |
| |
| // Have the ComSecurityKey constructor compute the hash value for the the User's ID. |
| // Note: The following code doesn't care about the object's hash value or the resulting |
| // ComSecurityKey's ActionType....we just need the hash value for the User's ID. |
| int64_t objectUID = 12345; |
| Int32 thisUserID = ComUser::getCurrentUser(); |
| ComSecurityKey userKey( thisUserID , objectUID |
| , SELECT_PRIV |
| , ComSecurityKey::OBJECT_IS_OBJECT |
| ); |
| uint32_t userHashValue = userKey.getSubjectHashValue(); |
| |
| |
| // Set up a PrivMgrCommands class in case we need to get privilege information |
| NAString privMDLoc; |
| CONCAT_CATSCH(privMDLoc,CmpSeabaseDDL::getSystemCatalogStatic(),SEABASE_PRIVMGR_SCHEMA); |
| PrivMgrCommands privInterface(privMDLoc.data(), CmpCommon::diags(), PrivMgr::PRIV_INITIALIZED); |
| PrivStatus retcode = STATUS_GOOD; |
| |
| // ==> Check privileges for tables used in the query. |
| SqlTableOpenInfo * stoi = NULL ; |
| OptSqlTableOpenInfo * optStoi = NULL; |
| for(Int32 i=0; i<(Int32)bindWA->getStoiList().entries(); i++) |
| { |
| RemoveNATableEntryFromCache = FALSE ; // Initialize each time through loop |
| optStoi = (bindWA->getStoiList())[i]; |
| stoi = optStoi->getStoi(); |
| NATable* tab = optStoi->getTable(); |
| |
| // System metadata tables do not, by default, have privileges stored in the |
| // NATable structure. Go ahead and retrieve them now. |
| PrivMgrUserPrivs *pPrivInfo = tab->getPrivInfo(); |
| PrivMgrUserPrivs privInfo; |
| if (!pPrivInfo) |
| { |
| CmpSeabaseDDL cmpSBD(STMTHEAP); |
| if (cmpSBD.switchCompiler(CmpContextInfo::CMPCONTEXT_TYPE_META)) |
| { |
| if (CmpCommon::diags()->getNumber(DgSqlCode::ERROR_) == 0) |
| *CmpCommon::diags() << DgSqlCode( -4400 ); |
| return FALSE; |
| } |
| retcode = privInterface.getPrivileges( tab, thisUserID, privInfo); |
| cmpSBD.switchBackCompiler(); |
| |
| if (retcode != STATUS_GOOD) |
| { |
| tab->setRemoveFromCacheBNC(TRUE); |
| bindWA->setFailedForPrivileges(TRUE); |
| *CmpCommon::diags() << DgSqlCode( -1034 ); |
| return FALSE; |
| } |
| pPrivInfo = &privInfo; |
| } |
| |
| // Check each primary DML privilege to see if the query requires it. If |
| // so, verify that the user has the privilege |
| bool insertQIKeys = (QI_enabled && tab->getSecKeySet().entries() > 0); |
| for (int_32 i = FIRST_DML_PRIV; i <= LAST_PRIMARY_DML_PRIV; i++) |
| { |
| if (stoi->getPrivAccess((PrivType)i)) |
| { |
| if (!pPrivInfo->hasPriv((PrivType)i) && !optStoi->checkColPriv((PrivType)i, pPrivInfo)) |
| RemoveNATableEntryFromCache = TRUE; |
| else |
| if (insertQIKeys) |
| findKeyAndInsertInOutputList(tab->getSecKeySet(),userHashValue,(PrivType)(i)); |
| } |
| } |
| |
| // wait until all the primary DML privileges have been checked before |
| // setting failure information |
| if ( RemoveNATableEntryFromCache ) |
| { |
| bindWA->setFailedForPrivileges( TRUE ); |
| tab->setRemoveFromCacheBNC(TRUE); // To be removed by CmpMain before Compilation retry |
| } |
| } // for loop over tables in stoi list |
| |
| // ==> Check privileges for functions and procedures used in the query. |
| NABoolean RemoveNARoutineEntryFromCache = FALSE ; |
| if (bindWA->getUdrStoiList().entries()) |
| { |
| for(Int32 i=0; i<(Int32)bindWA->getUdrStoiList().entries(); i++) |
| { |
| // Privilege info for the user/routine combination is stored in the |
| // NARoutine object. |
| OptUdrOpenInfo *udrStoi = (bindWA->getUdrStoiList())[i]; |
| NARoutine* rtn = udrStoi->getNARoutine(); |
| PrivMgrUserPrivs *pPrivInfo = rtn->getPrivInfo(); |
| |
| NABoolean insertQIKeys = FALSE; |
| if (QI_enabled && (rtn->getSecKeySet().entries() > 0)) |
| insertQIKeys = TRUE; |
| |
| if (pPrivInfo == NULL) |
| { |
| RemoveNARoutineEntryFromCache = TRUE ; |
| *CmpCommon::diags() << DgSqlCode( -1034 ); |
| } |
| |
| // Verify that the user has execute priv |
| else |
| { |
| if (pPrivInfo->hasPriv(EXECUTE_PRIV)) |
| { |
| // do this only if QI is enabled and object has security keys defined |
| if ( insertQIKeys ) |
| findKeyAndInsertInOutputList(rtn->getSecKeySet(), userHashValue, EXECUTE_PRIV); |
| } |
| |
| // plan requires privilege but user has none, report an error |
| else |
| { |
| RemoveNARoutineEntryFromCache = TRUE ; |
| *CmpCommon::diags() |
| << DgSqlCode( -4482 ) |
| << DgString0( "EXECUTE" ) |
| << DgString1( udrStoi->getUdrName() ); |
| } |
| } |
| |
| if ( RemoveNARoutineEntryFromCache ) |
| { |
| bindWA->setFailedForPrivileges(TRUE); |
| |
| // If routine exists in cache, add it to the list to remove |
| NARoutineDB *pRoutineDBCache = bindWA->getSchemaDB()->getNARoutineDB(); |
| NARoutineDBKey key(rtn->getSqlName(), bindWA->wHeap()); |
| NARoutine *cachedNARoutine = pRoutineDBCache->get(bindWA, &key); |
| if (cachedNARoutine != NULL) |
| pRoutineDBCache->moveRoutineToDeleteList(cachedNARoutine, &key); |
| } |
| } // for loop over UDRs |
| } // end if any UDRs. |
| |
| // ==> Check privs on any CoprocAggrs used in the query. |
| for (Int32 i=0; i<(Int32)bindWA->getCoProcAggrList().entries(); i++) |
| { |
| RemoveNATableEntryFromCache = FALSE ; // Initialize each time through loop |
| ExeUtilHbaseCoProcAggr *coProcAggr = (bindWA->getCoProcAggrList())[i]; |
| NATable* tab = bindWA->getSchemaDB()->getNATableDB()-> |
| get(coProcAggr->getCorrName(), bindWA, NULL); |
| |
| Int32 numSecKeys = 0; |
| |
| // Privilege info for the user/table combination is stored in the NATable |
| // object. |
| PrivMgrUserPrivs* pPrivInfo = tab->getPrivInfo(); |
| PrivMgrUserPrivs privInfo; |
| |
| // System metadata tables do not, by default, have privileges stored in the |
| // NATable structure. Go ahead and retrieve them now. |
| if (!pPrivInfo) |
| { |
| CmpSeabaseDDL cmpSBD(STMTHEAP); |
| if (cmpSBD.switchCompiler(CmpContextInfo::CMPCONTEXT_TYPE_META)) |
| { |
| if (CmpCommon::diags()->getNumber(DgSqlCode::ERROR_) == 0) |
| *CmpCommon::diags() << DgSqlCode( -4400 ); |
| return FALSE; |
| } |
| retcode = privInterface.getPrivileges( tab, thisUserID, privInfo); |
| cmpSBD.switchBackCompiler(); |
| |
| if (retcode != STATUS_GOOD) |
| { |
| bindWA->setFailedForPrivileges( TRUE ); |
| tab->setRemoveFromCacheBNC(TRUE); // To be removed by CmpMain before Compilation retry |
| *CmpCommon::diags() << DgSqlCode( -1034 ); |
| return FALSE; |
| } |
| pPrivInfo = &privInfo; |
| } |
| |
| // Verify that the user has select priv |
| // Select priv is needed for EXPLAIN requests, so no special check is done |
| NABoolean insertQIKeys = FALSE; |
| if (QI_enabled && (tab->getSecKeySet().entries()) > 0) |
| insertQIKeys = TRUE; |
| if (pPrivInfo->hasPriv(SELECT_PRIV)) |
| { |
| // do this only if QI is enabled and object has security keys defined |
| if ( insertQIKeys ) |
| findKeyAndInsertInOutputList(tab->getSecKeySet(), userHashValue, SELECT_PRIV ); |
| } |
| |
| // plan requires privilege but user has none, report an error |
| else |
| { |
| bindWA->setFailedForPrivileges( TRUE ); |
| tab->setRemoveFromCacheBNC(TRUE); // To be removed by CmpMain before Compilation retry |
| *CmpCommon::diags() |
| << DgSqlCode( -4481 ) |
| << DgString0( "SELECT" ) |
| << DgString1( tab->getTableName().getQualifiedNameAsAnsiString() ); |
| } |
| } // for loop over coprocs |
| |
| // ==> Check privs on any sequence generators used in the query. |
| for (Int32 i=0; i<(Int32)bindWA->getSeqValList().entries(); i++) |
| { |
| RemoveNATableEntryFromCache = FALSE ; // Initialize each time through loop |
| SequenceValue *seqVal = (bindWA->getSeqValList())[i]; |
| NATable* tab = const_cast<NATable*>(seqVal->getNATable()); |
| CMPASSERT(tab); |
| |
| // get privilege information from the NATable structure |
| PrivMgrUserPrivs *pPrivInfo = tab->getPrivInfo(); |
| PrivMgrUserPrivs privInfo; |
| if (!pPrivInfo) |
| { |
| CmpSeabaseDDL cmpSBD(STMTHEAP); |
| if (cmpSBD.switchCompiler(CmpContextInfo::CMPCONTEXT_TYPE_META)) |
| { |
| if (CmpCommon::diags()->getNumber(DgSqlCode::ERROR_) == 0) |
| *CmpCommon::diags() << DgSqlCode( -4400 ); |
| return FALSE; |
| } |
| retcode = privInterface.getPrivileges(tab, thisUserID, privInfo); |
| cmpSBD.switchBackCompiler(); |
| if (retcode != STATUS_GOOD) |
| { |
| bindWA->setFailedForPrivileges(TRUE); |
| tab->setRemoveFromCacheBNC(TRUE); // Not used until sequences stored in table cache |
| *CmpCommon::diags() << DgSqlCode( -1034 ); |
| return FALSE; |
| } |
| pPrivInfo = &privInfo; |
| } |
| |
| // Verify that the user has usage priv |
| NABoolean insertQIKeys = FALSE; |
| if (QI_enabled && (tab->getSecKeySet().entries()) > 0) |
| insertQIKeys = TRUE; |
| if (pPrivInfo->hasPriv(USAGE_PRIV)) |
| { |
| // do this only if QI is enabled and object has security keys defined |
| if ( insertQIKeys ) |
| findKeyAndInsertInOutputList(tab->getSecKeySet(), userHashValue, USAGE_PRIV ); |
| } |
| |
| // plan requires privilege but user has none, report an error |
| else |
| { |
| bindWA->setFailedForPrivileges( TRUE ); |
| tab->setRemoveFromCacheBNC(TRUE); // To be removed by CmpMain before Compilation retry |
| *CmpCommon::diags() |
| << DgSqlCode( -4491 ) |
| << DgString0( "USAGE" ) |
| << DgString1( tab->getTableName().getQualifiedNameAsAnsiString()); |
| } |
| } // for loop over sequences |
| |
| return !bindWA->failedForPrivileges() ; |
| } |
| |
| // **************************************************************************** |
| // method: findKeyAndInsertInOutputList |
| // |
| // This method searches through the list of security keys associated with the |
| // object to find the best candidate to save in the plan based on the |
| // privilege required. If it finds a candidate, it inserts the best candidate |
| // into securityKeySet_ member of the RelRoot class. |
| // |
| // Security key types currently include: |
| // COM_QI_OBJECT_<priv>: privileges granted directly to the user |
| // COM_QI_USER_GRANT_ROLE: privileges granted to the user via a role |
| // COM_QI_USER_GRANT_SPECIAL_ROLE: privileges granted to PUBLIC |
| // |
| // COM_QI_OBJECT_<priv> types are preferred over COM_QI_USER_GRANT_ROLE. |
| // **************************************************************************** |
| void RelRoot::findKeyAndInsertInOutputList( ComSecurityKeySet KeysForTab |
| , const uint32_t userHashValue |
| , const PrivType which |
| ) |
| { |
| // If no keys associated with object, just return |
| if (KeysForTab.entries() == 0) |
| return; |
| |
| ComSecurityKey * UserObjectKey = NULL; |
| ComSecurityKey * RoleObjectKey = NULL; |
| ComSecurityKey * UserObjectPublicKey = NULL; |
| |
| // These may be implemented at a later time |
| ComSecurityKey * UserSchemaKey = NULL; //privs granted at schema level to user |
| ComSecurityKey * RoleSchemaKey = NULL; //privs granted at schema level to role |
| |
| // Get action type for UserObjectKey based on the privilege (which) |
| // so if (which) is SELECT, then the objectActionType is COM_QI_OBJECT_SELECT |
| ComSecurityKey dummyKey; |
| ComQIActionType objectActionType = |
| dummyKey.convertBitmapToQIActionType ( which, ComSecurityKey::OBJECT_IS_OBJECT ); |
| |
| ComSecurityKey * thisKey = NULL; |
| |
| // NOTE: hashValueOfPublic will be the same for all keys, so we generate it only once. |
| uint32_t hashValueOfPublic = ComSecurityKey::SPECIAL_OBJECT_HASH; |
| |
| // Traverse List looking for ANY appropriate ComSecurityKey |
| for ( Int32 ii = 0; ii < (Int32)(KeysForTab.entries()); ii++ ) |
| { |
| thisKey = &(KeysForTab[ii]); |
| |
| // See if the key is object related |
| if ( thisKey->getSecurityKeyType() == objectActionType ) |
| { |
| if ( thisKey->getSubjectHashValue() == userHashValue ) |
| { |
| // Found a security key for the objectActionType |
| if ( ! UserObjectKey ) |
| UserObjectKey = thisKey; |
| } |
| } |
| |
| // See if the security key is role related |
| else if (thisKey->getSecurityKeyType() == COM_QI_USER_GRANT_ROLE) |
| { |
| if ( thisKey->getSubjectHashValue() == userHashValue ) |
| { |
| if (! RoleObjectKey ) |
| RoleObjectKey = thisKey; |
| } |
| } |
| |
| else if (thisKey->getSecurityKeyType() == COM_QI_USER_GRANT_SPECIAL_ROLE) |
| { |
| if (thisKey->getObjectHashValue() == hashValueOfPublic ) |
| { |
| if (! UserObjectPublicKey ) |
| UserObjectPublicKey = thisKey; |
| } |
| } |
| |
| else {;} // Not right action type, just continue traversing. |
| } |
| |
| // Determine best key, UserObjectKeys are better than RoleObjectKeys |
| ComSecurityKey * BestKey = (UserObjectKey) ? UserObjectKey : RoleObjectKey; |
| |
| if ( BestKey != NULL) |
| securityKeySet_.insert(*BestKey); |
| |
| // Add public if it exists |
| if ( UserObjectPublicKey != NULL ) |
| securityKeySet_.insert(*UserObjectPublicKey); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class GroupByAgg |
| // ----------------------------------------------------------------------- |
| |
| RelExpr *GroupByAgg::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // |
| // add any aggregate functions found in the parent node(s) |
| // |
| BindScope *currScope = bindWA->getCurrentScope(); |
| aggregateExpr_ += currScope->getUnresolvedAggregates(); |
| currScope->getUnresolvedAggregates().clear(); |
| // |
| // Bind the child nodes. |
| // |
| currScope->context()->lookAboveToDecideSubquery() = TRUE; |
| bindChildren(bindWA); |
| currScope->context()->lookAboveToDecideSubquery() = FALSE; |
| if (bindWA->errStatus()) return this; |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| |
| // QSTUFF |
| NAString fmtdList(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList(bindWA->wHeap()); |
| bindWA->getTablesInScope(xtnmList, &fmtdList); |
| |
| // can be removed when supporting aggregates on streams |
| if (getGroupAttr()->isStream()){ |
| *CmpCommon::diags() << DgSqlCode(-4162) << DgString0(fmtdList); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if ((getGroupAttr()->isEmbeddedUpdateOrDelete()) || |
| (bindWA->isEmbeddedIUDStatement())) { |
| *CmpCommon::diags() << DgSqlCode(-4163) << DgString0(fmtdList) |
| << (getGroupAttr()->isEmbeddedUpdate() ? |
| DgString1("UPDATE"):DgString1("DELETE")); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // QSTUFF |
| |
| // if unresolved aggregate functions have been found in the children of the |
| // Groupby node, that would mean that we are referencing aggregates before |
| // the groupby operation is performed |
| // |
| if (checkUnresolvedAggregates(bindWA)) |
| return this; |
| // |
| // Detach the item expression tree for the grouping column list, bind it, |
| // convert it to a ValueIdSet, and attach it to the GroupByAgg node. |
| // |
| ItemExpr *groupExprTree = removeGroupExprTree(); |
| if (groupExprTree) { |
| currScope->context()->inGroupByClause() = TRUE; |
| groupExprTree->convertToValueIdSet(groupExpr(), bindWA, ITM_ITEM_LIST); |
| |
| if (isRollup()) |
| groupExprTree->convertToValueIdList( |
| rollupGroupExprList(), bindWA, ITM_ITEM_LIST); |
| |
| currScope->context()->inGroupByClause() = FALSE; |
| if (bindWA->errStatus()) return this; |
| |
| ValueIdList groupByList(groupExpr()); |
| |
| for (CollIndex i = 0; i < groupByList.entries(); i++) |
| { |
| ValueId vid = groupByList[i]; |
| vid.getItemExpr()->setIsGroupByExpr(TRUE); |
| } |
| |
| if ((groupExprTree != NULL) && |
| (getParentRootSelectList() != NULL)) |
| { |
| RETDesc * childRETDesc = child(0)->getRETDesc(); |
| ItemExprList origSelectList(getParentRootSelectList(), bindWA->wHeap()); |
| |
| for (CollIndex i = 0; i < groupByList.entries(); i++) |
| { |
| ValueId vid = groupByList[i]; |
| if((vid.getItemExpr()->getOperatorType() == ITM_SEL_INDEX)&& |
| (((SelIndex*)(vid.getItemExpr()))->renamedColNameInGrbyClause())) |
| { |
| ULng32 indx = ((SelIndex*)(vid.getItemExpr()))->getSelIndex() - 1; |
| if (origSelectList.entries() > indx && |
| origSelectList[indx]->getOperatorType() == ITM_RENAME_COL) |
| { |
| const ColRefName &selectListColRefName = |
| *((RenameCol *)origSelectList[indx])->getNewColRefName(); |
| ColumnNameMap *baseColExpr = |
| childRETDesc->findColumn(selectListColRefName); |
| if (baseColExpr) |
| { |
| groupExpr().remove(vid); |
| groupExpr().insert(baseColExpr->getValueId()); |
| |
| if (isRollup()) |
| { |
| CollIndex idx = rollupGroupExprList().index(vid); |
| rollupGroupExprList()[idx] = baseColExpr->getValueId(); |
| } |
| |
| baseColExpr->getColumnDesc()->setGroupedFlag(); |
| origSelectList[indx]->setInGroupByOrdinal(FALSE); |
| } |
| } |
| } |
| } |
| |
| if (getSelPredTree()) |
| { |
| ItemExpr * havingPred = (ItemExpr *) getSelPredTree(); |
| |
| // see if having expr refers to any renamed col in the select list. |
| // that is NOT a name exposed by child RETDesc. |
| // If it does, replace it with SelIndex. |
| // For now, do this for special1 mode and only if the having |
| // is a simple pred of the form: col <op> value. |
| // Later, we can extend this to all kind of having pred by |
| // traversing the having pred tree and replacing renamed cols. |
| NABoolean replaced = FALSE; |
| NABoolean notAllowedWithSelIndexInHaving = FALSE; |
| replaceRenamedColInHavingWithSelIndex( |
| bindWA, havingPred, origSelectList, replaced, |
| notAllowedWithSelIndexInHaving,child(0)->getRETDesc()); |
| if (bindWA->errStatus()) |
| return this; |
| if (replaced) |
| { |
| if (notAllowedWithSelIndexInHaving) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4196) ; |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| setSelIndexInHaving(TRUE); |
| } |
| } |
| setParentRootSelectList(NULL); |
| } |
| |
| // Indicate that we are not in a scalar groupby. Any aggregate |
| // functions found in the select list or having clause cannot |
| // evaluate to NULL unless their argument is null. |
| currScope->context()->inScalarGroupBy() = FALSE; |
| } |
| // |
| // bind the having predicates and attach the resulting value id set |
| // to the node (as a selection predicate on the groupby node) |
| // |
| ItemExpr *havingPred = removeSelPredTree(); |
| if (havingPred && NOT selIndexInHaving()) |
| { |
| currScope->context()->inHavingClause() = TRUE; |
| havingPred->convertToValueIdSet(selectionPred(), bindWA, ITM_AND); |
| currScope->context()->inHavingClause() = FALSE; |
| if (bindWA->errStatus()) |
| return this; |
| } |
| |
| // |
| // Bind the base class. |
| // |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) return boundExpr; |
| |
| if ((havingPred) && |
| (selIndexInHaving())) |
| { |
| addSelPredTree(havingPred); |
| } |
| |
| // |
| // Get the aggregate expressions from the list that has accumulated |
| // in the current bind scope and clear the list in the bind scope -- |
| // but first, if Tuple::bindNode()/checkTupleElementsAreAllScalar() |
| // created this node, add the subquery aggregate expr |
| // (Genesis 10-000221-6676). |
| // |
| if (aggregateExprTree_) { // only Binder, not Parser, should put anything here |
| // CMPASSERT(bindWA->getCurrentScope()->context()->inTupleList()); |
| CMPASSERT(aggregateExprTree_->nodeIsBound() || |
| aggregateExprTree_->child(0)->nodeIsBound()); |
| aggregateExprTree_ = aggregateExprTree_->bindNode(bindWA); |
| if (bindWA->errStatus()) return boundExpr; |
| aggregateExpr_ += aggregateExprTree_->getValueId(); |
| aggregateExprTree_ = NULL; |
| } |
| aggregateExpr_ += currScope->getUnresolvedAggregates(); |
| currScope->getUnresolvedAggregates().clear(); |
| getRETDesc()->setGroupedFlag(); |
| |
| return boundExpr; |
| } // GroupByAgg::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Scan |
| // ----------------------------------------------------------------------- |
| |
| |
| //////////////////////////////////////////////////////////////////////// |
| // A list of 'fabricated' hostvar representing the hostvars is generated |
| // that will contain the primary key values. These primary key |
| // values are retrieved at runtime from the cursor statement |
| // specified in the 'current of' clause. A predicate of the |
| // form 'where pkey1 = :pkey1 and pkey2 = :pkey2...' is attached |
| // to the selection pred of this node. The hostvar values are |
| // then passed in by the root node to its child and they reach |
| // this node at runtime where the 'where' predicate is evaluated. |
| //////////////////////////////////////////////////////////////////////// |
| void Scan::bindUpdateCurrentOf(BindWA *bindWA, NABoolean updateQry) |
| { |
| ValueIdList keyList = getTableDesc()->getClusteringIndex()->getIndexKey(); |
| ItemExpr * rootPtr = NULL; |
| char hvName[30]; |
| CollIndex i = 0; |
| for (i = 0; i < keyList.entries(); i++) |
| { |
| ValueId vid = keyList[i]; |
| |
| // Fabricate a name for the i'th host variable, |
| // make a hostvar,add it to pkeyHvarList. |
| sprintf(hvName,"_upd_pkey_HostVar%d",i); |
| |
| HostVar *hv = new(bindWA->wHeap()) HostVar(hvName, &vid.getType(), TRUE); |
| hv->bindNode(bindWA); |
| |
| pkeyHvarList().insert(hv->getValueId()); |
| |
| // Build a 'pkey = pkey_hvar' predicate. |
| ItemExpr * eqPred = new(bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, vid.getItemExpr(), hv); |
| |
| if (!rootPtr) |
| rootPtr = eqPred; |
| else |
| rootPtr = new(bindWA->wHeap()) BiLogic(ITM_AND, rootPtr, eqPred); |
| } // loop over all pkey columns |
| |
| if (updateQry) |
| { |
| ItemExpr * updCheckPtr = NULL; |
| ValueIdList nonKeyColList; |
| getTableDesc()->getClusteringIndex()->getNonKeyColumnList(nonKeyColList); |
| for (i = 0; i < nonKeyColList.entries(); i++) |
| { |
| ValueId vid = nonKeyColList[i]; |
| |
| // Fabricate a name for the i'th host variable, |
| // make a hostvar,add it to pkeyHvarList. |
| sprintf(hvName,"_upd_col_HostVar%d",i); |
| |
| HostVar *hv = new(bindWA->wHeap()) HostVar(hvName, &vid.getType(), TRUE); |
| hv->bindNode(bindWA); |
| |
| pkeyHvarList().insert(hv->getValueId()); |
| |
| // Build a 'col = col_hvar' predicate. |
| ItemExpr * eqPred = new(bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, vid.getItemExpr(), hv, TRUE); |
| |
| if (!updCheckPtr) |
| updCheckPtr = eqPred; |
| else |
| updCheckPtr = |
| new(bindWA->wHeap()) BiLogic(ITM_AND, updCheckPtr, eqPred); |
| } // loop over all pkey columns |
| |
| if (updCheckPtr) |
| { |
| updCheckPtr = new (bindWA->wHeap()) |
| Case(NULL, |
| new (bindWA->wHeap()) |
| IfThenElse(updCheckPtr, |
| new (bindWA->wHeap()) BoolVal(ITM_RETURN_TRUE), |
| new (bindWA->wHeap()) |
| BoolVal(ITM_RETURN_TRUE, |
| new (bindWA->wHeap()) |
| RaiseError(-(Lng32)EXE_CURSOR_UPDATE_CONFLICT)))); |
| |
| rootPtr = new(bindWA->wHeap()) BiLogic(ITM_AND, rootPtr, updCheckPtr); |
| } |
| } |
| |
| // rootPtr->bindNode(bindWA); |
| |
| // add this new tree to the existing selection predicate |
| addSelPredTree(rootPtr); |
| |
| bindSelf(bindWA); // just in case |
| |
| } // Scan::bindUpdateCurrentOf() |
| |
| // Every Scan and every GenericUpdate has its own stoi, |
| // plus copies of some of these stoi's are copied to the BindWA |
| // |
| // The scan/gu stoi's will become ex_partn_access stoi's |
| // |
| // The stoiList copies in BindWA will have their security |
| // checked in the binder, in RelRoot::checkPrivileges |
| // |
| // Stoi's must exist for every table/view/MV/index. |
| // Stoi's that are not copied to the BindWA are those for which Ansi mandates |
| // that no security checking be done (e.g., indexes). |
| // |
| OptSqlTableOpenInfo *setupStoi(OptSqlTableOpenInfo *&optStoi_, |
| BindWA *bindWA, |
| const RelExpr *re, |
| const NATable *naTable, |
| const CorrName &corrName, |
| NABoolean noSecurityCheck) |
| { |
| // Get the PHYSICAL (non-Ansi/non-delimited) filename of the table or view. |
| CMPASSERT(!naTable->getViewText() || naTable->getViewFileName()); |
| NAString fileName( naTable->getViewText() ? |
| (NAString)naTable->getViewFileName() : |
| naTable->getClusteringIndex()-> |
| getFileSetName().getQualifiedNameAsString(), |
| bindWA->wHeap()); |
| |
| SqlTableOpenInfo * stoi_ = new (bindWA->wHeap()) SqlTableOpenInfo; |
| |
| optStoi_ = new(bindWA->wHeap()) OptSqlTableOpenInfo(stoi_, |
| corrName, |
| bindWA->wHeap()); |
| |
| stoi_->setFileName(convertNAString(fileName, bindWA->wHeap())); |
| |
| if (naTable->getIsSynonymTranslationDone()) |
| { |
| stoi_->setAnsiName(convertNAString( |
| naTable->getSynonymReferenceName(), |
| bindWA->wHeap())); |
| } |
| else |
| { |
| stoi_->setAnsiName(convertNAString( |
| naTable->getTableName().getQualifiedNameAsAnsiString(), |
| bindWA->wHeap())); |
| } |
| |
| if(naTable->isUMDTable() || naTable->isSMDTable() |
| || naTable->isMVUMDTable() || naTable->isTrigTempTable()) |
| { |
| stoi_->setIsMXMetadataTable(1); |
| } |
| |
| if (NOT corrName.getCorrNameAsString().isNull()) |
| { |
| NABoolean corrNameSpecified = TRUE; |
| if (corrNameSpecified) |
| { |
| stoi_->setCorrName(convertNAString( |
| corrName.getCorrNameAsString(), |
| bindWA->wHeap())); |
| } |
| } |
| |
| // Materialized-View is considered as a regular table |
| stoi_->setSpecialTable(naTable->getSpecialType() != ExtendedQualName::NORMAL_TABLE && |
| naTable->getSpecialType() != ExtendedQualName::MV_TABLE); |
| stoi_->setIsView(naTable->getViewText() ? TRUE : FALSE); |
| |
| if (naTable->isHbaseTable()) |
| stoi_->setIsHbase(TRUE); |
| |
| stoi_->setLocationSpecified(corrName.isLocationNameSpecified() || |
| corrName.isPartitionNameSpecified() ); |
| |
| stoi_->setUtilityOpen(corrName.isUtilityOpenIdSpecified()); |
| stoi_->setUtilityOpenId(corrName.getUtilityOpenId()); |
| |
| stoi_->setIsNSAOperation(corrName.isNSAOperation()); |
| |
| |
| if (! naTable->getViewText()) |
| stoi_->setIsAudited(naTable->getClusteringIndex()->isAudited()); |
| |
| switch (re->getOperatorType()) |
| { |
| case REL_UNARY_INSERT: |
| case REL_LEAF_INSERT: |
| stoi_->setInsertAccess(); |
| break; |
| case REL_UNARY_UPDATE: |
| { |
| stoi_->setUpdateAccess(); |
| if (((GenericUpdate*)re)->isMerge()) |
| stoi_->setInsertAccess(); |
| } |
| break; |
| case REL_UNARY_DELETE: |
| case REL_LEAF_DELETE: |
| { |
| stoi_->setDeleteAccess(); |
| if (((GenericUpdate*)re)->isMerge()) |
| stoi_->setInsertAccess(); |
| if (((Delete*)re)->isFastDelete()) |
| stoi_->setSelectAccess(); |
| } |
| break; |
| case REL_SCAN: |
| case REL_LOCK: |
| case REL_UNLOCK: |
| case REL_HBASE_COPROC_AGGR: |
| stoi_->setSelectAccess(); |
| break; |
| case REL_EXE_UTIL: |
| |
| stoi_->setSelectAccess(); |
| stoi_->setInsertAccess(); |
| stoi_->setUpdateAccess(); |
| stoi_->setDeleteAccess(); |
| break; |
| default: |
| CMPASSERT(FALSE); |
| } |
| |
| NABoolean validateTS = TRUE; |
| |
| if ((naTable->getClusteringIndex() && |
| naTable->getClusteringIndex()->isSystemTable()) || |
| (NOT validateTS)) |
| stoi_->setValidateTimestamp(FALSE); |
| else |
| stoi_->setValidateTimestamp(TRUE); |
| |
| |
| // MV -- |
| // For INTERNAL REFRESH statements, leave only the insert on the MV itself. |
| if (re->getInliningInfo().isAvoidSecurityCheck() || |
| (bindWA->isBindingMvRefresh() && |
| (!naTable->isAnMV() || !stoi_->getInsertAccess()))) |
| { |
| return NULL; |
| } |
| |
| |
| // In a SCAN, only the topmost view is inserted into BindWA StoiList |
| // (thus there will be no security check on underlying views/basetables, |
| // as Ansi says there shouldn't). |
| if (re->getOperatorType() == REL_SCAN && bindWA->viewCount()) |
| { |
| return NULL; |
| } |
| |
| // Genesis 10-980306-4309: |
| // Ansi says not supposed to be any security check on referenced tables, |
| // nor of course on indexes, RIs and temp tables which are not an Ansi |
| // notion to begin with. |
| if ((naTable->getSpecialType() == ExtendedQualName::TRIGTEMP_TABLE) || |
| (naTable->getSpecialType() == ExtendedQualName::IUD_LOG_TABLE) || |
| (naTable->getSpecialType() == ExtendedQualName::INDEX_TABLE) || |
| (naTable->getSpecialType() == ExtendedQualName::RESOURCE_FORK)) |
| { |
| return NULL; |
| } |
| |
| if (noSecurityCheck) |
| { |
| return NULL; |
| } |
| |
| if (re->getOperator().match(REL_ANY_GEN_UPDATE)&& |
| (((GenericUpdate*)re)->getUpdateCKorUniqueIndexKey())) |
| { |
| return NULL; |
| } |
| |
| OptSqlTableOpenInfo *stoiInList = NULL; |
| |
| for (CollIndex i=0; i < bindWA->getStoiList().entries(); i++) |
| if (strcmp(bindWA->getStoiList()[i]->getStoi()->fileName(), fileName) == 0) { |
| stoiInList = bindWA->getStoiList()[i]; |
| break; |
| } |
| |
| if (!stoiInList) { |
| stoiInList = |
| new(bindWA->wHeap()) OptSqlTableOpenInfo( |
| new (bindWA->wHeap()) SqlTableOpenInfo(*stoi_), |
| corrName, |
| bindWA->wHeap()); |
| |
| stoiInList->setTable((NATable*)naTable); |
| bindWA->getStoiList().insert(stoiInList); |
| |
| bindWA->hbaseColUsageInfo()->insert((QualifiedName*)&naTable->getTableName()); |
| |
| } else { |
| // This is conceptually equivalent to |
| // stoiInList->AccessFlags |= stoi_->AccessFlags : |
| if (stoi_->getInsertAccess()) stoiInList->getStoi()->setInsertAccess(); |
| if (stoi_->getUpdateAccess()) stoiInList->getStoi()->setUpdateAccess(); |
| if (stoi_->getDeleteAccess()) stoiInList->getStoi()->setDeleteAccess(); |
| if (stoi_->getSelectAccess()) stoiInList->getStoi()->setSelectAccess(); |
| } |
| |
| return stoiInList; |
| |
| } // setupStoi() |
| |
| //---------------------------------------------------------------------------- |
| RelExpr *Scan::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // -- Triggers |
| // Is this a Scan on a temporary table inside the action of a statement trigger? |
| if (getTableName().isATriggerTransitionName(bindWA)) |
| return buildTriggerTransitionTableView(bindWA); // Located in Inlining.cpp |
| |
| // -- MV |
| // Is this a Scan on a log inside the select statement of a Materialized View? |
| // If so - maybe we need to replace this Scan with some other RelExpr tree. |
| // Ignore when inDDL() because the log may not exist yet. |
| if (!bindWA->inDDL() && |
| getTableName().getSpecialType() == ExtendedQualName::NORMAL_TABLE) |
| { |
| const MvBindContext *pMvBindContext = bindWA->getClosestMvBindContext(); |
| if (NULL != pMvBindContext) |
| { |
| RelExpr *replacementTree = |
| pMvBindContext->getReplacementFor(getTableName().getQualifiedNameObj()); |
| |
| if (replacementTree != NULL) |
| { |
| // We need to replace the Scan on the base table by some other tree. |
| // Make sure this tree has the same name as the Scan. |
| const CorrName& baseCorrName = getTableName(); |
| replacementTree = new(bindWA->wHeap()) |
| RenameTable(TRUE, replacementTree, baseCorrName); |
| |
| // Move any selection predicates on the Scan to the tree. |
| replacementTree->addSelPredTree(removeSelPredTree()); |
| |
| // Bind the tree and return instead of the tree. |
| return replacementTree->bindNode(bindWA); |
| } |
| } |
| } |
| |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // Get the NATable for this object. |
| // |
| NATable *naTable = bindWA->getNATable(getTableName()); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Set up stoi. bindWA->viewCount is altered during expanding the view. |
| setupStoi(stoi_, bindWA, this, naTable, getTableName(), noSecurityCheck()); |
| |
| // If the object is a view, expand the view. |
| // |
| if (naTable->getViewText()) { |
| |
| // Allow view on exception_table or any other special_table_name objects |
| ComBoolean specialTableFlagOn = Get_SqlParser_Flags(ALLOW_SPECIALTABLETYPE); |
| if (specialTableFlagOn == FALSE) |
| { |
| Set_SqlParser_Flags(ALLOW_SPECIALTABLETYPE); |
| SQL_EXEC_SetParserFlagsForExSqlComp_Internal(ALLOW_SPECIALTABLETYPE); |
| } |
| |
| RelExpr * boundView = bindWA->bindView(getTableName(), |
| naTable, |
| accessOptions(), |
| removeSelPredTree(), |
| getGroupAttr(), |
| TRUE/*catmanCollectUsages*/); |
| |
| // QSTUFF |
| // First we checked whether its a view and if so it must be updatable |
| // when using it for stream access or an embedded update or delete |
| if (!naTable->isUpdatable() && getGroupAttr()->isEmbeddedUpdateOrDelete()){ |
| *CmpCommon::diags() << DgSqlCode(-4206) |
| << DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()) |
| << (getGroupAttr()->isEmbeddedUpdate() ? |
| DgString0("UPDATE") : DgString0("DELETE")); |
| bindWA->setErrStatus(); |
| |
| // restore ALLOW_SPECIALTABLETYPE setting |
| if (specialTableFlagOn == FALSE) |
| Reset_SqlParser_Flags(ALLOW_SPECIALTABLETYPE); |
| return NULL; |
| } |
| |
| if (!naTable->isUpdatable() && getGroupAttr()->isStream()){ |
| *CmpCommon::diags() << DgSqlCode(-4151) |
| << DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| if (specialTableFlagOn == FALSE) |
| Reset_SqlParser_Flags(ALLOW_SPECIALTABLETYPE); |
| return NULL; |
| } |
| |
| |
| // Second we make sure the the underlying base table is key sequenced |
| // in case of embedded d/u and streams |
| if (boundView->getLeftmostScanNode()) { |
| // this is not a "create view V(a) as values(3)" kind of a view |
| const NATable * baseTable = |
| boundView->getLeftmostScanNode()->getTableDesc()->getNATable(); |
| |
| if (getGroupAttr()->isStream()) { |
| if (!baseTable->getClusteringIndex()->isKeySequenced()) { |
| *CmpCommon::diags() << DgSqlCode(-4204) |
| << DgTableName( |
| baseTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| if (specialTableFlagOn == FALSE) |
| Reset_SqlParser_Flags(ALLOW_SPECIALTABLETYPE); |
| |
| return NULL; |
| } |
| } |
| |
| if (getGroupAttr()->isEmbeddedUpdateOrDelete()){ |
| if (!baseTable->getClusteringIndex()->isKeySequenced()){ |
| *CmpCommon::diags() << DgSqlCode(-4205) |
| << DgTableName( |
| baseTable->getTableName().getQualifiedNameAsAnsiString()) |
| << (getGroupAttr()->isEmbeddedUpdate() ? |
| DgString0("UPDATE") : DgString0("DELETE")); |
| bindWA->setErrStatus(); |
| if (specialTableFlagOn == FALSE) |
| Reset_SqlParser_Flags(ALLOW_SPECIALTABLETYPE); |
| |
| return NULL; |
| } |
| } |
| } |
| // QSTUFF |
| |
| // restore ALLOW_SPECIALTABLETYPE setting |
| if (specialTableFlagOn == FALSE) |
| Reset_SqlParser_Flags(ALLOW_SPECIALTABLETYPE); |
| |
| return boundView; |
| } |
| |
| |
| // -- MV |
| // If this is the expanded tree pass during CREATE MV, expand the MV into |
| // its SELECT tree, just like a regular view. |
| // Do this only for incremental MVs, otherwise they may introduce unsupported |
| // operators such as Union. |
| if (naTable->isAnMV() && |
| bindWA->isExpandMvTree() && |
| naTable->getMVInfo(bindWA)->isIncremental()) |
| { |
| CMPASSERT(bindWA->inDDL()); |
| return bindExpandedMaterializedView(bindWA, naTable); |
| } |
| |
| // Do not allow to select from an un initialized MV |
| if (naTable->isAnMV() && !bindWA->inDDL() && !bindWA->isBindingMvRefresh()) |
| { |
| if (naTable->verifyMvIsInitializedAndAvailable(bindWA)) |
| return NULL; |
| } |
| |
| // Allocate a TableDesc and attach it to the Scan node. |
| // This call also allocates a RETDesc, attached to the BindScope, |
| // which we want to attach also to the Scan. |
| // |
| // disable override schema for synonym |
| NABoolean os = FALSE; |
| if ( ( bindWA->overrideSchemaEnabled() ) |
| && ( ! naTable->getSynonymReferenceName().isNull() ) ) |
| { |
| os = bindWA->getToOverrideSchema(); |
| bindWA->setToOverrideSchema(FALSE); |
| } |
| |
| TableDesc * tableDesc = NULL; |
| |
| if ((NOT isHbaseScan()) || (! getTableDesc())) |
| { |
| tableDesc = bindWA->createTableDesc(naTable, getTableName(), |
| FALSE, getHint()); |
| } |
| else |
| tableDesc = getTableDesc(); |
| |
| // restore override schema setting |
| if ( ( bindWA->overrideSchemaEnabled() ) |
| && ( ! naTable->getSynonymReferenceName().isNull() ) ) |
| bindWA->setToOverrideSchema(os); |
| |
| // before attaching set the selectivity hint defined by the user for this |
| // table |
| |
| if (tableDesc && getHint() && |
| getTableName().getSpecialType() == ExtendedQualName::NORMAL_TABLE) |
| { |
| double s; |
| s = getHint()->getSelectivity(); |
| if (0.0 <= s && s <= 1.0) { |
| SelectivityHint *selHint = new (STMTHEAP) SelectivityHint(); |
| selHint->setScanSelectivityFactor(s); |
| tableDesc->setSelectivityHint(selHint); |
| } |
| if (getHint()->getCardinality() >= 1.0) { |
| s = getHint()->getCardinality(); |
| CostScalar scanCard(s); |
| if((scanCard.getValue() - floor(scanCard.getValue())) > 0.00001) |
| scanCard = ceil(scanCard.getValue()); |
| |
| CardinalityHint *cardHint = new (STMTHEAP) CardinalityHint(); |
| cardHint->setScanCardinality(scanCard); |
| tableDesc->setCardinalityHint(cardHint); |
| } |
| } |
| |
| setTableDesc(tableDesc); |
| if (bindWA->errStatus()) return this; |
| setRETDesc(bindWA->getCurrentScope()->getRETDesc()); |
| |
| if ((CmpCommon::getDefault(ALLOW_DML_ON_NONAUDITED_TABLE) == DF_OFF) && |
| (naTable && naTable->getClusteringIndex() && !naTable->getClusteringIndex()->isAudited())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4211) |
| << DgTableName( |
| naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // restricted partitions for HBase table |
| if (naTable->isHbaseTable() && |
| (naTable->isPartitionNameSpecified() || |
| naTable->isPartitionRangeSpecified())) |
| { |
| PartitioningFunction * partFunc = naTable->getClusteringIndex()->getPartitioningFunction(); |
| |
| // find the salt column and apply a predicate on the salt column. |
| // For Hash2, since the partittion key columns are columns used to build |
| // the _SALT_ column, we need to search all columns for the _SALT_ column. |
| const NAColumnArray &ccCols = |
| (partFunc && partFunc->castToHash2PartitioningFunction())? |
| naTable->getClusteringIndex()->getAllColumns() |
| : |
| naTable->getClusteringIndex()->getPartitioningKeyColumns(); |
| |
| NABoolean saltColFound = FALSE; |
| |
| for (CollIndex i=0; i<ccCols.entries() && !saltColFound; i++) |
| { |
| if (ccCols[i]->isComputedColumn() && |
| ccCols[i]->getColName() == |
| ElemDDLSaltOptionsClause::getSaltSysColName()) |
| { |
| saltColFound = TRUE; |
| // create a predicate "_SALT_" = <num> or |
| // "_SALT_" between <num> and <num> |
| Int32 beginPartNum = partFunc->getRestrictedBeginPartNumber() - 1; |
| Int32 endPartNum = partFunc->getRestrictedEndPartNumber() - 1; |
| |
| // fill in defaults, indicated by -1 (-2 after subtraction above) |
| if (beginPartNum < 0) |
| beginPartNum = 0; |
| if (endPartNum < 0) |
| endPartNum = partFunc->getCountOfPartitions() - 1; |
| |
| ItemExpr *partPred = NULL; |
| ColReference *saltColRef = new(bindWA->wHeap()) ColReference( |
| new(bindWA->wHeap()) ColRefName( |
| ccCols[i]->getFullColRefName(), bindWA->wHeap())); |
| |
| if (beginPartNum == endPartNum) |
| { |
| partPred = new(bindWA->wHeap()) BiRelat |
| (ITM_EQUAL, |
| saltColRef, |
| new(bindWA->wHeap()) ConstValue(beginPartNum,bindWA->wHeap())); |
| } |
| else |
| { |
| partPred = new(bindWA->wHeap()) Between |
| (saltColRef, |
| new(bindWA->wHeap()) ConstValue(beginPartNum,bindWA->wHeap()), |
| new(bindWA->wHeap()) ConstValue(endPartNum,bindWA->wHeap())); |
| } |
| |
| ItemExpr *newSelPred = removeSelPredTree(); |
| if (newSelPred) |
| newSelPred = new(bindWA->wHeap()) BiLogic(ITM_AND, |
| newSelPred, |
| partPred); |
| else |
| newSelPred = partPred; |
| |
| // now add the partition predicates |
| addSelPredTree(newSelPred->bindNode(bindWA)); |
| } |
| } |
| |
| if (!saltColFound) |
| { |
| // not allowed to select individual partitions from HBase tables |
| // unless they are salted |
| char buf[20]; |
| snprintf(buf, 20, "%d", partFunc->getRestrictedBeginPartNumber()); |
| *CmpCommon::diags() << DgSqlCode(-1276) |
| << DgString0(buf) |
| << DgTableName( |
| naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| |
| if (naTable->isHiveTable() && |
| !(naTable->getClusteringIndex()->getHHDFSTableStats()->isOrcFile() || |
| naTable->getClusteringIndex()->getHHDFSTableStats() |
| ->isSequenceFile()) && |
| (CmpCommon::getDefaultNumeric(HDFS_IO_BUFFERSIZE_BYTES) == 0) && |
| (naTable->getRecordLength() > |
| CmpCommon::getDefaultNumeric(HDFS_IO_BUFFERSIZE)*1024)) |
| { |
| // do not raise error if buffersize is set though buffersize_bytes. |
| // Typically this setting is used for testing alone. |
| *CmpCommon::diags() << DgSqlCode(-4226) |
| << DgTableName( |
| naTable->getTableName(). |
| getQualifiedNameAsAnsiString()) |
| << DgInt0(naTable->getRecordLength()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| // Bind the base class. |
| // |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) return this; |
| // |
| // Assign the set of columns that belong to the table to be scanned |
| // as the output values that can be produced by this scan. |
| // |
| getGroupAttr()->addCharacteristicOutputs(getTableDesc()->getColumnList()); |
| getGroupAttr()->addCharacteristicOutputs(getTableDesc()->hbaseTSList()); |
| |
| // MV -- |
| if (getInliningInfo().isMVLoggingInlined()) |
| projectCurrentEpoch(bindWA); |
| |
| // QSTUFF |
| // Second we make sure the the underlying base table is key sequenced in case |
| // of embedded d/u and streams |
| if (getGroupAttr()->isStream()){ |
| |
| if (!naTable->getClusteringIndex()->isKeySequenced() || |
| naTable->hasVerticalPartitions()){ |
| *CmpCommon::diags() << DgSqlCode(-4204) |
| << DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| if (!getTableDesc()->getClusteringIndex()->getNAFileSet()->isAudited()) { |
| // Stream access not allowed on a non-audited table |
| *CmpCommon::diags() << DgSqlCode(-4215) |
| << DgTableName( |
| naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| |
| if (getGroupAttr()->isEmbeddedUpdateOrDelete()){ |
| if (!naTable->getClusteringIndex()->isKeySequenced() |
| || naTable->hasVerticalPartitions()){ |
| *CmpCommon::diags() << DgSqlCode(-4205) |
| << DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()) |
| << (getGroupAttr()->isEmbeddedUpdate() ? |
| DgString0("UPDATE") : DgString0("DELETE")); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| // QSTUFF |
| |
| // Fix "browse access mode incorrectly starts transaction" genesis case |
| // 10-021111-1080. Here's a glimpse at what may have been the original |
| // intent of the old code (taken from RelExpr.h comment for the now |
| // defunct RelExpr::propagateAccessOptions): |
| // |
| // At parse time, user can specify statement level access options. |
| // (See SQL/ARK Language spec). These options are attached to the |
| // RelRoot node and could be different for different Scans in the query. |
| // All Scan and Update nodes under a RelRoot have the same Access |
| // type and the Lock Mode. |
| // |
| // The problem is propagateAccessOptions did not visit all the Scans, |
| // eg, it did not propagate to subquery Scans, and it did not propagate |
| // to internal RelRoots. This "push" model seems harder to understand |
| // and to do correctly. |
| // |
| // So, we go with the "pull" model. An interesting node such as a Scan, |
| // GenericUpdate, RelRoot that needs a user-specified access/lock mode |
| // can "pull" one from BindWA. BindWA already implements SQL scoping |
| // and visibility rules. It's easier to explain also. Each table |
| // reference inherits the user-specified access/lock mode of the |
| // nearest SQL scope, going from the table outwards. If the entire |
| // query has no user-specified access/lock mode, then it uses the |
| // session-level default access/lock mode. |
| // |
| // if we have no user-specified access options then |
| // get it from nearest enclosing scope that has one (if any) |
| if (!accessOptions().userSpecified()) { |
| StmtLevelAccessOptions *axOpts = bindWA->findUserSpecifiedAccessOption(); |
| if (axOpts) { |
| accessOptions() = *axOpts; |
| } |
| } |
| // The above code is in RelRoot::bindNode also. |
| // It would be nice to refactor this common code; someday. |
| |
| // See Halloween handling code in GenericUpdate::bindNode |
| if (accessOptions().userSpecified()) { |
| if ( accessOptions().accessType() == REPEATABLE_ || |
| accessOptions().accessType() == STABLE_ || |
| accessOptions().accessType() == BROWSE_ |
| ) { |
| naTable->setRefsIncompatibleDP2Halloween(); |
| } |
| } |
| else { |
| TransMode::IsolationLevel il = CmpCommon::transMode()->getIsolationLevel(); |
| if((CmpCommon::transMode()->ILtoAT(il) == REPEATABLE_ ) || |
| (CmpCommon::transMode()->ILtoAT(il) == STABLE_ ) || |
| (CmpCommon::transMode()->ILtoAT(il) == BROWSE_ )) { |
| naTable->setRefsIncompatibleDP2Halloween(); |
| } |
| } |
| |
| const NAString * tableLockVal = |
| ActiveControlDB()->getControlTableValue( |
| getTableName().getUgivenName(), "TABLELOCK"); |
| if (*tableLockVal == "ON") |
| naTable->setRefsIncompatibleDP2Halloween(); |
| |
| //Embedded update/delete queries on partitioned table |
| //generates assertion when ATTEMPT_ASYNCHRONOUS_ACCESS |
| //flag is OFF.This is because split operator is used. |
| //Removing of split top operator causes some problems. |
| //Error 66 from file system is one of them. |
| //So, for now compiler will generate error if these |
| //conditions occur. |
| if (getGroupAttr()->isEmbeddedUpdateOrDelete() && |
| naTable->getClusteringIndex()->isPartitioned() && |
| (CmpCommon::getDefault(ATTEMPT_ASYNCHRONOUS_ACCESS) == DF_OFF)) { |
| |
| *CmpCommon::diags() << DgSqlCode(-4321) |
| << DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| // Stream access not allowed on a multi-partioned access paths, when |
| // CQD ATTEMPT_ASYNCHRONOUS_ACCESS is set to OFF.If we find |
| // that all access paths are partitioned we give an error. |
| |
| if (getGroupAttr()->isStream() && |
| (CmpCommon::getDefault(ATTEMPT_ASYNCHRONOUS_ACCESS) == DF_OFF)) { |
| NABoolean atleastonenonpartitionedaccess = FALSE; |
| NAFileSetList idescList = naTable->getIndexList(); |
| |
| for(CollIndex i = 0; |
| i < idescList.entries() && !atleastonenonpartitionedaccess; i++) |
| if(!(idescList[i]->isPartitioned()) ) |
| atleastonenonpartitionedaccess = TRUE; |
| |
| if (!atleastonenonpartitionedaccess) { |
| *CmpCommon::diags() << DgSqlCode(-4320) |
| << DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| |
| if (hbaseAccessOptions_) |
| { |
| if (hbaseAccessOptions_->isMaxVersions()) |
| { |
| hbaseAccessOptions_->setHbaseVersions |
| ( |
| getTableDesc()->getClusteringIndex()->getNAFileSet()->numMaxVersions() |
| ); |
| } |
| } |
| |
| return boundExpr; |
| } // Scan::bindNode() |
| |
| //---------------------------------------------------------------------------- |
| RelExpr *Scan::bindExpandedMaterializedView(BindWA *bindWA, NATable *naTable) |
| { |
| CollHeap *heap = bindWA->wHeap(); |
| MVInfoForDML *mvInfo = naTable->getMVInfo(bindWA); |
| QualifiedName mvName(mvInfo->getNameOfMV(), 3, heap, bindWA); |
| CorrName mvCorrName(mvName, heap, getTableName().getCorrNameAsString()); |
| |
| RelExpr *viewTree = mvInfo->buildMVSelectTree(); |
| viewTree = new(heap) RenameTable(TRUE, viewTree, mvCorrName); |
| viewTree->addSelPredTree(removeSelPredTree()); |
| RelExpr *boundExpr = viewTree->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (naTable->getClusteringIndex()->hasSyskey()) |
| { |
| // In case the MV on top of this MV is an MJV, it needs the SYSKEY |
| // column of this MV. Since the SYSKEY column is not projected from |
| // the select list of this MV, just fake it. It's value will never be |
| // used anyway - just it's existance. |
| ConstValue *dummySyskey = new(heap) ConstValue(0); |
| dummySyskey->changeType(new(heap) SQLLargeInt()); |
| ItemExpr *dummySyskeyCol = dummySyskey->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| ColRefName syskeyName("SYSKEY", mvCorrName); |
| boundExpr->getRETDesc()->addColumn(bindWA, |
| syskeyName, |
| dummySyskeyCol->getValueId(), |
| SYSTEM_COLUMN); |
| } |
| |
| bindWA->getCurrentScope()->setRETDesc(boundExpr->getRETDesc()); |
| return boundExpr; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // This Scan needs to project the CurrentEpoch column. |
| // Create and bind the CurrentEpoch function |
| void Scan::projectCurrentEpoch(BindWA *bindWA) |
| { |
| ItemExpr *currEpoch = |
| new(bindWA->wHeap()) GenericUpdateOutputFunction(ITM_CURRENTEPOCH); |
| currEpoch->bindNode(bindWA); |
| |
| // Add it to the RETDesc |
| ColRefName virtualColName(InliningInfo::getEpochVirtualColName()); |
| getRETDesc()->addColumn(bindWA, virtualColName, currEpoch->getValueId()); |
| |
| // And force the generator to project it even though it is not |
| // a column in the IndexDesc. |
| ValueIdSet loggingCols; |
| loggingCols.insert(currEpoch->getValueId()); |
| setExtraOutputColumns(loggingCols); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class Tuple |
| // ----------------------------------------------------------------------- |
| |
| // Genesis 10-990226-4329 and 10-000221-6676. |
| static RelExpr *checkTupleElementsAreAllScalar(BindWA *bindWA, RelExpr *re) |
| { |
| if (!re) return NULL; |
| RETDesc *rd = re->getRETDesc(); |
| CMPASSERT(rd); |
| |
| // an empty tuple is okay (dummy for Triggers, e.g.) |
| const ColumnDescList &cols = *rd->getColumnList(); |
| for (CollIndex i = cols.entries(); i--; ) { |
| ColumnDesc *col = cols[i]; |
| Subquery *subq = (Subquery *)cols[i]->getValueId().getItemExpr(); |
| if (subq->isASubquery()) { |
| if (cols.entries() > 1 && subq->getDegree() > 1) { |
| // 4125 The select list of a subquery in a VALUES clause must be scalar. |
| *CmpCommon::diags() << DgSqlCode(-4125); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| else if (cols.entries() == 1) { // if cols.entries() > 1 && subq->getDegree() > 1 |
| // we do not want to make the transformation velow. We want to keep the |
| // values clause, so that it cann be attached by a tsj to the subquery |
| // during transform. |
| CMPASSERT(subq->isARowSubquery()); |
| if (CmpCommon::getDefault(COMP_BOOL_137) == DF_ON) |
| { |
| ValueIdList subqSelectList; |
| RETDesc *subqRD = subq->getSubquery()->getRETDesc()->nullInstantiate( |
| bindWA, TRUE/*forceCast for GenRelGrby*/, subqSelectList); |
| subq->getSubquery()->setRETDesc(subqRD); |
| ItemExpr *agg = new(bindWA->wHeap()) |
| Aggregate(ITM_ONE_ROW, subqSelectList.rebuildExprTree()); |
| RelExpr * gby = new(bindWA->wHeap()) |
| GroupByAgg(subq->getSubquery(), REL_GROUPBY, NULL, agg); |
| NABoolean save = bindWA->getCurrentScope()->context()->inTupleList(); |
| bindWA->getCurrentScope()->context()->inTupleList() = TRUE; |
| gby = gby->bindNode(bindWA); |
| bindWA->getCurrentScope()->context()->inTupleList() = save; |
| return gby; |
| } |
| else |
| { |
| return subq->getSubquery(); |
| } |
| |
| } |
| } |
| } |
| return re; |
| } |
| |
| RelExpr *Tuple::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // Used by rowsets. We search for occurrences of arrays within this node to |
| // replace them with scalar variables |
| |
| if (bindWA->getHostArraysArea() && !bindWA->getHostArraysArea()->done()) |
| { |
| RelExpr *boundExpr = bindWA->getHostArraysArea()->modifyTupleNode(this); |
| |
| if (boundExpr) |
| return checkTupleElementsAreAllScalar(bindWA, boundExpr); |
| } |
| |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // Detach the item expression tree for the value list and bind it. |
| // We use counterForRowValues() and pass in parent, for DEFAULT processing |
| // (Ansi 7.1 SR 1). |
| // |
| CollIndex counterRowVals = 0; |
| CMPASSERT(!bindWA->getCurrentScope()->context()->counterForRowValues()); |
| bindWA->getCurrentScope()->context()->counterForRowValues() = &counterRowVals; |
| // |
| setRETDesc(bindRowValues(bindWA, removeTupleExprTree(), tupleExpr(), this, FALSE)); |
| if (bindWA->errStatus()) return this; |
| // |
| bindWA->getCurrentScope()->context()->counterForRowValues() = NULL; |
| |
| // Do NOT set currently scoped RETDesc to this VALUES(...) RETDesc -- |
| // makes "select * from t where ((values(1)),a) = (1,2);" |
| // fail with error 4001 "column A not found, no named tables in scope" |
| // |
| // bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| |
| // Bind the base class. |
| // |
| RelExpr *boundExpr = bindSelf(bindWA); |
| // -- Trigger |
| if (bindWA->errStatus()) return this; |
| |
| // |
| //for case 10-020716-5497 |
| RelExpr *newExpr = checkTupleElementsAreAllScalar(bindWA, boundExpr); |
| |
| //before doing anything with newExpr make sure it is not null it can |
| //be null if there is an error incheckTupleElementsAreAllScalar. |
| |
| getGroupAttr()->addCharacteristicOutputs(tupleExpr()); |
| |
| return newExpr; |
| } // Tuple::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // methods for class TupleList |
| // ----------------------------------------------------------------------- |
| |
| RelExpr *TupleList::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| RelExpr * boundExpr = NULL; |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| ExprValueId eVid(tupleExprTree()); |
| ItemExprTreeAsList tupleList(&eVid, ITM_ITEM_LIST); |
| NABoolean castTo = castToList().entries() > 0; |
| |
| if (tupleExprTree()->containsSubquery() || |
| tupleExprTree()->containsUDF() |
| #ifndef NDEBUG |
| || |
| getenv("UNIONED_TUPLES") |
| #endif |
| ) { |
| |
| // Make a union'ed tree of all the tuples in tupleList. ## |
| // This is done coz TupleList doesn't handle transformation ## |
| // of subqueries in tuples correctly yet. ## |
| CollIndex nTupleListEntries = (CollIndex)tupleList.entries(); |
| for (CollIndex i = 0; i < nTupleListEntries ; i++) { |
| |
| ItemExpr *ituple = tupleList[i]->child(0)->castToItemExpr(); |
| RelExpr *rtuple = new(bindWA->wHeap()) Tuple(ituple); |
| rtuple = rtuple->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // If INSERTing a TupleList, make some Assign's (even tmp's work!) |
| // to do some error-checking for MP-NCHAR-as-single-byte target columns. |
| // |
| // Similar code exists in |
| // (a) the loop further down, (b) TupleList::codeGen() |
| // and yes, it needs to be in all three places. |
| // |
| // NOTE: tmpAssign MUST BE ON HEAP -- |
| // Cannot be done with a stack-allocated tmpAssign |
| // because ItemExpr destructor will delete children, |
| // which we (and parent) are still referencing! |
| if (castTo) { |
| const ColumnDescList &itms = *rtuple->getRETDesc()->getColumnList(); |
| for (CollIndex j = 0; j < (CollIndex)itms.entries(); j++) { |
| ValueId src = itms[j]->getValueId(); |
| |
| Assign *tmpAssign = new(bindWA->wHeap()) |
| Assign(castToList()[j].getItemExpr(), src.getItemExpr()); |
| tmpAssign = (Assign *)tmpAssign->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| } |
| } |
| |
| if (!boundExpr) |
| boundExpr = rtuple; |
| else |
| boundExpr = new(bindWA->wHeap()) Union(boundExpr, rtuple); |
| } // for loop over tupleList |
| |
| CMPASSERT(boundExpr); |
| return boundExpr->bindNode(bindWA); |
| |
| } // containsSubquery |
| |
| // Detach the item expression tree for the value list and bind it. |
| // We use counterForRowValues() and pass in parent, for DEFAULT processing |
| // (Ansi 7.1 SR 1). |
| // |
| CollIndex counterRowVals = 0; |
| CMPASSERT(!bindWA->getCurrentScope()->context()->counterForRowValues()); |
| bindWA->getCurrentScope()->context()->counterForRowValues() = &counterRowVals; |
| |
| // tupleExprTree() contains a list of tuples. |
| // Each tuple is also a list of values (this list may contain one item). |
| // Bind all values in all the tuples. |
| // Check that the number of elements in each tuple is the same, |
| // and that the types of corresponding elements are compatible. |
| // |
| numberOfTuples_ = tupleList.entries(); |
| CollIndex prevTupleNumEntries = NULL_COLL_INDEX; |
| |
| // A list of ValueIdUnions nodes. Will create as many as there are |
| // entries in each tuple. The valIds from corresponding elements of |
| // the tuples will be added so that each ValueIdUnion represents a |
| // column of the tuple virtual table. Used to determine the |
| // union-compatible type to be used for the result type produced by |
| // the tuplelist. |
| // |
| ItemExprList vidUnions(bindWA->wHeap()); |
| ValueIdUnion *vidUnion; |
| |
| CollIndex i = 0; |
| CollIndex nEntries = (CollIndex)tupleList.entries() ; |
| for (i = 0; i < nEntries ; i++) { |
| |
| counterRowVals = 0; |
| |
| ValueIdList vidList; |
| ItemExpr *tuple = tupleList[i]->child(0)->castToItemExpr(); |
| tuple->convertToValueIdList(vidList, bindWA, ITM_ITEM_LIST, this); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| if (prevTupleNumEntries == NULL_COLL_INDEX) { |
| prevTupleNumEntries = vidList.entries(); |
| } |
| else if (prevTupleNumEntries != vidList.entries()) { |
| // 4126 The row-value-ctors of a VALUES must be of equal degree. |
| *CmpCommon::diags() << DgSqlCode(-4126); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // Genesis 10-980611-7153 |
| if (castTo && prevTupleNumEntries != castToList().entries()) break; |
| |
| for (CollIndex j = 0; j < prevTupleNumEntries; j++) { |
| // If any unknown type in the tuple, coerce it to the target type. |
| // Also do same MP-NCHAR magic as above. |
| if (castTo) { |
| ValueId src = vidList[j]; |
| src.coerceType(castToList()[j].getType()); |
| |
| // tmpAssign MUST BE ON HEAP -- see note above! |
| Assign *tmpAssign = new(bindWA->wHeap()) |
| Assign(castToList()[j].getItemExpr(), src.getItemExpr()); |
| tmpAssign = (Assign *)tmpAssign->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| } |
| |
| if(i == 0) { |
| ValueIdList vids; |
| |
| // Create an empty ValueIdUnion. Will create as many as there |
| // are entries in each tuple. Add the valIds from |
| // corresponding elements of the tuples so that each |
| // ValueIdUnion represents a column of the tuple virtual |
| // table. |
| // |
| vidUnion = new(bindWA->wHeap()) |
| ValueIdUnion(vids, NULL_VALUE_ID); |
| |
| vidUnion->setWasDefaultClause(TRUE); |
| |
| vidUnions.insertAt(j, vidUnion); |
| } |
| |
| // Add the valIds from corresponding elements of the tuples so |
| // that each ValueIdUnion represents a column of the tuple |
| // virtual table. |
| // |
| vidUnion = (ValueIdUnion *)vidUnions[j]; |
| vidUnion->setSource((Lng32)i, vidList[j]); |
| |
| if (NOT vidList[j].getItemExpr()->wasDefaultClause()) |
| vidUnion->setWasDefaultClause(FALSE); |
| } // for loop over entries in tuple |
| |
| } // for loop over tupleList |
| |
| if (castTo && prevTupleNumEntries != castToList().entries()) |
| { |
| |
| // 4023 degree of row value constructor must equal that of target table |
| *CmpCommon::diags() << DgSqlCode(-4023) |
| << DgInt0((Lng32)prevTupleNumEntries) |
| << DgInt1((Lng32)castToList().entries()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // do INFER_CHARSET fixup |
| if (!doInferCharSetFixup(bindWA, CharInfo::ISO88591, prevTupleNumEntries, |
| tupleList.entries())) { |
| return NULL; |
| } |
| |
| ItemExpr * outputList = NULL; |
| for (CollIndex j = 0; j < prevTupleNumEntries; j++) { |
| |
| // Get the ValueIdUnion node corresponding to this column of the |
| // tuple list virtual table |
| // |
| vidUnion = (ValueIdUnion *)vidUnions[j]; |
| |
| if (castTo) { |
| // Make sure the place holder type can support all the values in |
| // the tuple list and target column |
| // |
| vidUnion->setSource(numTuples(), castToList()[j]); |
| } |
| |
| vidUnion->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| if (castTo) { |
| // Check that the source and target types are compatible. |
| // Cannot be done with a stack-allocated tmpAssign |
| // because ItemExpr destructor will delete children, |
| // which we (and parent) are still referencing! |
| Assign *tmpAssign = new(bindWA->wHeap()) |
| Assign(castToList()[j].getItemExpr(), vidUnion); |
| |
| if ( CmpCommon::getDefault(ALLOW_IMPLICIT_CHAR_CASTING) == DF_ON ) |
| { |
| tmpAssign->tryToDoImplicitCasting(bindWA); |
| } |
| const NAType *targetType = tmpAssign->synthesizeType(); |
| if (!targetType) { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| |
| |
| NAType *phType = vidUnion->getValueId().getType().newCopy(bindWA->wHeap()); |
| |
| NATypeToItem *placeHolder = new(bindWA->wHeap()) NATypeToItem(phType); |
| |
| Cast * cnode; |
| if (castTo) |
| { |
| cnode = new(bindWA->wHeap()) Cast(placeHolder, phType, ITM_CAST, TRUE); |
| if (vidUnion->getValueId().getItemExpr()->wasDefaultClause()) |
| cnode->setWasDefaultClause(TRUE); |
| } |
| else |
| cnode = new(bindWA->wHeap()) Cast(placeHolder, phType); |
| cnode->setConstFoldingDisabled(TRUE); |
| cnode->bindNode(bindWA); |
| |
| if (!outputList) |
| outputList = cnode; |
| else |
| outputList = new(bindWA->wHeap()) ItemList(outputList, cnode); |
| } |
| |
| setRETDesc(bindRowValues(bindWA, outputList, tupleExpr(), this, FALSE)); |
| |
| if (bindWA->errStatus()) return this; |
| |
| bindWA->getCurrentScope()->context()->counterForRowValues() = NULL; |
| |
| // Bind the base class. |
| // |
| boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // need to add system columns as well....? |
| |
| NABoolean inSubquery = FALSE; |
| BindScope *currScope = bindWA->getCurrentScope(); |
| BindScope *prevScope = bindWA->getPreviousScope(currScope); |
| if (prevScope) |
| inSubquery = prevScope->context()->inSubquery(); |
| |
| if (inSubquery) |
| { |
| // need to change tupleExpr() & make it null-instantiated as RETDesc stores |
| // null instantiated columns (most probably these are constants, but not |
| // necessarily) |
| |
| const ColumnDescList *viewColumns = getRETDesc()->getColumnList(); |
| |
| tupleExpr().clear(); |
| for (CollIndex k=0; k < viewColumns->entries(); k++) |
| { |
| ValueId vid = (*viewColumns)[k]->getValueId(); |
| |
| // Special logic in Normalizer to optimize away a LEFT JOIN is not to |
| // be explored there, as this is not a LEFT JOIN |
| // Genesis case: 10-010312-1675 |
| // If the query were to be a LEFT JOIN, we would not be here |
| if (vid.getItemExpr()->getOperatorType() == ITM_INSTANTIATE_NULL) |
| |
| { |
| ((InstantiateNull *)vid.getItemExpr())->NoCheckforLeftToInnerJoin |
| = TRUE; |
| } |
| |
| tupleExpr().insert(vid); |
| } |
| } |
| getGroupAttr()->addCharacteristicOutputs(tupleExpr()); |
| |
| return boundExpr; |
| } // TupleList::bindNode() |
| |
| // set vidlist = ith tuple of this tuplelist and return TRUE |
| RelExpr* TupleList::getTuple |
| (BindWA *bindWA, ValueIdList& vidList, CollIndex i) |
| { |
| ExprValueId eVid(tupleExprTree()); |
| ItemExprTreeAsList tupleList(&eVid, ITM_ITEM_LIST); |
| ItemExpr *tuple = tupleList[i]->child(0)->castToItemExpr(); |
| tuple->convertToValueIdList(vidList, bindWA, ITM_ITEM_LIST, this); |
| return bindWA->errStatus() ? NULL : this; |
| } |
| |
| // set needsFixup to TRUE iff tuplelist needs INFER_CHARSET fixup |
| RelExpr* |
| TupleList::needsCharSetFixup(BindWA *bindWA, |
| CollIndex arity, |
| CollIndex nTuples, |
| NAList<NABoolean> &strNeedsFixup, |
| NABoolean &needsFixup) |
| { |
| // assume it needs no INFER_CHARSET fixup until proven otherwise |
| needsFixup = FALSE; |
| if (CmpCommon::wantCharSetInference()) { |
| CollIndex t, x; |
| for (x = 0; x < arity; x++) { // initialize |
| strNeedsFixup.insert(FALSE); |
| } |
| // go thru tuplelist looking for unprefixed string literals |
| for (t = 0; t < nTuples; t++) { |
| // get tuple |
| ValueIdList tup; |
| if (!getTuple(bindWA, tup, t)) { |
| return NULL; // something wrong |
| } |
| else { |
| // go thru columns of tuple looking for unprefixed string literals |
| for (x = 0; x < arity; x++) { |
| if (!strNeedsFixup[x] && tup[x].inferableCharType()) { |
| strNeedsFixup[x] = TRUE; |
| needsFixup = TRUE; |
| } |
| } |
| } |
| } |
| } |
| return this; // all OK |
| } |
| |
| // find fixable strings' inferredCharTypes |
| RelExpr* |
| TupleList::pushDownCharType(BindWA *bindWA, |
| enum CharInfo::CharSet cs, |
| NAList<const CharType*> &inferredCharType, |
| NAList<NABoolean> &strNeedsFixup, |
| CollIndex arity, |
| CollIndex nTuples) |
| { |
| // mimic CharType::findPushDownCharType() logic |
| const CharType* dctp = CharType::desiredCharType(cs); |
| NAList<const CharType*> sampleCharType(CmpCommon::statementHeap(),arity); |
| NAList<Int32> total(CmpCommon::statementHeap(),arity); |
| NAList<Int32> ct (CmpCommon::statementHeap(),arity); |
| CollIndex t, x; |
| for (x = 0; x < arity; x++) { // initialize |
| total.insert(0); |
| ct.insert(0); |
| sampleCharType.insert(NULL); |
| } |
| // go thru tuplelist looking for fixable strings' inferredCharType |
| for (t = 0; t < nTuples; t++) { |
| // get tuple |
| ValueIdList tup; |
| if (!getTuple(bindWA, tup, t)) { |
| return NULL; // something wrong |
| } |
| else { |
| // go thru tuple looking for fixable strings' inferredCharType |
| for (x = 0; x < arity; x++) { |
| if (strNeedsFixup[x]) { |
| total[x] += 1; |
| const CharType *ctp; |
| if (tup[x].hasKnownCharSet(&ctp)) { |
| ct[x] += 1; |
| if (sampleCharType[x] == NULL) { |
| sampleCharType[x] = ctp; |
| } |
| } |
| } |
| } |
| } |
| } |
| for (x = 0; x < arity; x++) { |
| if (ct[x] == total[x]) { |
| // all have known char set or none need fixup |
| inferredCharType.insert(NULL); // nothing to fix |
| } |
| else { |
| inferredCharType.insert(sampleCharType[x] ? sampleCharType[x] : dctp); |
| } |
| } |
| return this; // all OK |
| } |
| |
| // do INFER_CHARSET fixup |
| RelExpr* |
| TupleList::doInferCharSetFixup(BindWA *bindWA, |
| enum CharInfo::CharSet cs, |
| CollIndex arity, |
| CollIndex nTuples) |
| { |
| NABoolean needsFixup; |
| NAList<NABoolean> strNeedsFixup(CmpCommon::statementHeap(),arity); |
| RelExpr *result = needsCharSetFixup |
| (bindWA, arity, nTuples, strNeedsFixup, needsFixup); |
| if (!result || // something went wrong |
| !needsFixup) { // no INFER_CHARSET fixup needed |
| return result; |
| } |
| else { // some string literal needs INFER_CHARSET fixup |
| NAList<const CharType*> inferredCharType(CmpCommon::statementHeap(),arity); |
| if (!pushDownCharType(bindWA, cs, inferredCharType, |
| strNeedsFixup, arity, nTuples)) { |
| return NULL; // something went wrong |
| } |
| else { |
| // go thru tuplelist fixing up literals' char sets |
| CollIndex t, x; |
| for (t = 0; t < nTuples; t++) { |
| // get tuple |
| ValueIdList tup; |
| if (!getTuple(bindWA, tup, t)) { |
| return NULL; // something went wrong |
| } |
| else { |
| // go thru tuple fixing up literals' char sets |
| for (x = 0; x < arity; x++) { |
| if (strNeedsFixup[x] && tup[x].inferableCharType()) { |
| // coerce literal to have column's inferred char set |
| tup[x].coerceType(*(inferredCharType[x]), NA_CHARACTER_TYPE); |
| } |
| } |
| } |
| } |
| } |
| } |
| return this; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class RenameTable |
| // ----------------------------------------------------------------------- |
| |
| RelExpr *RenameTable::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); // -- Triggers |
| return this; |
| } |
| |
| // |
| // Create a new table name scope. |
| // |
| bindWA->getCurrentScope()->xtnmStack()->createXTNM(); |
| |
| // code to enforce the specification that if an index expression is specified |
| // with a rowset and the index is included in the derived table, the index |
| // must be the last column of the derived column list |
| if((getTableName().getCorrNameAsString() != "Rowset___") && (getArity() != 0)) |
| { |
| if(child(0)->getOperatorType() == REL_ROWSET) |
| { |
| NAString indexExpr(bindWA->wHeap()); |
| NAString lastString("", bindWA->wHeap()); |
| ItemExpr *tempPtr; |
| |
| indexExpr = ((Rowset *)getChild(0))->getIndexName(); |
| if((indexExpr != "") && newColNamesTree_) |
| { |
| for (tempPtr = newColNamesTree_; tempPtr; tempPtr=tempPtr->child(1)) |
| { |
| Int32 arity = tempPtr->getArity(); |
| if(arity == 1) |
| { |
| lastString = ((RenameCol *)tempPtr)->getNewColRefName()->getColName(); |
| } |
| } |
| if(indexExpr != lastString) |
| { |
| *CmpCommon::diags() << DgSqlCode(-30012) |
| << DgString0(indexExpr) |
| << DgString1(getTableName().getCorrNameAsString()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| } |
| } |
| |
| // |
| // Bind the child nodes. |
| // |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // |
| // Remove the table name scope. |
| // |
| bindWA->getCurrentScope()->xtnmStack()->removeXTNM(); |
| // |
| // Create the result table. |
| // |
| RETDesc *resultTable = new (bindWA->wHeap()) RETDesc(bindWA); |
| const RETDesc &sourceTable = *child(0)->getRETDesc(); |
| const CorrName &tableName = getTableName(); |
| ItemExpr *derivedColTree = removeColNameTree(); |
| ItemExprList derivedColList(bindWA->wHeap()); |
| const NAString *simpleColNameStr; |
| CollIndex i; |
| // |
| // Check that there are an equal number of columns to values. |
| // |
| if (derivedColTree) { |
| derivedColList.insertTree(derivedColTree); |
| if (derivedColList.entries() != sourceTable.getDegree()) { |
| // 4016 The number of derived columns must equal the degree of the derived table. |
| *CmpCommon::diags() << DgSqlCode(-4016) |
| #pragma nowarn(1506) // warning elimination |
| << DgInt0(derivedColList.entries()) << DgInt1(sourceTable.getDegree()); |
| #pragma warn(1506) // warning elimination |
| bindWA->setErrStatus(); |
| delete resultTable; |
| return this; |
| } |
| } |
| // |
| // Insert the derived column names into the result table. |
| // By ANSI 6.3 SR 6 (applies to explicit derived column list), |
| // duplicate names are not allowed. |
| // If user did not specify a derived column name list, |
| // expose the select list's column names (implicit derived column list); |
| // ANSI does not say that these cannot be duplicates -- |
| // if there's a later (i.e. in an outer scope) reference to a duplicately |
| // named column, ColReference::bindNode will issue an error |
| // (in particular, if all references are to constants, e.g. "count(*)", |
| // then duplicates are not disallowed in the implicit derived column list!). |
| // |
| // When Create View DDL uses this Binder, we must enforce |
| // ANSI 11.19 SR 8 + 9, clearly disallowing dups/ambigs |
| // (and disallowing implem-dependent names, i.e. our unnamed '(expr)' cols!). |
| // |
| for (i = 0; i < sourceTable.getDegree(); i++) { |
| // |
| if (derivedColTree) { // explicit derived column list |
| CMPASSERT(derivedColList[i]->getOperatorType() == ITM_RENAME_COL); |
| simpleColNameStr = &((RenameCol *) derivedColList[i])-> |
| getNewColRefName()->getColName(); |
| if (*simpleColNameStr != "") { // named column, not an expression |
| if (resultTable->findColumn(*simpleColNameStr)) { |
| ColRefName errColName(*simpleColNameStr, tableName); |
| // 4017 Derived column name $ColumnName was specified more than once. |
| *CmpCommon::diags() << DgSqlCode(-4017) |
| << DgColumnName(errColName.getColRefAsAnsiString()); |
| bindWA->setErrStatus(); |
| delete resultTable; |
| return this; |
| } |
| } |
| } else // implicit derived column list |
| simpleColNameStr = &sourceTable.getColRefNameObj(i).getColName(); |
| // |
| ColRefName colRefName(*simpleColNameStr, tableName); |
| ValueId valId = sourceTable.getValueId(i); |
| resultTable->addColumn(bindWA, colRefName, valId); |
| } // for-loop |
| // |
| // Insert system columns similarly, completely ignoring dup names. |
| // |
| const ColumnDescList &sysColList = *sourceTable.getSystemColumnList(); |
| for (i = 0; i < sysColList.entries(); i++) { |
| simpleColNameStr = &sysColList[i]->getColRefNameObj().getColName(); |
| if (NOT resultTable->findColumn(*simpleColNameStr)) { |
| ColRefName colRefName(*simpleColNameStr, tableName); |
| ValueId valId = sysColList[i]->getValueId(); // (slight diff from the |
| resultTable->addColumn(bindWA, colRefName, valId, SYSTEM_COLUMN); //above) |
| } |
| } |
| setRETDesc(resultTable); |
| |
| // MVs -- |
| // When binding INTERNAL REFRESH commands, the SYSKEY and @OP columns should |
| // be propageted to the scope above, even when they are not specified in the |
| // select list. |
| if (bindWA->isPropagateOpAndSyskeyColumns()) |
| getRETDesc()->propagateOpAndSyskeyColumns(bindWA, FALSE); |
| |
| bindWA->getCurrentScope()->setRETDesc(resultTable); |
| // |
| // Insert the table name into the XTNM, |
| // casting away constness on the correlation name |
| // in order to have default cat+sch filled in. |
| // |
| bindWA->getCurrentScope()->getXTNM()->insertNames(bindWA, |
| (CorrName &)tableName); |
| |
| if (bindWA->errStatus()) { |
| delete resultTable; |
| return this; |
| } |
| |
| if (getViewNATable()) |
| { |
| const NATable * natable = getViewNATable() ; |
| const ColumnDescList &columnsRET = *(resultTable->getColumnList()); |
| for (i = 0; i < natable->getColumnCount(); i++) |
| { |
| columnsRET[i]->setViewColPosition( |
| ((natable->getNAColumnArray())[i])->getPosition()); |
| columnsRET[i]->setViewFileName((const char*)natable->getViewFileName()); |
| } |
| } |
| |
| // |
| // Bind the base class. |
| // |
| return bindSelf(bindWA); |
| } // RenameTable::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class RenameReference |
| // ----------------------------------------------------------------------- |
| |
| // This method replaces the RETDesc of the current scope, with a new RETDesc |
| // that contains the columns of the transition values (OLD@ and NEW@) but |
| // with correlation names specified by the user in the REFERENCING clause |
| // of the row trigger. |
| void RenameReference::prepareRETDescWithTableRefs(BindWA *bindWA) |
| { |
| CollIndex refsToFind = getRefList().entries(); |
| CollIndex refsFound = 0; |
| RETDesc *retDesc; |
| |
| // First find the NEW@ and OLD@ tables in one of the scopes. |
| BindScope *scope = bindWA->getCurrentScope(); |
| // For each BindScope, |
| while ((scope!=NULL) && (refsToFind > refsFound)) |
| { // until we find all the references. |
| retDesc = scope->getRETDesc(); |
| // Skip if an empty RETDesc |
| if ((retDesc!=NULL) && !retDesc->isEmpty()) |
| { |
| // For each reference to change |
| for (CollIndex i=0; i<refsToFind; i++) |
| // Find the table name in the RETDesc, and save a pointer to it's |
| // column list in the TableRefName object. |
| if(getRefList().at(i).lookupTableName(retDesc)) |
| refsFound++; |
| } |
| |
| // Get the next BindScope to search. |
| scope = bindWA->getPreviousScope(scope); |
| } // while not done |
| |
| RETDesc *resultTable = new (bindWA->wHeap()) RETDesc(bindWA); |
| |
| // Create an empty RETDesc for the current scope. |
| bindWA->getCurrentScope()->setRETDesc(resultTable); |
| |
| // For each table reference, add to the RETDesc of the current scope, the |
| // columns of the columns of the referenced tables with the new referencing |
| // names as correlation names. |
| for (CollIndex i=0; i<refsToFind; i++) |
| getRefList()[i].bindRefColumns(bindWA); |
| } |
| |
| // The RenaneReference node renames values flowing down through it. |
| // It is used above a row trigger body, to implement the REFERENCING clause |
| // of the trigger definition - renaming the OLD and NEW transition variables |
| // to user specified names. |
| // |
| // This bind is top-down, so we first prepare the RETDesc, and then bind |
| // the children using this RETDesc. |
| RelExpr *RenameReference::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // Save the current RETDesc. |
| RETDesc *prevRETDesc = bindWA->getCurrentScope()->getRETDesc(); |
| |
| // Replace the RETDesc of the current scope with one that contains the user |
| // names (MY_NEW, MY_OLD) instead of the reference names (NEW@, OLD@). |
| prepareRETDescWithTableRefs(bindWA); |
| |
| // Bind the child nodes, in a new BindScope. |
| // If we don't open a new scope here, the bindChildren() method will |
| // overwrite the RETDesc of the current scope with NULL. |
| bindWA->initNewScope(); |
| |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Bind the base class. |
| RelExpr *boundNode = bindSelf(bindWA); |
| |
| // Save this scope's outer references before removing the scope. |
| const ValueIdSet myOuterRefs = bindWA->getCurrentScope()->getOuterRefs(); |
| |
| setRETDesc(bindWA->getCurrentScope()->getRETDesc()); |
| |
| bindWA->removeCurrentScope(); |
| bindWA->getCurrentScope()->setRETDesc(prevRETDesc); |
| |
| // Now merge the outer references into the previous scope. |
| bindWA->getCurrentScope()->mergeOuterRefs(myOuterRefs, FALSE); |
| |
| return boundNode; |
| } // RenameReference::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class BeforeTrigger |
| // ----------------------------------------------------------------------- |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Find the name and position of a column SET to by this before Trigger. |
| // The targetColName is an output parameter, saving the bindSetClause() |
| // method the work of finding the column name. |
| // The naTable parameter is NULL during DML. and is only used for DDL |
| // semantic checks. |
| ////////////////////////////////////////////////////////////////////////////// |
| Lng32 BeforeTrigger::getTargetColumn(CollIndex i, // Index of Assign expr. |
| ColRefName* targetColName, |
| const NATable *naTable) |
| { |
| ItemExpr *currentAssign = setList_->at(i); |
| CMPASSERT(currentAssign->getOperatorType() == ITM_ASSIGN); |
| |
| ItemExpr *targetColReference = currentAssign->child(0); |
| CMPASSERT(targetColReference->getOperatorType() == ITM_REFERENCE); |
| ColRefName& targetColRefName = |
| ((ColReference *)targetColReference)->getColRefNameObj(); |
| |
| if (targetColName != NULL) // return the column name to the binder. |
| *targetColName = targetColRefName; |
| |
| const NAString& colName = targetColRefName.getColName(); |
| |
| // If called during DML binding of the BeforeTrigger node, the |
| // column position will not be used, because the check for duplicate |
| // SET columns was done in DDL time. |
| if (naTable == NULL) |
| return 0; |
| |
| // We get here from DDL binding of the BeforeTrigger node, or from |
| // the Inlining code. |
| NAColumn *colObj = naTable->getNAColumnArray().getColumn(colName); |
| |
| // If colObj is NULL, it's a bad column name. |
| if (colObj == NULL) |
| return -1; |
| |
| return colObj->getPosition(); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method is called only during DDL (CREATE TRIGGER) of a before trigger |
| // with a SET clause. |
| // Each of the columns updated by the SET clause goes through several |
| // semantic checks, that cannot be done in the parser. |
| ////////////////////////////////////////////////////////////////////////////// |
| void BeforeTrigger::doSetSemanticChecks(BindWA *bindWA, RETDesc *origRETDesc) |
| { |
| UpdateColumns localCols = UpdateColumns(FALSE); |
| ColRefName currentCol; |
| const NATable *scanNaTable = NULL; |
| NABoolean isUpdateOp=FALSE; |
| |
| Scan *scanNode = getLeftmostScanNode(); |
| CMPASSERT(scanNode != NULL); |
| scanNaTable = scanNode->getTableDesc()->getNATable(); |
| |
| CorrName oldCorr(OLDCorr); |
| if (origRETDesc->getQualColumnList(oldCorr)) |
| isUpdateOp = TRUE; |
| |
| for (CollIndex i=0; i<setList_->entries(); i++) |
| { |
| // Get the name and position of the Assign target column. |
| Lng32 targetColPosition = getTargetColumn(i, ¤tCol, scanNaTable); |
| |
| if (!currentCol.getCorrNameObj().isATriggerTransitionName(bindWA, TRUE)) |
| { |
| // 11017 Left hand of SET assignment must be qualified with the name of the NEW transition variable |
| *CmpCommon::diags() << DgSqlCode(-11017) ; // must be NEW name |
| bindWA->setErrStatus(); |
| return; |
| } |
| |
| if (targetColPosition == -1) |
| { |
| // 11022 Column $0~ColumnName is not a column in table $0~TableName |
| NAString tableName = scanNaTable->getTableName().getQualifiedNameAsString(); |
| *CmpCommon::diags() << DgSqlCode(-11022) |
| << DgColumnName(currentCol.getColName()) |
| << DgTableName(tableName); |
| bindWA->setErrStatus(); |
| return; |
| } |
| |
| // We need to check for duplicate SET columns in DDL time only. |
| if (localCols.contains(targetColPosition)) |
| { |
| // 4022 column specified more than once |
| *CmpCommon::diags() << DgSqlCode(-4022) |
| << DgColumnName(currentCol.getColName()); |
| bindWA->setErrStatus(); |
| return; |
| } |
| localCols.addColumn(targetColPosition); |
| |
| // Is this a SET into a column that is part of the clustering key? |
| // This is only allowed on Inserts, not on Updates (Deletes never get here). |
| if (isUpdateOp && |
| scanNaTable->getNAColumnArray().getColumn(targetColPosition)->isClusteringKey()) |
| { |
| // 4033 Column $0~ColumnName is a primary or clustering key column and cannot be updated. |
| *CmpCommon::diags() << DgSqlCode(-4033) |
| << DgColumnName(currentCol.getColName()); |
| bindWA->setErrStatus(); |
| return; |
| } |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method is called for before triggers that use the SET clause. |
| // For each column to be set using SET MYNEW.<colname> = <setExpr> do: |
| // 1. Find NEW@.<colname> in origRETDesc. |
| // 2. Verify that there is such a column, and that the user is allowd to |
| // change it. |
| // 3. Get the column's ItemExpr expression, and save it in passThruExpr. |
| // 4. Create an ItemExpr tree as follows: |
| // case |
| // | |
| // IfThenElse |
| // / | \ |
| // condition setExpr passThruExpr |
| // |
| // where condition is the WHEN clause expression. |
| // 5. Bind this new expression in the RETDesc of the current scope. |
| // 6. remove NEW@.<colname> from origRETDesc, and re-insert it as the new |
| // expression. |
| ////////////////////////////////////////////////////////////////////////////// |
| void BeforeTrigger::bindSetClause(BindWA *bindWA, RETDesc *origRETDesc, CollHeap *heap) |
| { |
| // Semantic checks are only needed during DDL. |
| if (bindWA->inDDL()) |
| { |
| doSetSemanticChecks(bindWA, origRETDesc); |
| if (bindWA->errStatus()) |
| return; |
| } |
| |
| CorrName newCorr(NEWCorr); |
| const TableRefName *newRefName = getRefList().findTable(newCorr); |
| CMPASSERT(newRefName!=NULL); |
| CorrName newRef = newRefName->getTableCorr(); |
| ColRefName currentCol; |
| |
| // For each Assign expression in the list. |
| for (CollIndex i=0; i<setList_->entries(); i++) |
| { |
| // Get the name and position of the Assign target column. |
| Lng32 targetColPosition = getTargetColumn(i, ¤tCol, NULL); |
| |
| currentCol.getCorrNameObj() = newRef; |
| ItemExpr *setExpr = setList_->at(i)->child(1); |
| // Find the current value of this NEW@ column. |
| ColumnNameMap *currentColExpr = origRETDesc->findColumn(currentCol); |
| CMPASSERT(currentColExpr != NULL); // Otherwise we would have been thrown with error 11022 - see above. |
| ItemExpr *passThruExpr = currentColExpr->getValueId().getItemExpr(); |
| |
| ItemExpr *colExpr = NULL; |
| if (whenClause_ == NULL) |
| // After we add the support for reading the trigger status from |
| // the resource fork, and adding it to the condition, we should |
| // never get here. |
| colExpr = setExpr; |
| else |
| { |
| IfThenElse *ifExpr = new(heap) |
| IfThenElse(whenClause_, setExpr, passThruExpr); |
| colExpr = new(heap) Case(NULL, ifExpr); |
| } |
| colExpr = colExpr->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return; |
| |
| // Now remove and re-insert the column to the original RETDesc, |
| // that will be restored at the bottom of the method. |
| currentCol.getCorrNameObj() = newCorr; |
| origRETDesc->delColumn(bindWA, currentCol, USER_COLUMN); |
| origRETDesc->addColumn(bindWA, currentCol, colExpr->getValueId()); |
| |
| // force binding of the assign here so that type incompatability is caught |
| // during DDL |
| if (bindWA->inDDL()) |
| { |
| ItemExpr *currentAssign = setList_->at(i); |
| CMPASSERT(currentAssign->getOperatorType() == ITM_ASSIGN); |
| currentAssign->bindNode(bindWA); |
| } |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method is called for before triggers that use the SIGNAL clause. |
| // 1. Find the "virtual execId column" in origRETDesc. |
| // 3. Get the column's ItemExpr expression, and save it in passThruExpr. |
| // 4. Create an ItemExpr tree as follows: |
| // case |
| // | |
| // IfThenElse |
| // / | \ |
| // AND passThruExpr passThruExpr |
| // / \ |
| // condition RaiseError |
| // |
| // where condition is the WHEN clause expression, and RaiseError is the |
| // SIGNAL expression. |
| // 5. Bind this new expression in the RETDesc of the current scope. |
| // 6. remove "virtual execId column" from origRETDesc, and re-insert it as |
| // the new expression. |
| // |
| // The value of the expression is always the passThruExpr, for type |
| // compatibility. since if the SIGNAL fires, the actual value returned does |
| // not matter. The AND will evaluate the RaiseError only if the condition |
| // evaluates to TRUE. |
| ////////////////////////////////////////////////////////////////////////////// |
| void BeforeTrigger::bindSignalClause(BindWA *bindWA, RETDesc *origRETDesc, CollHeap *heap) |
| { |
| if (bindWA->inDDL()) |
| { |
| // In DDL time (CREATE TRIGGER) all we need is to bind the signal |
| // expression for semantic checks. |
| signal_->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return; |
| } |
| else |
| { |
| // The SIGNAL expression is piggy-backed on the Unique ExecuteID |
| // value inserted into the temp table. |
| ColumnNameMap *execIdCol = |
| origRETDesc->findColumn(InliningInfo::getExecIdVirtualColName()); |
| CMPASSERT(execIdCol != NULL); |
| const ColRefName& ExecIdColName = execIdCol->getColRefNameObj(); |
| ItemExpr *passThruExpr = execIdCol->getValueId().getItemExpr(); |
| ItemExpr *whenAndSignal = NULL; |
| |
| // Case 10-040604-5021: |
| // General AND logic uses "short circuiting" as follows: if the |
| // left side is FALSE, evaluation of the right side is skipped, and |
| // the result returned is FALSE. The following expression depends on |
| // evaluation of the right side being skipped whenever the left side |
| // is NOT TRUE, (i.e., FALSE or NULL). Therefore, an IS TRUE unary |
| // predicate must be placed above the actual WHEN condition. Otherwise, |
| // the signal will fire when the WHEN condition evaluates to NULL. |
| if (whenClause_ != NULL) |
| { |
| if (whenClause_->getOperatorType() == ITM_AND || |
| whenClause_->getOperatorType() == ITM_OR) |
| { |
| ItemExpr *isTrueExpr = new (heap) UnLogic(ITM_IS_TRUE, whenClause_); |
| whenAndSignal = new(heap) BiLogic(ITM_AND, isTrueExpr, signal_); |
| } |
| else |
| { |
| whenAndSignal = new(heap) BiLogic(ITM_AND, whenClause_, signal_); |
| } |
| } |
| |
| else |
| // After we add the support for reading the trigger status from |
| // the resource fork, and adding it to the condition, we should |
| // never get here. |
| whenAndSignal = signal_; |
| |
| // For type compatibity, the original value is used whatever the |
| // WHEN clause evaluates to. However, if it evaluates to TRUE, the |
| // evaluation of the signal expression will throw an SQLERROR. |
| ItemExpr *condSignalExpr = new(heap) |
| Case(NULL, new(heap) |
| IfThenElse(whenAndSignal, passThruExpr, passThruExpr)); |
| condSignalExpr = condSignalExpr->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return; |
| |
| // Now delete the original "virtual column" from the RETDesc, and |
| // re-insert it with the new value. |
| origRETDesc->delColumn(bindWA, ExecIdColName, USER_COLUMN); |
| origRETDesc->addColumn(bindWA, ExecIdColName, condSignalExpr->getValueId()); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This bind is bottom-up, so we first bind the children, and then use |
| // and change the RETDesc they created. |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *BeforeTrigger::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Now we know that we have the columns of OLD@ and NEW@ in the RETDesc |
| // of the current scope. Save this scope so we can update it and restore |
| // it when we are done. |
| RETDesc *origRETDesc = bindWA->getCurrentScope()->getRETDesc(); |
| CollHeap *heap = bindWA->wHeap(); |
| CollIndex refsToFind = getRefList().entries(); |
| |
| // For each reference to change, Find the table name in the RETDesc, |
| // and save a pointer to it's column list in the TableRefName object. |
| CollIndex i=0; |
| for (i=0; i<refsToFind; i++) |
| getRefList().at(i).lookupTableName(origRETDesc); |
| |
| // Create an empty RETDesc for the current scope. |
| // It will contain the names the user specified (MY_NEW, MY_OLD) for the |
| // OLD@ and NEW@ transition variables, and will be used to bind this |
| // node only. |
| bindWA->getCurrentScope()->setRETDesc(new(heap) RETDesc(bindWA)); |
| |
| // For each table reference, add to the RETDesc of the current scope, |
| // the columns of the referenced tables with the new referencing names |
| // as correlation names. |
| for (i=0; i<refsToFind; i++) |
| getRefList().at(i).bindRefColumns(bindWA); |
| |
| // First bind the condition. The ValueId will be used later (possibly |
| // multiple times) so that during execution, the expression will be |
| // evaluated only once. |
| if (whenClause_ != NULL) |
| { |
| whenClause_ = whenClause_->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| |
| // Use the bound condition to prepare the conditional expression |
| // for each column modified by the trigger (SET MY_NEW.a = ...) |
| if (setList_ != NULL) |
| bindSetClause(bindWA, origRETDesc, heap); |
| |
| // Use the bound condition to prepare the conditional SIGNAL |
| // expression, on the ExecuteId "virtual column". |
| if (signal_ != NULL) |
| bindSignalClause(bindWA, origRETDesc, heap); |
| |
| if (bindWA->errStatus()) |
| return this; |
| |
| // We don't need the RETDesc of the current scope anymore. Restore the |
| // original RETDesc with the updated columns. |
| bindWA->getCurrentScope()->setRETDesc(origRETDesc); |
| if (parentTSJ_ != NULL) |
| { |
| // If this is the top most before trigger, save a copy of the RETDesc |
| // for use by the transformNode() pass. |
| RETDesc *savedRETDesc = new(heap) RETDesc(bindWA, *origRETDesc); |
| setRETDesc(savedRETDesc); |
| } |
| // |
| // Bind the base class. |
| // |
| RelExpr *boundNode = bindSelf(bindWA); |
| |
| return boundNode; |
| } // BeforeTrigger::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Insert |
| // ----------------------------------------------------------------------- |
| // LCOV_EXCL_START - cnu |
| static void bindInsertRRKey(BindWA *bindWA, Insert *insert, |
| ValueIdList &sysColList, CollIndex i) |
| { |
| // For a KS round-robin partitioned table, the system column |
| // (for now there is only one, SYSKEY) is initialized via the expression |
| // "ProgDistribKey(partNum, rowPos, totalNumParts)". |
| // |
| const NAFileSet *fs = |
| insert->getTableDesc()->getClusteringIndex()->getNAFileSet(); |
| |
| // For now, round-robin partitioned tables are always stored in |
| // key-sequenced files, and there is only one system column (SYSKEY) |
| // which is at the beginning of the record. |
| CMPASSERT(fs->isKeySequenced() && i==0); |
| |
| CollHeap *heap = bindWA->wHeap(); |
| |
| // Host variables that provide access to partition number, |
| // row position, and total number of partitions -- |
| // supplied at run-time by the executor insert node. |
| // |
| ItemExpr *partNum = new (heap) |
| HostVar("_sys_hostVarInsertPartNum", |
| new (heap) SQLInt(FALSE,FALSE), // int unsigned not null |
| TRUE // is system-generated |
| ); |
| partNum->synthTypeAndValueId(); |
| insert->partNumInput() = partNum->getValueId(); // for later use in codeGen |
| |
| ItemExpr *rowPos = new (heap) |
| HostVar("_sys_hostVarInsertRowPos", |
| new (heap) SQLInt(FALSE,FALSE), // int unsigned not null |
| TRUE // is system-generated |
| ); |
| rowPos->synthTypeAndValueId(); |
| insert->rowPosInput() = rowPos->getValueId(); // for later use in codeGen |
| |
| ItemExpr *totNumParts = new (heap) |
| HostVar("_sys_hostVarInsertTotNumParts", |
| new (heap) SQLInt(FALSE,FALSE), // int unsigned not null |
| TRUE // is system-generated |
| ); |
| totNumParts->synthTypeAndValueId(); |
| insert->totalNumPartsInput() = totNumParts->getValueId(); // for later use |
| |
| // Generate expression to compute a round-robin key. Parameters to |
| // ProgDistribKey are the partition number, the row position (which |
| // is chosen randomly; the insert node will retry if a number is |
| // selected that is already in use), and the total number of |
| // partitions. |
| ItemExpr *rrKey = new (heap) ProgDistribKey(partNum, rowPos, totNumParts); |
| |
| // Build and set round-robin key expression. |
| Assign *assign = new (heap) |
| Assign(sysColList[i].getItemExpr(), rrKey, FALSE /*not user-specified*/); |
| assign->bindNode(bindWA); |
| insert->rrKeyExpr() = assign->getValueId(); |
| } // bindInsertRRKey |
| // LCOV_EXCL_STOP |
| |
| RelExpr *Insert::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // Set local binding flags |
| setInUpdateOrInsert(bindWA, this, REL_INSERT); |
| // The 8108 (unique constraint on an ID column) error must be raised |
| // only for system generated IDENTITY values and not for |
| // user generated ID values. We use the GenericUpdate::identityColumnUniqueIndex_ |
| // to indicate to the EID that 8108 should be raised in place of 8102. |
| // This variable is used to indicate that there is an IDENTITY column |
| // in the table for which the system is generating the value |
| |
| // This is NULL if "DEFAULT VALUES" was specified, |
| // non-NULL if a query-expr child was specified: VALUES.., TABLE.., SELECT.. |
| RelExpr *someNonDefaultValuesSpecified = child(0); |
| |
| // Set flag for firstN in context |
| if (child(0) && child(0)->getOperatorType() == REL_ROOT) // Indicating subquery |
| if (child(0)->castToRelExpr() && |
| child(0)->castToRelExpr()->getFirstNRows() >= 0) |
| if (bindWA && |
| bindWA->getCurrentScope() && |
| bindWA->getCurrentScope()->context()) |
| bindWA->getCurrentScope()->context()->firstN() = TRUE; |
| |
| if (NOT someNonDefaultValuesSpecified) { // "DEFAULT VALUES" specified |
| // Kludge up a dummy child before binding the GenericUpdate tree |
| setChild(0, new(bindWA->wHeap()) Tuple(new(bindWA->wHeap()) SystemLiteral(0))); |
| } |
| |
| // Bind the GenericUpdate tree. |
| // |
| RETDesc *incomingRETDescForSource = bindWA->getCurrentScope()->getRETDesc(); |
| RelExpr *boundExpr = GenericUpdate::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| |
| const NAFileSet* fileset = getTableDesc()->getNATable()->getClusteringIndex(); |
| const NAColumnArray& partKeyCols = fileset->getPartitioningKeyColumns(); |
| |
| if (getTableDesc()->getNATable()->isHiveTable()) |
| { |
| if (partKeyCols.entries() > 0) |
| { |
| // Insert into partitioned tables would require computing the target |
| // partition directory name, something we don't support yet. |
| *CmpCommon::diags() << DgSqlCode(-4222) |
| << DgString0("Insert into partitioned Hive tables"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // specifying a list of column names to insert to is not yet supported |
| if (insertColTree_) { |
| *CmpCommon::diags() << DgSqlCode(-4223) |
| << DgString0("Target column list for insert into Hive table"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| RelExpr *feResult = FastExtract::makeFastExtractTree( |
| getTableDesc(), |
| child(0).getPtr(), |
| getOverwriteHiveTable(), |
| TRUE, // called from within binder |
| FALSE, // not a common subexpr |
| bindWA); |
| |
| if (feResult) |
| { |
| feResult = feResult->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| return feResult; |
| } |
| else |
| return this; |
| } |
| |
| if(!(getOperatorType() == REL_UNARY_INSERT && |
| (child(0)->getOperatorType() == REL_TUPLE || // VALUES (1,'b') |
| child(0)->getOperatorType() == REL_TUPLE_LIST || // VALUES (1,'b'),(2,'Y') |
| child(0)->getOperatorType() == REL_UNION)) && // VALUES with subquery |
| (getOperatorType() != REL_LEAF_INSERT)) |
| { |
| setInsertSelectQuery(TRUE); |
| } |
| |
| // if table has a lob column, then fix up any reference to LOBinsert |
| // function in the source values list. |
| // |
| if ((getOperatorType() == REL_UNARY_INSERT) && |
| (getTableDesc()->getNATable()->hasLobColumn()) && |
| (child(0)->getOperatorType() == REL_TUPLE || // VALUES (1,'b') |
| child(0)->getOperatorType() == REL_TUPLE_LIST)) // VALUES (1,'b'),(2,'Y') |
| { |
| if (child(0)->getOperatorType() == REL_TUPLE_LIST) |
| { |
| TupleList * tl = (TupleList*)(child(0)->castToRelExpr()); |
| for (CollIndex x = 0; x < (UInt32)tl->numTuples(); x++) |
| { |
| ValueIdList tup; |
| if (!tl->getTuple(bindWA, tup, x)) |
| { |
| bindWA->setErrStatus(); |
| |
| return boundExpr; // something went wrong |
| } |
| |
| for (CollIndex n = 0; n < tup.entries(); n++) |
| { |
| ItemExpr * ie = tup[n].getItemExpr(); |
| if (ie->getOperatorType() == ITM_LOBINSERT) |
| { |
| // cannot have this function in a values list with multiple |
| // tuples. Use a single tuple. |
| *CmpCommon::diags() << DgSqlCode(-4483); |
| bindWA->setErrStatus(); |
| |
| return boundExpr; |
| |
| LOBinsert * li = (LOBinsert*)ie; |
| li->insertedTableObjectUID() = |
| getTableDesc()->getNATable()->objectUid().castToInt64(); |
| li->lobNum() = n; |
| |
| li->insertedTableSchemaName() = |
| getTableDesc()->getNATable()-> |
| getTableName().getSchemaName(); |
| } |
| } // for |
| } // for |
| } // if tuplelist |
| |
| } // if |
| |
| |
| // Prepare for any IDENTITY column checking later on |
| NAString identityColumnName; |
| NABoolean identityColumnGeneratedAlways = FALSE; |
| |
| identityColumnGeneratedAlways = |
| getTableDesc()->isIdentityColumnGeneratedAlways(&identityColumnName); |
| |
| if ((getTableName().isVolatile()) && |
| (CmpCommon::context()->sqlSession()->volatileSchemaInUse()) && |
| (getTableName().getSpecialType() == ExtendedQualName::NORMAL_TABLE) && |
| ((ActiveSchemaDB()->getDefaults()).getAsLong(IMPLICIT_UPD_STATS_THRESHOLD) > -1) && |
| (bindWA->isInsertSelectStatement()) && |
| (NOT getTableDesc()->getNATable()->isVolatileTableMaterialized())) |
| { |
| if (NOT Get_SqlParser_Flags(INTERNAL_QUERY_FROM_EXEUTIL)) |
| // if (NOT Get_SqlParser_Flags(NO_IMPLICIT_VOLATILE_TABLE_UPD_STATS)) |
| { |
| // treat this insert as a volatile load stmt. |
| RelExpr * loadVolTab = |
| new (bindWA->wHeap()) |
| ExeUtilLoadVolatileTable(getTableName(), |
| this, |
| bindWA->wHeap()); |
| |
| boundExpr = loadVolTab->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| |
| return boundExpr; |
| } |
| else |
| { |
| NATable * nat = (NATable*)(getTableDesc()->getNATable()); |
| nat->setIsVolatileTableMaterialized(TRUE); |
| } |
| } |
| |
| // Now we have to create the following three collections: |
| // |
| // - newRecExpr() |
| // An unordered set of Assign nodes of the form |
| // "col1 = value1, col2 = value2, ..." which is used by Norm/Optimizer. |
| // |
| // - newRecExprArray() |
| // An ordered array of Assign nodes of the same form, |
| // ordered by column position, which is used by Generator. |
| // This array must have the following properties: |
| // |
| // - All columns not specified in the insert statement must be |
| // Assign'ed with their default values. |
| // |
| // - If this is a key-sequenced table with a (non-RR) SYSKEY column, |
| // we must create the first entry in the newRecExprArray |
| // to be "SYSKEY_COL = 0". This is a placeholder where the timestamp |
| // value will be moved at runtime. Round-robin SYSKEY columns are |
| // initialized via an expression of the form "SYSKEY_COL = |
| // ProgDistribKey(..params..)". SYSKEY columns for other table |
| // organizations are handled by the file system or disk process. |
| // |
| // - updateToSelectMap() |
| // A ValueIdMap that can be used to rewrite value ids of the |
| // target table in terms of the source table and vice versa. |
| // The top value ids are target value ids, the bottom value ids |
| // are those of the source. |
| // |
| |
| NABoolean view = bindWA->getNATableInternal(getTableName())->getViewText() != NULL; |
| |
| ValueIdList tgtColList, userColList, sysColList, *userColListPtr; |
| CollIndexList colnoList(STMTHEAP); |
| CollIndex totalColCount, defaultColCount, i; |
| |
| getTableDesc()->getSystemColumnList(sysColList); |
| |
| // |
| // Detach the column list and bind the columns to the target table. |
| // Set up "colnoList" to map explicitly specified columns to where |
| // in the ordered array we will be inserting later. |
| // |
| ItemExpr *columnTree = removeInsertColTree(); |
| CMPASSERT(NOT columnTree || someNonDefaultValuesSpecified); |
| if (columnTree || (view && someNonDefaultValuesSpecified)) { |
| // |
| // INSERT INTO t(colx,coly,...) query-expr; |
| // INSERT INTO v(cola,colb,...) query-expr; |
| // INSERT INTO v query-expr; |
| // where query-expr is VALUES..., TABLE..., or SELECT..., |
| // but not DEFAULT VALUES. |
| // userColList is the full list of columns in the target table |
| // colnoList contains, for those columns specified in tgtColList, |
| // their ordinal position in the target table user column list |
| // (i.e., not counting system columns, which can't be specified |
| // in the insert column list); e.g. '(Z,X,Y)' -> [3,1,2] |
| // |
| CMPASSERT(NOT columnTree || |
| columnTree->getOperatorType() == ITM_REFERENCE || |
| columnTree->getOperatorType() == ITM_ITEM_LIST); |
| getTableDesc()->getUserColumnList(userColList); |
| userColListPtr = &userColList; |
| |
| RETDesc *columnLkp; |
| |
| if (columnTree) { |
| // bindRowValues will bind using the currently scoped RETDesc left in |
| // by GenericUpdate::bindNode, which will be that of the naTableTop |
| // (topmost view or table), *not* that of the base table (getTableDesc()). |
| columnLkp = bindRowValues(bindWA, columnTree, tgtColList, this, FALSE); |
| if (bindWA->errStatus()) return boundExpr; |
| } |
| else |
| { |
| columnLkp = bindWA->getCurrentScope()->getRETDesc(); |
| columnLkp->getColumnList()->getValueIdList(tgtColList); |
| } |
| |
| if (GU_DEBUG) { |
| // LCOV_EXCL_START - dpm |
| cerr << "columnLkp " << flush; |
| columnLkp->display(); |
| // LCOV_EXCL_STOP |
| } |
| |
| for (i = 0; i < columnLkp->getDegree(); i++) { |
| // Describes column in the base table: |
| ValueId source = columnLkp->getValueId(i); |
| const NAColumn *nacol = source.getNAColumn(); |
| |
| // Gets name of the column in this (possibly view) table: |
| const ColRefName colName = columnLkp->getColRefNameObj(i); |
| |
| // solution 10-081114-7315 |
| if (bindWA->inDDL() && bindWA->isInTrigger ()) |
| { |
| if (!userColListPtr->contains(source)) |
| { |
| // 4001 column not found |
| *CmpCommon::diags() << DgSqlCode(-4001) |
| << DgColumnName(colName.getColName()) |
| << DgString0(getTableName().getQualifiedNameObj().getQualifiedNameAsAnsiString()) |
| << DgString1(bindWA->getDefaultSchema().getSchemaNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| delete columnLkp; |
| return boundExpr; |
| } |
| } |
| |
| if (columnLkp->findColumn(colName)->isDuplicate()) { |
| // 4022 column specified more than once |
| *CmpCommon::diags() << DgSqlCode(-4022) |
| << DgColumnName(colName.getColName()); |
| bindWA->setErrStatus(); |
| delete columnLkp; |
| return boundExpr; |
| } |
| |
| colnoList.insert(nacol->getPosition()); |
| // Commented out this assert, as Assign::bindNode below emits nicer errmsg |
| // CMPASSERT((long)nacol->getPosition() - (long)firstColNumOnDisk >= 0); |
| } |
| if (columnTree) { |
| delete columnLkp; |
| columnLkp = NULL; |
| } |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| } |
| else { |
| // |
| // INSERT INTO t query-expr; |
| // INSERT INTO t DEFAULT VALUES; |
| // INSERT INTO v DEFAULT VALUES; |
| // userColListPtr points to tgtColList (which is the full list) |
| // userColList not used (because tgtColList already is the full list) |
| // colnoList remains empty (because tgtColList is already in order) |
| // if no system columns, set to list of user cols otherwise |
| getTableDesc()->getUserColumnList(tgtColList); |
| userColListPtr = &tgtColList; |
| if (sysColList.entries()) { |
| // set up colnoList to indicate the user columns, to help |
| // binding DEFAULT clauses in DefaultSpecification::bindNode() |
| for (CollIndex uc=0; uc<tgtColList.entries(); uc++) { |
| colnoList.insert(tgtColList[uc].getNAColumn()->getPosition()); |
| } |
| } |
| } |
| |
| // Compute total number of columns. Note that there may be some unused |
| // entries in newRecExprArray(), in the following case: |
| // - For computed columns that are not stored on disk |
| totalColCount = userColListPtr->entries() + sysColList.entries(); |
| newRecExprArray().resize(totalColCount); |
| |
| // Make sure children are bound -- GenericUpdate::bindNode defers |
| // their binding to now if this is an INSERT..VALUES(..), |
| // because only now do we have target column position info for |
| // correct binding of INSERT..VALUES(..,DEFAULT,..) |
| // in DefaultSpecification::bindNode. |
| // |
| // Save current RETDesc and XTNM. |
| // Bind the source in terms of the original RETDesc, |
| // with target column position info available through |
| // bindWA->getCurrentScope()->context()->updateOrInsertNode() |
| // (see DefaultSpecification::bindNode, calls Insert::getColDefaultValue). |
| // Restore RETDesc and XTNM. |
| // |
| RETDesc *currRETDesc = bindWA->getCurrentScope()->getRETDesc(); |
| bindWA->getCurrentScope()->setRETDesc(incomingRETDescForSource); |
| bindWA->getCurrentScope()->xtnmStack()->createXTNM(); |
| setTargetUserColPosList(colnoList); |
| |
| // if my child is a TupleList, then all tuples are to be converted/cast |
| // to the corresponding target type of the tgtColList. |
| // Pass on the tgtColList to TupleList so it can generate the Cast nodes |
| // with the target types during the TupleList::bindNode. |
| TupleList *tl = NULL; |
| if (child(0)->getOperatorType() == REL_TUPLE_LIST) { |
| tl = (TupleList *)child(0)->castToRelExpr(); |
| tl->castToList() = tgtColList; |
| } |
| |
| if (getTolerateNonFatalError() != RelExpr::UNSPECIFIED_) { |
| HostArraysWA * arrayWA = bindWA->getHostArraysArea() ; |
| if (arrayWA && arrayWA->hasHostArraysInTuple()) { |
| if (getTolerateNonFatalError() == RelExpr::NOT_ATOMIC_) |
| arrayWA->setTolerateNonFatalError(TRUE); |
| else |
| arrayWA->setTolerateNonFatalError(FALSE); // Insert::tolerateNonfatalError == ATOMIC_ |
| } |
| else if (NOT arrayWA->getRowwiseRowset()) { |
| // NOT ATOMIC only for rowset inserts |
| *CmpCommon::diags() << DgSqlCode(-30025) ; |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| } |
| |
| |
| |
| |
| |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // if this is an insert into native hbase table in _ROW_ format, then |
| // validate that only REL_TUPLE or REL_TUPLE_LIST is being used. |
| if ((getOperatorType() == REL_UNARY_INSERT) && |
| (getTableDesc()->getNATable()->isHbaseRowTable())) |
| { |
| NABoolean isError = FALSE; |
| if (NOT (child(0)->getOperatorType() == REL_TUPLE || // VALUES (1,'b') |
| child(0)->getOperatorType() == REL_TUPLE_LIST)) // VALUES (1,'b'),(2,'Y') |
| { |
| isError = TRUE; |
| } |
| |
| // Also make sure that inserts into column_details field of _ROW_ format |
| // hbase virtual table are being done through column_create function. |
| // For ex: insert into hbase."_ROW_".hb values ('1', column_create('cf:a', '100')) |
| // |
| if ((NOT isError) && (child(0)->getOperatorType() == REL_TUPLE)) |
| { |
| ValueIdList &tup = ((Tuple*)(child(0)->castToRelExpr()))->tupleExpr(); |
| if (tup.entries() == 2) // can only have 2 entries |
| { |
| ItemExpr * ie = tup[1].getItemExpr(); |
| if (ie && ie->getOperatorType() != ITM_HBASE_COLUMN_CREATE) |
| { |
| isError = TRUE; |
| } |
| } |
| else |
| isError = TRUE; |
| } |
| |
| if ((NOT isError) && (child(0)->getOperatorType() == REL_TUPLE_LIST)) |
| { |
| TupleList * tl = (TupleList*)(child(0)->castToRelExpr()); |
| for (CollIndex x = 0; x < (UInt32)tl->numTuples(); x++) |
| { |
| ValueIdList tup; |
| if (!tl->getTuple(bindWA, tup, x)) |
| { |
| isError = TRUE; |
| } |
| |
| if (NOT isError) |
| { |
| if (tup.entries() == 2) // must have 2 entries |
| { |
| ItemExpr * ie = tup[1].getItemExpr(); |
| if (ie->getOperatorType() != ITM_HBASE_COLUMN_CREATE) |
| { |
| isError = TRUE; |
| } |
| } |
| else |
| isError = TRUE; |
| } // if |
| } // for |
| } // if |
| |
| if (isError) |
| { |
| *CmpCommon::diags() << DgSqlCode(-1429); |
| bindWA->setErrStatus(); |
| |
| return boundExpr; |
| } |
| } |
| |
| // the only time that tgtColList.entries()(Insert's colList) != tl->castToList().entries() |
| // (TupleList's colList) is when DEFAULTS are removed in TupleList::bindNode() for insert |
| // into table with IDENTITY column, where the system generates the values |
| // for it using SG (Sequence Generator). |
| // See TupleList::bindNode() for detailed comments. |
| // When tgtColList.entries()(Insert's col list) is not |
| // equal to tl->castToList().entries() (TupleList's column list) |
| // make sure the correct colList is used during binding. |
| ValueIdList newTgtColList; |
| if(tl && (tgtColList.entries() != tl->castToList().entries())) |
| { |
| newTgtColList = tl->castToList(); |
| CMPASSERT(newTgtColList.entries() == (tgtColList.entries() -1)); |
| } |
| else |
| newTgtColList = tgtColList; |
| |
| setTargetUserColPosList(); |
| bindWA->getCurrentScope()->xtnmStack()->removeXTNM(); |
| bindWA->getCurrentScope()->setRETDesc(currRETDesc); |
| NABoolean bulkLoadIndex = bindWA->isTrafLoadPrep() && noIMneeded() ; |
| |
| if (someNonDefaultValuesSpecified) |
| // query-expr child specified |
| { |
| |
| const RETDesc &sourceTable = *child(0)->getRETDesc(); |
| if ((sourceTable.getDegree() != newTgtColList.entries())&& !bulkLoadIndex) { |
| // 4023 degree of row value constructor must equal that of target table |
| *CmpCommon::diags() << DgSqlCode(-4023) |
| #pragma nowarn(1506) // warning elimination |
| << DgInt0(sourceTable.getDegree()) << DgInt1(tgtColList.entries()); |
| #pragma warn(1506) // warning elimination |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| |
| |
| OptSqlTableOpenInfo* stoiInList = NULL; |
| for (CollIndex ii=0; ii < bindWA->getStoiList().entries(); ii++) |
| { |
| if (getOptStoi() && getOptStoi()->getStoi()) |
| { |
| if (strcmp((bindWA->getStoiList())[ii]->getStoi()->fileName(), |
| getOptStoi()->getStoi()->fileName()) == 0) |
| { |
| stoiInList = bindWA->getStoiList()[ii]; |
| break; |
| } |
| } |
| } |
| |
| // Combine the ValueIdLists for the column list and value list into a |
| // ValueIdSet (unordered) of Assign nodes and a ValueIdArray (ordered). |
| // Maintain a ValueIdMap between the source and target value ids. |
| CollIndex i2 = 0; |
| const ColumnDescList *viewColumns = NULL; |
| if (getBoundView()) |
| viewColumns = getBoundView()->getRETDesc()->getColumnList(); |
| |
| if (bulkLoadIndex) { |
| setRETDesc(child(0)->getRETDesc()); |
| } |
| |
| |
| |
| for (i = 0; i < tgtColList.entries() && i2 < newTgtColList.entries(); i++) { |
| if(tgtColList[i] != newTgtColList[i2]) |
| continue; |
| |
| ValueId target = tgtColList[i]; |
| ValueId source ; |
| if (!bulkLoadIndex) |
| source = sourceTable.getValueId(i2); |
| else { |
| ColRefName & cname = ((ColReference *)(baseColRefs()[i2]))->getColRefNameObj(); |
| source = sourceTable.findColumn(cname)->getValueId(); |
| } |
| CMPASSERT(target != source); |
| |
| const NAColumn *nacol = target.getNAColumn(); |
| |
| const NAType &sourceType = source.getType(); |
| const NAType &targetType = target.getType(); |
| |
| if ( DFS2REC::isFloat(sourceType.getFSDatatype()) && |
| DFS2REC::isNumeric(targetType.getFSDatatype()) && |
| (getTableDesc()->getNATable()->getPartitioningScheme() == |
| COM_HASH_V1_PARTITIONING || |
| getTableDesc()->getNATable()->getPartitioningScheme() == |
| COM_HASH_V2_PARTITIONING) ) |
| { |
| const NAColumnArray &partKeyCols = getTableDesc()->getNATable() |
| ->getClusteringIndex()->getPartitioningKeyColumns(); |
| |
| for (CollIndex j=0; j < partKeyCols.entries(); j++) |
| { |
| if (partKeyCols[j]->getPosition() == nacol->getPosition()) |
| { |
| ItemExpr *ie = source.getItemExpr(); |
| ItemExpr *cast = new (bindWA->wHeap()) |
| Cast(ie, &targetType, ITM_CAST); |
| cast = cast->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| source = cast->getValueId(); |
| |
| } |
| } |
| } |
| |
| Assign *assign = new (bindWA->wHeap()) |
| Assign(target.getItemExpr(), source.getItemExpr()); |
| assign->bindNode(bindWA); |
| |
| if(bindWA->errStatus()) |
| return NULL; |
| |
| if (stoiInList && !getUpdateCKorUniqueIndexKey()) |
| { |
| if(!getBoundView()) |
| stoiInList->addInsertColumn(nacol->getPosition()); |
| else |
| { |
| NABoolean found = FALSE; |
| for (CollIndex k=0; k < viewColumns->entries(); k++) { |
| if ((*viewColumns)[k]->getValueId() == target) { |
| stoiInList->addInsertColumn((Lng32) k); |
| found = TRUE; |
| // Updatable views cannot have any underlying basetable column |
| // appear more than once, so it's safe to break out of the loop. |
| break; |
| } |
| } // loop k |
| CMPASSERT(found); |
| } |
| } |
| |
| // |
| // Check for automatically inserted TRANSLATE nodes. |
| // Such nodes are inserted by the Implicit Casting And Translation feature. |
| // If this node has a child TRANSLATE node, then that TRANSLATE node |
| // is the real "source" that we must use from here on. |
| // |
| ItemExpr *assign_child = assign->child(1); |
| if ( assign_child->getOperatorType() == ITM_CAST ) |
| { |
| const NAType& type = assign_child->getValueId().getType(); |
| if ( type.getTypeQualifier() == NA_CHARACTER_TYPE ) |
| { |
| ItemExpr *assign_grndchld = assign_child->child(0); |
| if ( assign_grndchld->getOperatorType() == ITM_TRANSLATE ) |
| { |
| source = assign_grndchld->getValueId(); |
| CMPASSERT(target != source); |
| } |
| } |
| } |
| |
| const NAType *colType = nacol->getType(); |
| if (!colType->isSupportedType()) { |
| *CmpCommon::diags() << DgSqlCode(-4027) // 4027 table not insertable |
| << DgTableName(nacol->getNATable()->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| } |
| |
| if (bindWA->errStatus()) return boundExpr; |
| newRecExprArray().insertAt(nacol->getPosition(), assign->getValueId()); |
| newRecExpr().insert(assign->getValueId()); |
| |
| const NAType& assignSrcType = assign->getSource().getType(); |
| // if ( <we added some type of conversion> AND |
| // ( <tgt and src are both character> AND |
| // (<they are big and errors can occur> OR <charsets differ> OR <difference between tgt and src lengths is large>))) |
| // OR |
| // ( <we changed the basic type and we allow incompatible types> ) |
| // ) |
| // <then incorporate this added conversion into the updateToSelectMap> |
| if ( source != assign->getSource() && |
| ((assignSrcType.getTypeQualifier() == NA_CHARACTER_TYPE && |
| sourceType.getTypeQualifier() == NA_CHARACTER_TYPE && |
| ((assign->getSource().getItemExpr()->getOperatorType() == ITM_CAST && |
| sourceType.errorsCanOccur(assignSrcType) && |
| sourceType.getNominalSize() > |
| CmpCommon::getDefaultNumeric(LOCAL_MESSAGE_BUFFER_SIZE)*1024) || |
| // Temporary code to fix QC4395 in M6. For M7, try to set source |
| // to the right child of the assign after calling assign->bindNode. |
| // We should then be able to eliminate this entire if statement |
| // as well as the code to check for TRANSLATE nodes above. |
| ((CharType &) assignSrcType).getCharSet() != |
| ((CharType &) sourceType).getCharSet() || |
| // The optimizer may ask for source data to be partitioned or sorted on original source columns |
| // This is the reason we need to choose the else branch below unless we have a particular reason |
| // to do otherwise. Each of the conditions in this if statement reflects one of those partcular |
| // conditions. The bottomValues of updateToSelectMap will be placed in their entirety in the |
| // characteristic outputs of the source node. Outputs of the source node may be used to allocate |
| // buffers at runtime and therefore we would like to keep the output as small as possible. |
| // If the source cannot be partioned/sorted on a column because we have assign-getSource in the bottomValues |
| // then the cost is that data will be repartitioned with an additional exchange node. If the difference in |
| // length between source and assignSrc is large then the cost of repartition is less than the cost of |
| // allocating and using large buffers. |
| sourceType.getNominalSize() > (assignSrcType.getNominalSize() + |
| (ActiveSchemaDB()->getDefaults()).getAsLong(COMP_INT_98)) // default value is 512 |
| )) |
| || |
| // If we allow incompatible type assignments, also include the |
| // added cast into the updateToSelectMap |
| assignSrcType.getTypeQualifier() != sourceType.getTypeQualifier() && |
| CmpCommon::getDefault(ALLOW_INCOMPATIBLE_OPERATIONS) == DF_ON)) |
| { |
| updateToSelectMap().addMapEntry(target,assign->getSource()); |
| } |
| else |
| { |
| updateToSelectMap().addMapEntry(target,source); |
| } |
| |
| i2++; |
| } |
| } |
| setBoundView(NULL); |
| |
| // Is the table round-robin (horizontal) partitioned? |
| PartitioningFunction *partFunc = |
| getTableDesc()->getClusteringIndex()->getNAFileSet()-> |
| getPartitioningFunction(); |
| |
| NABoolean isRRTable = |
| partFunc && partFunc->isARoundRobinPartitioningFunction(); |
| |
| // Fill in default values for any columns not explicitly specified. |
| // |
| if (someNonDefaultValuesSpecified) // query-expr child specified, set system cols |
| defaultColCount = totalColCount - newTgtColList.entries(); |
| else // "DEFAULT VALUES" specified |
| defaultColCount = totalColCount; |
| |
| if (identityColumnGeneratedAlways) |
| defaultColCount = totalColCount; |
| |
| NABoolean isAlignedRowFormat = getTableDesc()->getNATable()->isSQLMXAlignedTable(); |
| NABoolean omittedDefaultCols = FALSE; |
| NABoolean omittedCurrentDefaultClassCols = FALSE; |
| |
| if (defaultColCount) { |
| NAWchar zero_w_Str[2]; zero_w_Str[0] = L'0'; zero_w_Str[1] = L'\0'; // wide version |
| CollIndex sysColIx = 0, usrColIx = 0; |
| |
| for (i = 0; i < totalColCount; i++) { |
| |
| ValueId target; |
| NABoolean isASystemColumn = FALSE; |
| const NAColumn *nacol = NULL; |
| |
| // find column on position i in the system or user column lists |
| if (sysColIx < sysColList.entries() && |
| sysColList[sysColIx].getNAColumn()->getPosition() == i) |
| { |
| isASystemColumn = TRUE; |
| target = sysColList[sysColIx]; |
| } |
| else |
| { |
| CMPASSERT((*userColListPtr)[usrColIx].getNAColumn()->getPosition() == i); |
| target = (*userColListPtr)[usrColIx]; |
| } |
| nacol = target.getNAColumn(); |
| |
| // if we need to add the default value, we don't have a new rec expr yet |
| if (NOT newRecExprArray().used(i)) { |
| |
| const char* defaultValueStr = NULL; |
| ItemExpr * defaultValueExpr = NULL; |
| NABoolean needToDeallocateColDefaultValueStr = FALSE; |
| |
| // Used for datetime columns with COM_CURRENT_DEFAULT. |
| // |
| NAType *castType = NULL; |
| |
| if (isASystemColumn) { |
| if (isRRTable) { |
| bindInsertRRKey(bindWA, this, sysColList, sysColIx); |
| if (bindWA->errStatus()) return boundExpr; |
| } |
| |
| if (nacol->isComputedColumn()) |
| { |
| CMPASSERT(target.getItemExpr()->getOperatorType() == ITM_BASECOLUMN); |
| ValueId defaultExprValId = ((BaseColumn *) target.getItemExpr())-> |
| getComputedColumnExpr(); |
| ValueIdMap updateToSelectMapCopy(updateToSelectMap()); |
| |
| // Use a copy to rewrite the value, to avoid requesting additional |
| // values from the child. We ask the child for all entries in this |
| // map in GenericUpdate::pushdownCoveredExpr(). |
| updateToSelectMapCopy.rewriteValueIdDown(defaultExprValId, defaultExprValId); |
| defaultValueExpr = defaultExprValId.getItemExpr(); |
| } |
| else |
| defaultValueStr = (char *)zero_w_Str; |
| } |
| else { // a user column (cf. Insert::getColDefaultValue) |
| CMPASSERT(NOT nacol->isComputedColumn()); // computed user cols not yet supported |
| defaultValueStr = nacol->getDefaultValue(); |
| } |
| |
| if (NOT defaultValueStr && NOT defaultValueExpr) { |
| // 4024 column has neither a default nor an explicit value. |
| *CmpCommon::diags() << DgSqlCode(-4024) << DgColumnName(nacol->getColName()); |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| |
| if (defaultValueStr) { |
| // If the column has a default class of COM_CURRENT_DEFAULT, |
| // cast the default value (which is CURRENT_TIMESTAMP) to |
| // the type of the column. Here we capture the type of the |
| // column. COM_CURRENT_DEFAULT is only used for Datetime |
| // columns. |
| // |
| if (nacol->getDefaultClass() == COM_CURRENT_DEFAULT) { |
| castType = nacol->getType()->newCopy(bindWA->wHeap()); |
| omittedCurrentDefaultClassCols = TRUE; |
| omittedDefaultCols = TRUE; |
| } |
| else if ((nacol->getDefaultClass() == COM_IDENTITY_GENERATED_ALWAYS) || |
| (nacol->getDefaultClass() == COM_IDENTITY_GENERATED_BY_DEFAULT)) { |
| setSystemGeneratesIdentityValue(TRUE); |
| } |
| else if (nacol->getDefaultClass() != COM_NO_DEFAULT) |
| omittedDefaultCols = TRUE; |
| |
| // Bind the default value, make an Assign, etc, as above |
| Parser parser(bindWA->currentCmpContext()); |
| |
| // save the current parserflags setting |
| ULng32 savedParserFlags = Get_SqlParser_Flags (0xFFFFFFFF); |
| Set_SqlParser_Flags(INTERNAL_QUERY_FROM_EXEUTIL); |
| Set_SqlParser_Flags(ALLOW_VOLATILE_SCHEMA_IN_TABLE_NAME); |
| |
| defaultValueExpr = parser.getItemExprTree(defaultValueStr); |
| CMPASSERT(defaultValueExpr); |
| |
| // Restore parser flags settings to what they originally were |
| Assign_SqlParser_Flags (savedParserFlags); |
| } // defaultValueStr != NULL |
| |
| Assign *assign = NULL; |
| |
| // If the default value string was successfully parsed, |
| // Create an ASSIGN node and bind. |
| // |
| if (defaultValueExpr) { |
| |
| // If there is a non-NULL castType, then cast the default |
| // value to the castType. This is used in the case of |
| // datetime value with COM_CURRENT_DEFAULT. The default |
| // value will be CURRENT_TIMESTAMP for all datetime types, |
| // so must cast the CURRENT_TIMESTAMP to the type of the |
| // column. |
| // |
| if(castType) { |
| defaultValueExpr = new (bindWA->wHeap()) |
| Cast(defaultValueExpr, castType); |
| } |
| |
| // system generates value for IDENTITY column. |
| if (defaultValueExpr->getOperatorType() == ITM_IDENTITY && |
| (CmpCommon::getDefault(COMP_BOOL_210) == DF_ON)) |
| { |
| // SequenceGenerator::createSequenceSubqueryExpression() |
| // is called for introducing the subquery in |
| // defaultValueExpr::bindNode() (IdentityVar::bindNode()). |
| // We bind here to make sure the correct subquery |
| // is used. |
| defaultValueExpr = defaultValueExpr->bindNode(bindWA); |
| } |
| |
| if (((isUpsertLoad()) || |
| ((isUpsert()) && (getTableDesc()->getNATable()-> isSQLMXAlignedTable()))) && |
| (NOT defaultValueExpr->getOperatorType() == ITM_IDENTITY) && |
| (NOT isASystemColumn)) |
| { |
| // for 'upsert using load' construct, all values must be specified so |
| // data could be loaded using inserts. |
| // If some values are missing, then it becomes an update. |
| *CmpCommon::diags() << DgSqlCode(-4246) ; |
| bindWA->setErrStatus(); |
| |
| return boundExpr; |
| } |
| assign = new (bindWA->wHeap()) |
| Assign(target.getItemExpr(), defaultValueExpr, |
| FALSE /*Not user Specified */); |
| if ((nacol->getDefaultClass() != COM_CURRENT_DEFAULT) && |
| (nacol->getDefaultClass() != COM_USER_FUNCTION_DEFAULT)) |
| assign->setToBeSkipped(TRUE); |
| assign->bindNode(bindWA); |
| } |
| |
| // |
| // Note: Parser or Binder errors from MP texts are possible. |
| // |
| if (!defaultValueExpr || bindWA->errStatus()) { |
| // 7001 Error preparing default on <column> for <table>. |
| *CmpCommon::diags() << DgSqlCode(-7001) |
| << DgString0(defaultValueStr) |
| << DgString1(nacol->getFullColRefNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| |
| newRecExprArray().insertAt(i, assign->getValueId()); |
| newRecExpr().insert(assign->getValueId()); |
| updateToSelectMap().addMapEntry(target,defaultValueExpr->getValueId()); |
| |
| if (needToDeallocateColDefaultValueStr && defaultValueStr != NULL) |
| { |
| NADELETEBASIC((NAWchar*)defaultValueStr, bindWA->wHeap()); |
| defaultValueStr = NULL; |
| } |
| |
| if (--defaultColCount == 0) |
| break; // tiny performance hack |
| |
| } // NOT newRecExprArray().used(i) |
| else |
| { |
| if (nacol->getDefaultClass() == COM_IDENTITY_GENERATED_ALWAYS) |
| { |
| Assign * assign = (Assign*)newRecExprArray()[i].getItemExpr(); |
| ItemExpr * ie = assign->getSource().getItemExpr(); |
| if (NOT ie->wasDefaultClause()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3428) |
| << DgString0(nacol->getColName()); |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| } |
| } |
| |
| if (isASystemColumn) |
| sysColIx++; |
| else |
| usrColIx++; |
| } // for i < totalColCount |
| } // defaultColCount |
| |
| // Now add the default values created as part of the Assigns above |
| // to the charcteristic inputs. The user specified values are added |
| // to the characteristic inputs during GenericUpdate::bindNode |
| // executed earlier as part of this method. |
| getGroupAttr()->addCharacteristicInputs(bindWA-> |
| getCurrentScope()-> |
| getOuterRefs()); |
| |
| |
| if (isRRTable) { |
| // LCOV_EXCL_START - |
| const LIST(IndexDesc *) indexes = getTableDesc()->getIndexes(); |
| for(i = 0; i < indexes.entries(); i++) { |
| indexes[i]->getPartitioningFunction()->setAssignPartition(TRUE); |
| } |
| // LCOV_EXCL_STOP |
| } |
| |
| // It is a system generated identity value if |
| // identityColumn() != NULL_VALUE_ID. The identityColumn() |
| // is set two places (1) earlier in this method. |
| // (2) DefaultSpecification::bindNode() |
| |
| // The IDENTITY column of type GENERATED ALWAYS AS IDENTITY |
| // must be specified in the values list as (DEFAULT) or |
| // must be excluded from the values list forcing the default. |
| |
| if (identityColumnGeneratedAlways && |
| NOT systemGeneratesIdentityValue()) |
| { |
| // The IDENTITY column type of GENERATED ALWAYS AS IDENTITY |
| // can not be used with user specified values. |
| // However, if the override CQD is set, then |
| // allow user specified values to be added |
| // for a GENERATED ALWAYS AS IDENTITY column. |
| |
| if (CmpCommon::getDefault(OVERRIDE_GENERATED_IDENTITY_VALUES) == DF_OFF) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3428) |
| << DgString0(identityColumnName.data()); |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| } |
| |
| ItemExpr *orderByTree = removeOrderByTree(); |
| if (orderByTree) { |
| bindWA->getCurrentScope()->context()->inOrderBy() = TRUE; |
| bindWA->getCurrentScope()->setRETDesc(child(0)->getRETDesc()); |
| orderByTree->convertToValueIdList(reqdOrder(), bindWA, ITM_ITEM_LIST); |
| |
| bindWA->getCurrentScope()->context()->inOrderBy() = FALSE; |
| if (bindWA->errStatus()) return NULL; |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| } |
| |
| setInUpdateOrInsert(bindWA); |
| |
| // Triggers -- |
| NABoolean insertFromValuesList = |
| getOperatorType() == REL_UNARY_INSERT && |
| (child(0)->getOperatorType() == REL_TUPLE || // VALUES (1,'b') |
| child(0)->getOperatorType() == REL_TUPLE_LIST || // VALUES (1,'b'),(2,'Y') |
| child(0)->getOperatorType() == REL_UNION); // VALUES with subquery |
| |
| // Insert from values that gets input from above should not use flow, |
| // for performance. Cases, other than TUPLE, should be investigated. |
| if (bindWA->findNextScopeWithTriggerInfo() != NULL |
| && (getGroupAttr()->getCharacteristicInputs() != NULL) |
| && (insertFromValuesList)) |
| setNoFlow(TRUE); |
| |
| if (getUpdateCKorUniqueIndexKey()) |
| { |
| SqlTableOpenInfo * scanStoi = getLeftmostScanNode()->getOptStoi()->getStoi(); |
| short updateColsCount = scanStoi->getColumnListCount(); |
| getOptStoi()->getStoi()->setColumnListCount(updateColsCount); |
| getOptStoi()->getStoi()->setColumnList(new (bindWA->wHeap()) short[updateColsCount]); |
| for (short i=0; i<updateColsCount; i++) |
| getOptStoi()->getStoi()->setUpdateColumn(i,scanStoi->getUpdateColumn(i)); |
| } |
| |
| if ((getIsTrafLoadPrep()) && |
| (getTableDesc()->getCheckConstraints().entries() != 0 || |
| getTableDesc()->getNATable()->getRefConstraints().entries() != 0 )) |
| { |
| // enabling/disabling constraints is not supported yet |
| //4486--Constraints not supported with bulk load. Disable the constraints and try again. |
| *CmpCommon::diags() << DgSqlCode(-4486) |
| << DgString0("bulk load") ; |
| } |
| if (getIsTrafLoadPrep()) |
| { |
| PartitioningFunction *pf = getTableDesc()->getClusteringIndex()->getPartitioningFunction(); |
| |
| const NodeMap* np; |
| Lng32 partns = 1; |
| if ( pf && (np = pf->getNodeMap()) ) |
| { |
| partns = np->getNumEntries(); |
| if(partns > 1 && CmpCommon::getDefault(ATTEMPT_ESP_PARALLELISM) == DF_OFF) |
| // 4490 - BULK LOAD into a salted table is not supported if ESP parallelism is turned off |
| *CmpCommon::diags() << DgSqlCode(-4490); |
| } |
| } |
| NABoolean toMerge = FALSE; |
| if (isUpsertThatNeedsTransformation(isAlignedRowFormat, omittedDefaultCols, omittedCurrentDefaultClassCols,toMerge)) { |
| if ((CmpCommon::getDefault(TRAF_UPSERT_TO_EFF_TREE) == DF_OFF) ||toMerge) |
| { |
| boundExpr = xformUpsertToMerge(bindWA); |
| return boundExpr; |
| } |
| else |
| boundExpr = xformUpsertToEfficientTree(bindWA); |
| |
| |
| } |
| if (NOT (isMerge() || noIMneeded())) |
| boundExpr = handleInlining(bindWA, boundExpr); |
| |
| // turn OFF Non-atomic Inserts for ODBC if we have detected that Inlining is needed |
| // necessary warnings have been generated in handleInlining method. |
| if (CmpCommon::getDefault(ODBC_PROCESS) == DF_ON) { |
| if (bindWA->getHostArraysArea() && |
| (NOT bindWA->getHostArraysArea()->getRowwiseRowset()) && |
| !(bindWA->getHostArraysArea()->getTolerateNonFatalError())) |
| setTolerateNonFatalError(RelExpr::UNSPECIFIED_); |
| } |
| |
| |
| // When mtsStatement_ or bulkLoadIndex is set Insert needs to return rows; |
| // so potential outputs are added (note that it's not replaced) to |
| // the Insert node. Currently mtsStatement_ is set |
| // for MTS queries and embedded insert queries. |
| if (isMtsStatement() || bulkLoadIndex) |
| { |
| if(isMtsStatement()) |
| setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA, getTableDesc())); |
| |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| |
| ValueIdList outputs; |
| getRETDesc()->getValueIdList(outputs, USER_AND_SYSTEM_COLUMNS); |
| ValueIdSet potentialOutputs; |
| getPotentialOutputValues(potentialOutputs); |
| potentialOutputs.insertList(outputs); |
| setPotentialOutputValues(potentialOutputs); |
| |
| // this flag is set to indicate optimizer not to pick the |
| // TupleFlow operator |
| setNoFlow(TRUE); |
| } |
| |
| return boundExpr; |
| } // Insert::bindNode() |
| |
| /* Upsert into a table with an index is converted into a Merge to avoid |
| the problem described in Trafodion-14. An upsert may overwrite an existing row |
| in the base table (identical to the update when matched clause of Merge) or |
| it may insert a new row into the base table (identical to insert when not |
| matched clause of merge). If the upsert caused a row to be updated in the |
| base table then the old version of the row will have to be deleted from |
| indexes, and a new version inserted. Upsert is being transformed to merge |
| so that we can delete the old version of an updated row from the index. |
| |
| Upsert is also converted into merge when TRAF_UPSERT_MODE is set to MERGE and |
| there are omitted cols with default values in case of aligned format table or |
| omitted current timestamp cols in case of non-aligned row format |
| */ |
| NABoolean Insert::isUpsertThatNeedsTransformation(NABoolean isAlignedRowFormat, |
| NABoolean omittedDefaultCols, |
| NABoolean omittedCurrentDefaultClassCols, |
| NABoolean &toMerge) const |
| { |
| toMerge = FALSE; |
| // If the the table has an identity column in clustering key or has a syskey |
| // we dont need to do this transformation.The incoming row will always be |
| // unique. So first check if we any of the conditions are satisfied to |
| //even try the transform |
| NABoolean mustTryTransform = FALSE; |
| if (isUpsert() && |
| NOT ( getIsTrafLoadPrep() || |
| ( (getTableDesc()->isIdentityColumnGeneratedAlways() && |
| getTableDesc()->hasIdentityColumnInClusteringKey())) || |
| ((getTableDesc()->getClusteringIndex()->getNAFileSet()->hasSyskey())))) |
| { |
| mustTryTransform = TRUE; |
| } |
| |
| // Transform upsert to merge in case of special modes and |
| // omitted default columns |
| // Case 1 : CQD is set to MERGE, omitted current(timestamp) default |
| // columns with non-aligned row format table or omitted |
| // default columns with aligned row format tables |
| |
| // Case 2 : CQD is set to Optimal, for non-aligned row format with omitted |
| // current(timestamp) columns, it is converted into merge |
| // though it is not optimal for performance. This is done to ensure |
| // that when the CQD is set to optimal, non-aligned format would |
| // behave like merge when any column is omitted |
| if (isUpsert() && |
| mustTryTransform && |
| ((CmpCommon::getDefault(TRAF_UPSERT_MODE) == DF_MERGE) && |
| (((NOT isAlignedRowFormat) && omittedCurrentDefaultClassCols) || |
| (isAlignedRowFormat && omittedDefaultCols))) |
| || |
| ((CmpCommon::getDefault(TRAF_UPSERT_MODE) == DF_OPTIMAL) && |
| ((NOT isAlignedRowFormat) && omittedCurrentDefaultClassCols))) |
| { |
| toMerge = TRUE; |
| return TRUE; |
| } |
| |
| // Transform upsert to efficient tree if none of the above conditions |
| // are true and the table has secondary indexes |
| if (isUpsert() && |
| mustTryTransform && |
| (getTableDesc()->hasSecondaryIndexes())) |
| { |
| toMerge = FALSE; |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| /** commenting the following method out for future work. This may be enabled |
| as a further performance improvement if we can eliminate the sort node that |
| gets geenrated as part of the Sequence Node. In case of no duplicates we won't |
| need the Sequence node at all. |
| |
| // take an insert(src) node and transform it into |
| // a tuple_flow with old/new rows flowing to the IM tree. |
| // with a newly created input_scan |
| RelExpr* Insert::xformUpsertToEfficientTreeNoDup(BindWA *bindWA) |
| { |
| NATable *naTable = bindWA->getNATable(getTableName()); |
| if (bindWA->errStatus()) |
| return NULL; |
| if ((naTable->getViewText() != NULL) && (naTable->getViewCheck())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" View with check option not allowed."); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| RelExpr *topNode = this; |
| // Create a new BindScope, to encompass the new nodes |
| // upsert(left_join(input_scan, tuple)) |
| // and any inlining nodes that will be created. Any values the upsert |
| // and children will need from src will be marked as outer references in that |
| // new BindScope. We assume that "src" is already bound. |
| ValueIdSet currOuterRefs = bindWA->getCurrentScope()->getOuterRefs(); |
| |
| CMPASSERT(child(0)->nodeIsBound()); |
| |
| BindScope *upsertScope = bindWA->getCurrentScope(); |
| |
| // columns of the target table |
| const ValueIdList &tableCols = updateToSelectMap().getTopValues(); |
| const ValueIdList &sourceVals = updateToSelectMap().getBottomValues(); |
| |
| // create a Join node - left join of the base table columns with the columns to be upserted. |
| // columns of the target table |
| CMPASSERT(child(0)->nodeIsBound()); |
| |
| Scan * targetTableScan = |
| new (bindWA->wHeap()) |
| Scan(CorrName(getTableDesc()->getCorrNameObj(), bindWA->wHeap())); |
| |
| |
| //join predicate between source columns and target table. |
| ItemExpr * keyPred = NULL; |
| ItemExpr * keyPredPrev = NULL; |
| BaseColumn* baseCol; |
| ColReference * targetColRef; |
| int predCount = 0; |
| ValueIdSet newOuterRefs; |
| ItemExpr * pkeyValPrev; |
| ItemExpr * pkeyVals; |
| for (CollIndex i = 0; i < tableCols.entries(); i++) |
| { |
| baseCol = (BaseColumn *)(tableCols[i].getItemExpr()) ; |
| if (baseCol->getNAColumn()->isSystemColumn()) |
| continue; |
| |
| targetColRef = new(bindWA->wHeap()) ColReference( |
| new(bindWA->wHeap()) ColRefName( |
| baseCol->getNAColumn()->getFullColRefName(), bindWA->wHeap())); |
| |
| |
| if (baseCol->getNAColumn()->isClusteringKey()) |
| { |
| // create a join/key predicate between source and target table, |
| // on the clustering key columns of the target table, making |
| // ColReference nodes for the target table, so that we can bind |
| // those to the new scan |
| keyPredPrev = keyPred; |
| keyPred = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, targetColRef, |
| sourceVals[i].getItemExpr(), |
| baseCol->getType().supportsSQLnull()); |
| predCount++; |
| if (predCount > 1) |
| { |
| keyPred = new(bindWA->wHeap()) BiLogic(ITM_AND, |
| keyPredPrev, |
| keyPred); |
| } |
| pkeyValPrev = pkeyVals; |
| |
| pkeyVals = tableCols[i].getItemExpr(); |
| if (i > 0) |
| { |
| pkeyVals = new(bindWA->wHeap()) ItemList(pkeyVals,pkeyValPrev); |
| |
| } |
| } |
| |
| } |
| |
| // Map the table's primary key values to the source lists key values |
| ValueIdList tablePKeyVals = NULL; |
| ValueIdList sourcePKeyVals = NULL; |
| |
| pkeyVals->convertToValueIdList(tablePKeyVals,bindWA,ITM_ITEM_LIST); |
| updateToSelectMap().mapValueIdListDown(tablePKeyVals,sourcePKeyVals); |
| |
| Join *lj = new(bindWA->wHeap()) Join(child(0),targetTableScan,REL_LEFT_JOIN,keyPred); |
| lj->doNotTransformToTSJ(); |
| lj->setTSJForWrite(TRUE); |
| bindWA->getCurrentScope()->xtnmStack()->createXTNM(); |
| RelExpr *boundLJ = lj->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| bindWA->getCurrentScope()->xtnmStack()->removeXTNM(); |
| setChild(0,boundLJ); |
| topNode = handleInlining(bindWA,topNode); |
| |
| |
| return topNode; |
| } |
| */ |
| |
| // take an insert(src) node and transform it into |
| // a tuple_flow with old/new rows flowing to the IM tree. |
| // with a newly created sequence node used to eliminate duplicates. |
| /* |
| NJ |
| / \ |
| Sequence NJ |
| / \ |
| Left Join IM Tree |
| / \ |
| / \ |
| Input Tuplelist Target Table Scan |
| or select list |
| */ |
| |
| RelExpr* Insert::xformUpsertToEfficientTree(BindWA *bindWA) |
| { |
| NATable *naTable = bindWA->getNATable(getTableName()); |
| if (bindWA->errStatus()) |
| return NULL; |
| if ((naTable->getViewText() != NULL) && (naTable->getViewCheck())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" View with check option not allowed."); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| RelExpr *topNode = this; |
| |
| CMPASSERT(child(0)->nodeIsBound()); |
| |
| BindScope *upsertScope = bindWA->getCurrentScope(); |
| // Create a new BindScope, to encompass the new nodes |
| // upsert(left_join(input_scan, tuple)) |
| // and any inlining nodes that will be created. Any values the upsert |
| // and children will need from src will be marked as outer references in that |
| // new BindScope. We assume that "src" is already bound. |
| ValueIdSet currOuterRefs = bindWA->getCurrentScope()->getOuterRefs(); |
| // Save the current RETDesc. |
| RETDesc *prevRETDesc = bindWA->getCurrentScope()->getRETDesc(); |
| |
| // columns of the target table |
| const ValueIdList &tableCols = updateToSelectMap().getTopValues(); |
| const ValueIdList &sourceVals = updateToSelectMap().getBottomValues(); |
| |
| // create a Join node - left join of the base table columns with the columns to be upserted. |
| // columns of the target table |
| CMPASSERT(child(0)->nodeIsBound()); |
| |
| Scan * targetTableScan = |
| new (bindWA->wHeap()) |
| Scan(CorrName(getTableDesc()->getCorrNameObj(), bindWA->wHeap())); |
| |
| bindWA->getCurrentScope()->context()->inUpsertXform() = TRUE; |
| //join predicate between source columns and target table. |
| ItemExpr * keyPred = NULL; |
| ItemExpr * keyPredPrev = NULL; |
| BaseColumn* baseCol; |
| ColReference * targetColRef; |
| int predCount = 0; |
| ValueIdSet newOuterRefs; |
| ItemExpr * pkeyValPrev = NULL; |
| ItemExpr * pkeyVals = NULL; |
| for (CollIndex i = 0; i < tableCols.entries(); i++) |
| { |
| baseCol = (BaseColumn *)(tableCols[i].getItemExpr()) ; |
| if (baseCol->getNAColumn()->isSystemColumn()) |
| continue; |
| |
| targetColRef = new(bindWA->wHeap()) ColReference( |
| new(bindWA->wHeap()) ColRefName( |
| baseCol->getNAColumn()->getFullColRefName(), |
| bindWA->wHeap())); |
| |
| |
| if (baseCol->getNAColumn()->isClusteringKey()) |
| { |
| // create a join/key predicate between source and target table, |
| // on the clustering key columns of the target table, making |
| // ColReference nodes for the target table, so that we can bind |
| // those to the new scan |
| keyPredPrev = keyPred; |
| keyPred = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, targetColRef, |
| sourceVals[i].getItemExpr(), |
| baseCol->getType().supportsSQLnull()); |
| predCount++; |
| if (predCount > 1) |
| { |
| keyPred = new(bindWA->wHeap()) BiLogic(ITM_AND, |
| keyPredPrev, |
| keyPred); |
| } |
| |
| pkeyValPrev = pkeyVals; |
| |
| pkeyVals = tableCols[i].getItemExpr(); |
| |
| if (pkeyValPrev != NULL ) |
| { |
| pkeyVals = new(bindWA->wHeap()) ItemList(pkeyVals,pkeyValPrev); |
| |
| } |
| } |
| |
| } |
| |
| // Map the table's primary key values to the source lists key values |
| ValueIdList tablePKeyVals ; |
| ValueIdList sourcePKeyVals ; |
| |
| pkeyVals->convertToValueIdList(tablePKeyVals,bindWA,ITM_ITEM_LIST); |
| updateToSelectMap().mapValueIdListDown(tablePKeyVals,sourcePKeyVals); |
| |
| Join *lj = new(bindWA->wHeap()) Join(child(0),targetTableScan,REL_LEFT_JOIN,keyPred); |
| |
| bindWA->getCurrentScope()->xtnmStack()->createXTNM(); |
| |
| |
| RelExpr *boundLJ = lj->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| bindWA->getCurrentScope()->xtnmStack()->removeXTNM(); |
| |
| ValueId nullInstIndicator( |
| lj->addNullInstIndicatorVar( |
| bindWA, |
| new(bindWA->wHeap()) SystemLiteral( |
| "U", |
| CharInfo::ISO88591))); |
| ValueIdSet sequenceFunction ; |
| |
| //Retrieve all the system and user columns of the left join output |
| ValueIdList ljOutCols = NULL; |
| boundLJ->getRETDesc()->getValueIdList(ljOutCols); |
| //Retrieve the null instantiated part of the LJ output |
| ValueIdList ljNullInstColumns = lj->nullInstantiatedOutput(); |
| |
| //Create the olap node and use the primary key of the table as the |
| //"partition by" columns for the olap node. |
| CMPASSERT(!bindWA->getCurrentScope()->getSequenceNode()); |
| RelSequence *seqNode = new(bindWA->wHeap()) RelSequence(boundLJ, sourcePKeyVals.rebuildExprTree(ITM_ITEM_LIST), (ItemExpr *)NULL); |
| |
| |
| // Create a LEAD Item Expr for a random value 999. |
| // Use this to eliminate rows which have a NULL for this LEAD value within |
| // a particular partition range. |
| ItemExpr *leadItem, *boundLeadItem = NULL; |
| ItemExpr *constLead999 = new (bindWA->wHeap()) ConstValue( 999); |
| |
| leadItem = new(bindWA->wHeap()) ItmLeadOlapFunction(constLead999,1); |
| ((ItmLeadOlapFunction *)leadItem)->setIsOLAP(TRUE); |
| boundLeadItem = leadItem->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| boundLeadItem->convertToValueIdSet(sequenceFunction); |
| seqNode->setSequenceFunctions(sequenceFunction); |
| |
| // Add a selection predicate (post predicate) to check if the LEAD item is NULL |
| ItemExpr *selPredOnLead = NULL; |
| selPredOnLead = new (bindWA->wHeap()) UnLogic(ITM_IS_NULL,leadItem); |
| selPredOnLead = selPredOnLead->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| seqNode->selectionPred() += selPredOnLead->getValueId(); |
| seqNode->setChild(0,boundLJ); |
| |
| RelExpr *boundSeqNode = seqNode->bindNode(bindWA); |
| |
| setChild(0,boundSeqNode); |
| |
| // Fixup the newRecExpr() and newRecExprArray() to refer to the new |
| // valueIds of the new child - i.e RelSequence. Use the saved off valueIdMap |
| // from the current bindScope for this. |
| ValueIdSet newNewRecExpr; |
| ValueIdMap notCoveredMap = bindWA->getCurrentScope()->getNcToOldMap(); |
| notCoveredMap.rewriteValueIdSetUp(newNewRecExpr, newRecExpr()); |
| newRecExpr() = newNewRecExpr; |
| |
| ValueIdList oldRecArrList(newRecExprArray()); |
| ValueIdList newRecArrList; |
| notCoveredMap.rewriteValueIdListUp(newRecArrList, oldRecArrList); |
| ValueIdArray newNewRecArray(newRecArrList.entries()); |
| |
| for (CollIndex i = 0; i < newRecArrList.entries(); i++) |
| { |
| newNewRecArray.insertAt(i,newRecArrList.at(i)); |
| } |
| newRecExprArray() = newNewRecArray; |
| |
| ValueId notCoveredNullInstIndicator; |
| |
| notCoveredMap.rewriteValueIdUp(notCoveredNullInstIndicator, |
| nullInstIndicator); |
| ItemExpr *nvl = new(bindWA->wHeap()) BuiltinFunction( |
| ITM_NVL, |
| bindWA->wHeap(), |
| 2, |
| notCoveredNullInstIndicator.getItemExpr(), |
| new(bindWA->wHeap()) SystemLiteral("I", |
| CharInfo::ISO88591)); |
| nvl = nvl->bindNode(bindWA); |
| setProducedMergeIUDIndicator(nvl->getValueId()); |
| |
| setXformedEffUpsert(TRUE); |
| bindWA->getCurrentScope()->context()->inUpsertXform() = FALSE; |
| return topNode; |
| } |
| |
| |
| // take an insert(src) node and transform it into |
| // tsj_flow(src, merge_update(input_scan)) |
| // with a newly created input_scan |
| RelExpr* Insert::xformUpsertToMerge(BindWA *bindWA) |
| { |
| NATable *naTable = bindWA->getNATable(getTableName()); |
| if (bindWA->errStatus()) |
| return NULL; |
| if ((naTable->getViewText() != NULL) && (naTable->getViewCheck())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" View with check option not allowed."); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // columns of the target table |
| const ValueIdList &tableCols = updateToSelectMap().getTopValues(); |
| const ValueIdList &sourceVals = updateToSelectMap().getBottomValues(); |
| |
| NABoolean isAlignedRowFormat = getTableDesc()->getNATable()->isSQLMXAlignedTable(); |
| |
| // Create a new BindScope, to encompass the new nodes merge_update(input_scan) |
| // and any inlining nodes that will be created. Any values the merge_update |
| // and children will need from src will be marked as outer references in that |
| // new BindScope. We assume that "src" is already bound. |
| ValueIdSet currOuterRefs = bindWA->getCurrentScope()->getOuterRefs(); |
| |
| CMPASSERT(child(0)->nodeIsBound()); |
| bindWA->initNewScope(); |
| BindScope *mergeScope = bindWA->getCurrentScope(); |
| |
| // create a new scan of the target table, to be used in the merge |
| Scan * inputScan = |
| new (bindWA->wHeap()) |
| Scan(CorrName(getTableDesc()->getCorrNameObj(), bindWA->wHeap())); |
| |
| ItemExpr * keyPred = NULL; |
| ItemExpr * keyPredPrev = NULL; |
| ItemExpr * setAssign = NULL; |
| ItemExpr * setAssignPrev = NULL; |
| ItemExpr * insertVal = NULL; |
| ItemExpr * insertValPrev = NULL; |
| ItemExpr * insertCol = NULL; |
| ItemExpr * insertColPrev = NULL; |
| BaseColumn* baseCol; |
| ColReference * targetColRef; |
| int predCount = 0; |
| int setCount = 0; |
| ValueIdSet newOuterRefs; |
| |
| // loop over the columns of the target table |
| for (CollIndex i = 0; i<tableCols.entries(); i++) |
| { |
| baseCol = (BaseColumn *)(tableCols[i].getItemExpr()) ; |
| if (baseCol->getNAColumn()->isSystemColumn()) |
| continue; |
| |
| targetColRef = new(bindWA->wHeap()) ColReference( |
| new(bindWA->wHeap()) ColRefName( |
| baseCol->getNAColumn()->getFullColRefName(), bindWA->wHeap())); |
| if (baseCol->getNAColumn()->isClusteringKey()) |
| { |
| // create a join/key predicate between source and target table, |
| // on the clustering key columns of the target table, making |
| // ColReference nodes for the target table, so that we can bind |
| // those to the new scan |
| keyPredPrev = keyPred; |
| keyPred = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, targetColRef, |
| sourceVals[i].getItemExpr(), |
| baseCol->getType().supportsSQLnull()); |
| predCount++; |
| if (predCount > 1) |
| { |
| keyPred = new(bindWA->wHeap()) BiLogic(ITM_AND, |
| keyPredPrev, |
| keyPred); |
| } |
| |
| } |
| if (sourceVals[i].getItemExpr()->getOperatorType() != ITM_CONSTANT) |
| { |
| newOuterRefs += sourceVals[i]; |
| mergeScope->addOuterRef(sourceVals[i]); |
| } |
| |
| // create the INSERT (WHEN NOT MATCHED) part of the merge for this column, again |
| // with a ColReference that we will then bind to the MergeUpdate target table |
| insertValPrev = insertVal; |
| insertColPrev = insertCol ; |
| insertVal = sourceVals[i].getItemExpr(); |
| insertCol = new(bindWA->wHeap()) ColReference( |
| new(bindWA->wHeap()) ColRefName( |
| baseCol->getNAColumn()->getFullColRefName(), bindWA->wHeap())); |
| if (i > 0) |
| { |
| insertVal = new(bindWA->wHeap()) ItemList(insertVal,insertValPrev); |
| insertCol = new(bindWA->wHeap()) ItemList(insertCol,insertColPrev); |
| } |
| } |
| inputScan->addSelPredTree(keyPred); |
| for (CollIndex i = 0 ; i < newRecExprArray().entries(); i++) |
| { |
| const Assign *assignExpr = (Assign *)newRecExprArray()[i].getItemExpr(); |
| ValueId tgtValueId = assignExpr->child(0)->castToItemExpr()->getValueId(); |
| NAColumn *col = tgtValueId.getNAColumn( TRUE ); |
| NABoolean copySetAssign = FALSE; |
| if (col->isSystemColumn()) |
| continue; |
| else |
| if (! col->isClusteringKey()) |
| { |
| // Create the UPDATE (WHEN MATCHED) part of the new MergeUpdate for |
| // a non-key column. We need to bind in the new = old values |
| // in GenericUpdate::bindNode. So skip the columns that are not user |
| // specified. Note that we had a discussion on whether such a transformed |
| // UPSERT shouldn't update all columns. |
| // |
| if (assignExpr->isUserSpecified()) |
| copySetAssign = TRUE; |
| // If copy the Default values in case of replace mode or optiomal mode with |
| // aligned row tables |
| else if ((CmpCommon::getDefault(TRAF_UPSERT_MODE) == DF_REPLACE) || |
| (isAlignedRowFormat && CmpCommon::getDefault(TRAF_UPSERT_MODE) == DF_OPTIMAL)) |
| copySetAssign = TRUE; |
| if (copySetAssign) |
| { |
| setAssignPrev = setAssign; |
| setAssign = (ItemExpr *)assignExpr; |
| setCount++; |
| if (setCount > 1) |
| setAssign = new(bindWA->wHeap()) ItemList(setAssignPrev, setAssign); |
| } |
| } |
| } |
| MergeUpdate *mu = new (bindWA->wHeap()) |
| MergeUpdate(CorrName(getTableDesc()->getCorrNameObj(), bindWA->wHeap()), |
| NULL, |
| REL_UNARY_UPDATE, |
| inputScan, // USING |
| setAssign, // WHEN MATCHED THEN UPDATE |
| insertCol, // WHEN NOT MATCHED THEN INSERT (cols) ... |
| insertVal, // ... VALUES() |
| bindWA->wHeap(), |
| NULL); |
| |
| mu->setXformedUpsert(); |
| // Use mergeScope, the scope we created here, for the MergeUpdate. We are |
| // creating some expressions with outer references here in this method, so |
| // we need to control the scope from here. |
| mu->setNeedsBindScope(FALSE); |
| |
| RelExpr *boundMU = mu->bindNode(bindWA); |
| |
| // remove the BindScope created earlier in this method |
| bindWA->removeCurrentScope(); |
| |
| // Remove the outer refs from the parent scope, they are provided |
| // by the left child of the TSJ_FLOW, unless they were already outer refs |
| // when we started this method. The binder logic doesn't handle |
| // that well, since they come from a child scope, not the current one, |
| // so we help a little. |
| newOuterRefs -= currOuterRefs; |
| bindWA->getCurrentScope()->removeOuterRefs(newOuterRefs); |
| |
| Join * jn = new(bindWA->wHeap()) Join(child(0), boundMU, REL_TSJ_FLOW, NULL); |
| |
| jn->doNotTransformToTSJ(); |
| jn->setTSJForMerge(TRUE); |
| jn->setTSJForMergeWithInsert(TRUE); |
| jn->setTSJForMergeUpsert(TRUE); |
| jn->setTSJForWrite(TRUE); |
| |
| RelExpr *result = jn->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| // Copy the userSecified and canBeSkipped attribute to mergeUpdateInsertExprArray |
| ValueIdList mergeInsertExprArray = mu->mergeInsertRecExprArray(); |
| for (CollIndex i = 0 ; i < newRecExprArray().entries(); i++) |
| { |
| const Assign *assignExpr = (Assign *)newRecExprArray()[i].getItemExpr(); |
| ((Assign *)mergeInsertExprArray[i].getItemExpr())->setToBeSkipped(assignExpr->canBeSkipped()); |
| ((Assign *)mergeInsertExprArray[i].getItemExpr())->setUserSpecified(assignExpr->isUserSpecified()); |
| } |
| |
| return result; |
| } |
| |
| RelExpr *HBaseBulkLoadPrep::bindNode(BindWA *bindWA) |
| { |
| //CMPASSERT((CmpCommon::getDefault(TRAF_LOAD) == DF_ON && |
| // CmpCommon::getDefault(TRAF_LOAD_HFILE) == DF_ON)); |
| if (nodeIsBound()) |
| { |
| return this; |
| } |
| |
| Insert * newInsert = new (bindWA->wHeap()) |
| Insert(getTableName(), |
| NULL, |
| REL_UNARY_INSERT, |
| child(0)->castToRelExpr()); |
| |
| |
| newInsert->setInsertType(UPSERT_LOAD); |
| newInsert->setIsTrafLoadPrep(true); |
| newInsert->setCreateUstatSample(getCreateUstatSample()); |
| |
| // Pass the flag to bindWA to guarantee that a range partitioning is |
| // always used for all source and target tables. |
| bindWA->setIsTrafLoadPrep(TRUE); |
| |
| RelExpr *boundNewInsert = newInsert->bindNode(bindWA); |
| |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| return boundNewInsert; |
| |
| |
| |
| } |
| |
| // This is a callback from DefaultSpecification::bindNode |
| // called from Insert::bindNode |
| // (you need to understand the latter to understand this). |
| // |
| const char *Insert::getColDefaultValue(BindWA *bindWA, CollIndex i) const |
| { |
| CMPASSERT(canBindDefaultSpecification()); |
| |
| CollIndexList &colnoList = *targetUserColPosList_; |
| CollIndex pos = colnoList.entries() ? colnoList[i] : i; |
| |
| const ValueIdList &colList = getTableDesc()->getColumnList(); |
| |
| if (colList.entries() <= pos) { |
| // 4023 degree of row value constructor must equal that of target table |
| *CmpCommon::diags() << DgSqlCode(-4023) |
| #pragma nowarn(1506) // warning elimination |
| << DgInt0(++pos) |
| #pragma warn(1506) // warning elimination |
| #pragma nowarn(1506) // warning elimination |
| << DgInt1(colList.entries()); |
| #pragma warn(1506) // warning elimination |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| ValueId target = colList[pos]; |
| const NAColumn *nacol = target.getNAColumn(); |
| const char* defaultValueStr = nacol->getDefaultValue(); |
| |
| CharInfo::CharSet mapCS = CharInfo::ISO88591; |
| NABoolean mapCS_hasVariableWidth = CharInfo::isVariableWidthMultiByteCharSet(mapCS); |
| size_t defaultValueWcsLen = 0; |
| NAWchar *defaultValueWcs = (NAWchar *) defaultValueStr; |
| NABoolean ucs2StrLitPrefix = FALSE; |
| |
| if (nacol->getDefaultClass() == COM_USER_DEFINED_DEFAULT && |
| nacol->getType() && |
| nacol->getType()->getTypeQualifier() == NA_CHARACTER_TYPE && |
| ((CharType*)(nacol->getType()))->getCharSet() == CharInfo::ISO88591 && |
| mapCS_hasVariableWidth && |
| defaultValueWcs != NULL && |
| nacol->getNATable()->getObjectSchemaVersion() >= COM_VERS_2300 && |
| (defaultValueWcsLen = NAWstrlen(defaultValueWcs)) > 6 && |
| ( ( ucs2StrLitPrefix = ( NAWstrncmp(defaultValueWcs, NAWSTR("_UCS2\'"), 6) == 0 )) || |
| ( defaultValueWcsLen > 10 && |
| NAWstrncmp(defaultValueWcs, NAWSTR("_ISO88591\'"), 10) == 0 )) && |
| defaultValueWcs[defaultValueWcsLen-1] == NAWCHR('\'')) |
| { |
| NAWcharBuf *pWcharBuf = NULL; |
| if (ucs2StrLitPrefix) |
| { |
| // Strip the leading _UCS2 prefix. |
| pWcharBuf = |
| new (bindWA->wHeap()) NAWcharBuf(&defaultValueWcs[5], |
| defaultValueWcsLen - 5, |
| bindWA->wHeap()); |
| } |
| else |
| { |
| // Keep the leading _ISO88591 prefix. |
| pWcharBuf = |
| new (bindWA->wHeap()) NAWcharBuf(defaultValueWcs, |
| defaultValueWcsLen, |
| bindWA->wHeap()); |
| } |
| charBuf *pCharBuf = NULL; // must set this variable to NULL so the |
| // following function call will allocate |
| // space for the output literal string |
| Int32 errorcode = 0; |
| pCharBuf = unicodeTocset(*pWcharBuf, bindWA->wHeap(), |
| pCharBuf, mapCS, errorcode); |
| // Earlier releases treated the converted multibyte character |
| // string, in ISO_MAPPING character set, as if it is a string of |
| // ISO88591 characters and then convert it back to UCS-2 format; |
| // i.e., for each byte in the string, insert an extra byte |
| // containing the binary zero value. |
| NADELETE(pWcharBuf, NAWcharBuf, bindWA->wHeap()); |
| pWcharBuf = NULL; // must set this variable to NULL to force the |
| // following call to allocate space for the |
| // the output literal string |
| pWcharBuf = ISO88591ToUnicode(*pCharBuf, bindWA->wHeap(), pWcharBuf); |
| // Prepare the converted literal string for the following CAST |
| // function by setting pColDefaultValueStr to point to the string |
| NAWchar *pWcs = NULL; |
| if (ucs2StrLitPrefix) |
| { |
| pWcs = new (bindWA->wHeap()) NAWchar[10+NAWstrlen(pWcharBuf->data())]; |
| NAWstrcpy(pWcs, NAWSTR("_ISO88591")); |
| } |
| else |
| { |
| pWcs = new (bindWA->wHeap()) NAWchar[1+NAWstrlen(pWcharBuf->data())]; |
| pWcs[0] = NAWCHR('\0'); |
| } |
| NAWstrcat(pWcs, pWcharBuf->data()); |
| defaultValueStr = (char *)pWcs; |
| NADELETE(pWcharBuf, NAWcharBuf, bindWA->wHeap()); |
| NADELETE(pCharBuf, charBuf, bindWA->wHeap()); |
| } |
| |
| if (NOT defaultValueStr AND bindWA) { |
| // 4107 column has no default so DEFAULT cannot be specified. |
| *CmpCommon::diags() << DgSqlCode(-4107) << DgColumnName(nacol->getColName()); |
| bindWA->setErrStatus(); |
| } |
| |
| return defaultValueStr; |
| } // Insert::getColDefaultValue() |
| |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Update |
| // ----------------------------------------------------------------------- |
| |
| RelExpr *Update::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| // Set flag for firstN in context |
| if (child(0) && child(0)->getOperatorType() == REL_SCAN) |
| if (child(0)->castToRelExpr() && |
| ((Scan *)(child(0)->castToRelExpr()))->getFirstNRows() >= 0) |
| if (bindWA && |
| bindWA->getCurrentScope() && |
| bindWA->getCurrentScope()->context()) |
| bindWA->getCurrentScope()->context()->firstN() = TRUE; |
| |
| setInUpdateOrInsert(bindWA, this, REL_UPDATE); |
| |
| RelExpr * boundExpr = GenericUpdate::bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| setInUpdateOrInsert(bindWA); |
| |
| if (getTableDesc()->getNATable()->isHbaseCellTable()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-1425) |
| << DgTableName(getTableDesc()->getNATable()->getTableName(). |
| getQualifiedNameAsAnsiString()) |
| << DgString0("Reason: Cannot update an hbase table in CELL format. Use ROW format for this operation."); |
| |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // QSTUFF |
| if (getGroupAttr()->isStream() && |
| !getGroupAttr()->isEmbeddedUpdateOrDelete()) { |
| *CmpCommon::diags() << DgSqlCode(-4173); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // QSTUFF |
| |
| if (NOT bindWA->errStatus() AND |
| NOT getTableDesc()->getVerticalPartitions().isEmpty()) |
| { |
| // 4058 UPDATE query cannot be used on a vertically partitioned table. |
| *CmpCommon::diags() << DgSqlCode(-4058) << |
| DgTableName(getTableDesc()->getNATable()->getTableName(). |
| getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // make sure scan done as part of an update runs in serializable mode so a |
| // tsj(scan,update) implementation of a update runs as an atomic operation |
| if (child(0)->getOperatorType() == REL_SCAN) { |
| Scan *scanNode = (Scan*)(child(0)->castToRelExpr()); |
| if (!scanNode->accessOptions().userSpecified()) { |
| scanNode->accessOptions().updateAccessOptions |
| (TransMode::ILtoAT(TransMode::SERIALIZABLE_)); |
| } |
| } |
| |
| // if FIRST_N is requested, insert a FirstN node. |
| if ((getOperatorType() == REL_UNARY_UPDATE) && |
| (child(0)->getOperatorType() == REL_SCAN)) |
| { |
| Scan * scanNode = (Scan *)(child(0)->castToRelExpr()); |
| if ((scanNode->getFirstNRows() != -1) && |
| (getGroupAttr()->isEmbeddedUpdateOrDelete())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4216); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (scanNode->getFirstNRows() >= 0) |
| { |
| FirstN * firstn = new(bindWA->wHeap()) |
| FirstN(scanNode, scanNode->getFirstNRows(), NULL); |
| firstn->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| setChild(0, firstn); |
| } |
| } |
| |
| // if rowset is used in set clause a direct rowset that is not in subquery |
| // must be present in the where clause |
| if ((bindWA->getHostArraysArea()) && |
| (bindWA->getHostArraysArea()->hasHostArraysInSetClause()) && |
| (!(bindWA->getHostArraysArea()->hasHostArraysInWhereClause()))) { |
| *CmpCommon::diags() << DgSqlCode(-30021) ; |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| NABoolean transformUpdateKey = updatesClusteringKeyOrUniqueIndexKey(bindWA); |
| if (bindWA->errStatus()) // error occurred in updatesCKOrUniqueIndexKey() |
| return this; |
| // To be removed when TRAFODION-1610 is implemented. |
| NABoolean xnsfrmHbaseUpdate = FALSE; |
| if ((hbaseOper()) && (NOT isMerge())) |
| { |
| if (CmpCommon::getDefault(HBASE_TRANSFORM_UPDATE_TO_DELETE_INSERT) == DF_ON) |
| { |
| xnsfrmHbaseUpdate = TRUE; |
| } |
| else if (getCheckConstraints().entries()) |
| { |
| xnsfrmHbaseUpdate = TRUE; |
| } |
| else if (getTableDesc()->getNATable()->isHbaseMapTable()) |
| { |
| xnsfrmHbaseUpdate = TRUE; |
| } |
| } |
| |
| if (xnsfrmHbaseUpdate) |
| { |
| ULng32 savedParserFlags = Get_SqlParser_Flags (0xFFFFFFFF); |
| Set_SqlParser_Flags(INTERNAL_QUERY_FROM_EXEUTIL); |
| |
| boundExpr = transformHbaseUpdate(bindWA); |
| |
| Assign_SqlParser_Flags (savedParserFlags); |
| } |
| else |
| // till here and remove the function transformHbaseUpdate also |
| if ((transformUpdateKey) && (NOT isMerge())) |
| { |
| boundExpr = transformUpdatePrimaryKey(bindWA); |
| } |
| else |
| boundExpr = handleInlining(bindWA, boundExpr); |
| |
| if (bindWA->errStatus()) // error occurred in transformUpdatePrimaryKey() |
| return this; // or handleInlining() |
| |
| return boundExpr; |
| } // Update::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class MergeUpdate |
| // ----------------------------------------------------------------------- |
| |
| RelExpr *MergeUpdate::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| if (needsBindScope_) |
| bindWA->initNewScope(); |
| |
| // For an xformed upsert any UDF or subquery is guaranteed to be |
| // in the using clause. Upsert will not generate a merge without using |
| // clause. ON clause, when matched SET clause and when not matched INSERT |
| // clauses all use expressions from the using clause. (same vid). |
| // Therefore any subquery or UDF in the using clause will flow to the |
| // rest of he tree through the TSJ and will be available. Each subquery |
| // will be evaluated only once, and will be evaluated prior to the merge |
| if (isMerge() && child(0) && !xformedUpsert()) |
| { |
| ItemExpr *selPred = child(0)->castToRelExpr()->selPredTree(); |
| if (selPred || where_) |
| { |
| NABoolean ONhasSubquery = (selPred && selPred->containsSubquery()); |
| NABoolean ONhasAggr = (selPred && selPred->containsAnAggregate()); |
| NABoolean whrHasSubqry = FALSE; |
| if (ONhasSubquery || ONhasAggr || |
| (where_ && ((whrHasSubqry=where_->containsSubquery()) || |
| where_->containsAnAggregate()))) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0 |
| (ONhasSubquery ? "Subquery in ON clause not allowed." : |
| (ONhasAggr ? "aggregate function in ON clause not allowed." : |
| (whrHasSubqry ? |
| "subquery in UPDATE ... WHERE clause not allowed." : |
| "aggregate function in UPDATE ... WHERE clause not allowed."))); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| ItemExpr *ONhasUDF = (selPred ? selPred->containsUDF() : NULL); |
| ItemExpr *whereHasUDF = (where_ ? where_->containsUDF() : NULL); |
| if (ONhasUDF || whereHasUDF) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4471) |
| << DgString0 |
| (((UDFunction *)(ONhasUDF ? ONhasUDF : whereHasUDF))-> |
| getFunctionName().getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } |
| |
| if (isMerge() && recExprTree() && !xformedUpsert()) |
| { |
| if (recExprTree()->containsSubquery()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" Subquery in SET clause not allowed."); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (recExprTree()->containsUDF()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4471) |
| << DgString0(((UDFunction *)recExprTree()->containsUDF())-> |
| getFunctionName().getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // if insertValues, then this is an upsert stmt. |
| if (insertValues()) |
| { |
| if (insertValues()->containsSubquery() && !xformedUpsert()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" Subquery in INSERT clause not allowed."); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (insertValues()->containsUDF() && !xformedUpsert()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4471) |
| << DgString0(((UDFunction *)insertValues()->containsUDF())-> |
| getFunctionName().getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| Tuple * tuple = new (bindWA->wHeap()) Tuple(insertValues()); |
| Insert * ins = new (bindWA->wHeap()) |
| Insert(getTableName(), |
| NULL, |
| REL_UNARY_INSERT, |
| tuple, |
| insertCols(), |
| NULL); |
| ins->setInsertType(Insert::SIMPLE_INSERT); |
| if (isMergeUpdate()) |
| ins->setIsMergeUpdate(TRUE); |
| else |
| ins->setIsMergeDelete(TRUE); |
| |
| ins->setTableDesc(getTableDesc()); |
| bindWA->getCurrentScope()->xtnmStack()->createXTNM(); |
| ins = (Insert*)ins->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| bindWA->getCurrentScope()->xtnmStack()->removeXTNM(); |
| |
| mergeInsertRecExpr() = ins->newRecExpr(); |
| mergeInsertRecExprArray() = ins->newRecExprArray(); |
| } |
| |
| NATable *naTable = bindWA->getNATable(getTableName()); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| if ((naTable->getViewText() != NULL) && (naTable->getViewCheck())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" View with check option not allowed."); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if ((naTable->isHbaseCellTable()) || |
| (naTable->isHbaseRowTable())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0("Hbase tables not supported."); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (naTable->isHiveTable()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0("Hive tables not supported."); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| bindWA->setMergeStatement(TRUE); |
| |
| // Create a merge IUD indicator, a CHAR(1) CHARACTER SET ISO88591 |
| // NOT NULL variable, that can be used by index maintenance and |
| // other operations to find out what action the WHEN clause |
| // indicated, insert (I), update (U) or delete (D). This will be |
| // removed in GenericUpdate::normalizeNode() if nobody asked for |
| // it. The actual value will be produced by the executor work |
| // method, there is no expression for it. |
| if (getProducedMergeIUDIndicator() == NULL_VALUE_ID) |
| { |
| ItemExpr *mergeIUDIndicator = new(bindWA->wHeap()) NATypeToItem( |
| new(bindWA->wHeap()) SQLChar( |
| 1, FALSE, FALSE, FALSE, FALSE, CharInfo::ISO88591)); |
| |
| mergeIUDIndicator = mergeIUDIndicator->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| setProducedMergeIUDIndicator(mergeIUDIndicator->getValueId()); |
| } |
| |
| RelExpr * boundExpr = Update::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| if (checkForMergeRestrictions(bindWA)) |
| return NULL; |
| |
| if (where_) { |
| bindWA->getCurrentScope()->context()->inWhereClause() = TRUE; |
| where_->convertToValueIdSet(mergeUpdatePred(), bindWA, ITM_AND); |
| bindWA->getCurrentScope()->context()->inWhereClause() = FALSE; |
| |
| if (bindWA->errStatus()) return NULL; |
| |
| // any values added by where_ to Outer References Set should be |
| // added to input values that must be supplied to this MergeUpdate |
| getGroupAttr()->addCharacteristicInputs |
| (bindWA->getCurrentScope()->getOuterRefs()); |
| } |
| |
| if (needsBindScope_) |
| bindWA->removeCurrentScope(); |
| |
| bindWA->setMergeStatement(TRUE); |
| |
| return boundExpr; |
| } // MergeUpdate::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Delete |
| // ----------------------------------------------------------------------- |
| |
| RelExpr *Delete::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| // Save the current scope and node for children to peruse if necessary. |
| BindContext *context = bindWA->getCurrentScope()->context(); |
| if (context) { |
| context->deleteScope() = bindWA->getCurrentScope(); |
| context->deleteNode() = this; |
| if (getFirstNRows() >= 0) context->firstN() = TRUE; |
| } |
| |
| RelExpr * boundExpr = GenericUpdate::bindNode(bindWA); |
| if (bindWA->errStatus()) return boundExpr; |
| |
| if ((csl_) && |
| (NOT getTableDesc()->getNATable()->isHbaseRowTable())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-1425) |
| << DgTableName(getTableDesc()->getNATable()->getTableName(). |
| getQualifiedNameAsAnsiString()); |
| |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (getTableDesc()->getNATable()->isHbaseCellTable()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-1425) |
| << DgTableName(getTableDesc()->getNATable()->getTableName(). |
| getQualifiedNameAsAnsiString()) |
| << DgString0("Reason: Cannot delete from an hbase table in CELL format. Use ROW format for this operation."); |
| |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // QSTUFF |
| if (getGroupAttr()->isStream() && |
| !getGroupAttr()->isEmbeddedUpdateOrDelete()) { |
| *CmpCommon::diags() << DgSqlCode(-4180); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // QSTUFF |
| |
| // Not only are check constraints on a DELETE nonsensical, |
| // but they can cause VEGReference::replaceVEGReference to assert |
| // with valuesToBeBound.isEmpty (Genesis 10-980202-0718). |
| // |
| // in case we are binding a generic update within a generic update |
| // due to view expansion we would like to ensure that all constraints |
| // are checked properly for the update operation performed on the |
| // underlying base table |
| if (NOT (bindWA->inViewExpansion() && bindWA->inGenericUpdate())) { // QSTUFF |
| getTableDesc()->checkConstraints().clear(); |
| checkConstraints().clear(); |
| } |
| |
| if (NOT getTableDesc()->getVerticalPartitions().isEmpty()) |
| { |
| // 4029 DELETE query cannot be used on a vertically partitioned table. |
| *CmpCommon::diags() << DgSqlCode(-4029) << |
| DgTableName(getTableDesc()->getNATable()->getTableName(). |
| getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| Scan *scanNode = NULL; |
| // make sure scan done as part of a delete runs in serializable mode so a |
| // tsj(scan,delete) implementation of a delete runs as an atomic operation |
| if (child(0)->getOperatorType() == REL_SCAN) { |
| scanNode = (Scan*)(child(0)->castToRelExpr()); |
| if (!scanNode->accessOptions().userSpecified()) { |
| scanNode->accessOptions().updateAccessOptions |
| (TransMode::ILtoAT(TransMode::SERIALIZABLE_)); |
| } |
| } |
| |
| BindScope *prevScope = NULL; |
| BindScope *currScope = bindWA->getCurrentScope(); |
| NABoolean inUnion = FALSE; |
| |
| while (currScope && !inUnion) |
| { |
| BindContext *currContext = currScope->context(); |
| if (currContext->inUnion()) |
| { |
| inUnion = TRUE; |
| } |
| prevScope = currScope; |
| currScope = bindWA->getPreviousScope(prevScope); |
| } |
| |
| RelRoot *root = bindWA->getTopRoot(); |
| |
| if (getFirstNRows() >= 0) // First N Delete |
| { |
| CMPASSERT(getOperatorType() == REL_UNARY_DELETE); |
| |
| // First N Delete on a partitioned table. Not considered a MTS delete. |
| if (getTableDesc()->getClusteringIndex()->isPartitioned()) |
| { |
| |
| if (root->getCompExprTree() || inUnion ) // for unions we know there is a select |
| { // outer selectnot allowed for "non-MTS" first N delete |
| *CmpCommon::diags() << DgSqlCode(-4216); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| RelExpr * childNode = child(0)->castToRelExpr(); |
| FirstN * firstn = new(bindWA->wHeap()) |
| FirstN(childNode, getFirstNRows(), NULL); |
| firstn->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| setChild(0, firstn); |
| setFirstNRows(-1); |
| |
| } |
| else |
| { |
| // First N delete on a single partition. This is considered a MTS Delete. |
| if ((bindWA->getHostArraysArea()) && |
| ((bindWA->getHostArraysArea()->hasHostArraysInWhereClause()) || |
| (bindWA->getHostArraysArea()->getHasSelectIntoRowsets()))) |
| { // MTS delete not supported with rowsets |
| *CmpCommon::diags() << DgSqlCode(-30037); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| |
| if (scanNode && scanNode->getSelectionPred().containsSubquery()) |
| { |
| // MTS Delete not supported with subquery in where clause |
| *CmpCommon::diags() << DgSqlCode(-4138); |
| |
| bindWA->setErrStatus(); |
| return this; |
| |
| } |
| |
| if (root->hasOrderBy()) |
| { // mts delete not supported with order by |
| *CmpCommon::diags() << DgSqlCode(-4189); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (root->getCompExprTree() || // MTS Delete has an outer select |
| bindWA->isInsertSelectStatement() || // Delete inside an Insert Select statement, Soln:10-061103-0274 |
| inUnion ) // for unions we know there is a select |
| { |
| if (root->getFirstNRows() < -1 || |
| inUnion) // for unions we wish to raise a union |
| { // The outer select has a Last 1/0 clause // specific error later, so set the flag now. |
| setMtsStatement(TRUE); |
| } |
| else |
| { // raise an error if no Last 1 clause is found. |
| *CmpCommon::diags() << DgSqlCode(-4136); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } |
| } |
| |
| // Triggers -- |
| |
| if ((NOT isFastDelete()) && (NOT noIMneeded())) |
| boundExpr = handleInlining(bindWA, boundExpr); |
| else if (hbaseOper() && (getGroupAttr()->isEmbeddedUpdateOrDelete())) |
| { |
| setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA)); |
| |
| CorrName corrOLDTable (getScanNode(TRUE)->getTableDesc()->getCorrNameObj().getQualifiedNameObj(), |
| bindWA->wHeap(),"OLD"); |
| |
| // expose OLD table columns |
| getRETDesc()->addColumns(bindWA, *child(0)->getRETDesc(), &corrOLDTable); |
| |
| ValueIdList outputs; |
| getRETDesc()->getValueIdList(outputs, USER_AND_SYSTEM_COLUMNS); |
| addPotentialOutputValues(outputs); |
| |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| } |
| |
| if (isMtsStatement()) |
| bindWA->setEmbeddedIUDStatement(TRUE); |
| |
| if (getFirstNRows() > 0) |
| { |
| // create a firstN node to delete FIRST N rows, if no such node was created |
| // during handleInlining. Occurs when DELETE FIRST N is used on table with no |
| // dependent objects. |
| FirstN * firstn = new(bindWA->wHeap()) |
| FirstN(boundExpr, getFirstNRows()); |
| if (NOT(scanNode && scanNode->getSelectionPred().containsSubquery())) |
| firstn->setCanExecuteInDp2(TRUE); |
| firstn->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| setFirstNRows(-1); |
| boundExpr = firstn; |
| } |
| |
| if (csl()) |
| { |
| for (Lng32 i = 0; i < csl()->entries(); i++) |
| { |
| NAString * nas = (NAString*)(*csl())[i]; |
| |
| bindWA->hbaseColUsageInfo()->insert |
| ((QualifiedName*)&getTableDesc()->getNATable()->getTableName(), nas); |
| } |
| } |
| |
| return boundExpr; |
| } // Delete::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class MergeDelete |
| // ----------------------------------------------------------------------- |
| |
| RelExpr *MergeDelete::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| bindWA->initNewScope(); |
| |
| if ((isMerge()) && |
| (child(0)) && |
| (child(0)->castToRelExpr()->selPredTree())) |
| { |
| if (child(0)->castToRelExpr()->selPredTree()->containsSubquery()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" Subquery in ON clause not allowed."); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (child(0)->castToRelExpr()->selPredTree()->containsUDF()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4471) |
| << DgString0(((UDFunction *)child(0)-> |
| castToRelExpr()->selPredTree()-> |
| containsUDF())-> |
| getFunctionName().getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| |
| // if insertValues, then this is an upsert stmt. |
| if (insertValues()) |
| { |
| if (insertValues()->containsSubquery()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" Subquery in INSERT clause not allowed."); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (insertValues()->containsUDF()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4471) |
| << DgString0(((UDFunction *)insertValues()-> |
| containsUDF())-> |
| getFunctionName().getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (CmpCommon::getDefault(COMP_BOOL_175) == DF_OFF) |
| { |
| // MERGE DELETE + INSERT is buggy, so disallow it unless CQD is on. In |
| // particular, the optimizer sometimes fails to produce a plan in phase 1. |
| // JIRA TRAFODION-1509 covers completing the MERGE DELETE + INSERT feature. |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" MERGE DELETE not allowed with INSERT."); |
| |
| } |
| |
| Tuple * tuple = new (bindWA->wHeap()) Tuple(insertValues()); |
| Insert * ins = new (bindWA->wHeap()) |
| Insert(getTableName(), |
| NULL, |
| REL_UNARY_INSERT, |
| tuple, |
| insertCols(), |
| NULL); |
| ins->setInsertType(Insert::SIMPLE_INSERT); |
| ins->setIsMergeDelete(TRUE); |
| |
| ins->setTableDesc(getTableDesc()); |
| bindWA->getCurrentScope()->xtnmStack()->createXTNM(); |
| ins = (Insert*)ins->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| bindWA->getCurrentScope()->xtnmStack()->removeXTNM(); |
| |
| mergeInsertRecExpr() = ins->newRecExpr(); |
| mergeInsertRecExprArray() = ins->newRecExprArray(); |
| } |
| |
| NATable *naTable = bindWA->getNATable(getTableName()); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| if ((naTable->getViewText() != NULL) && (naTable->getViewCheck())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" View with check option not allowed."); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| bindWA->setMergeStatement(TRUE); |
| RelExpr * boundExpr = Delete::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| if (checkForMergeRestrictions(bindWA)) |
| return NULL; |
| |
| bindWA->removeCurrentScope(); |
| |
| bindWA->setMergeStatement(TRUE); |
| |
| return boundExpr; |
| } // MergeDelete::bindNode() |
| |
| static const char NEWTable [] = "NEW"; // QSTUFF: corr for embedded d/u |
| static const char OLDTable [] = "OLD"; // QSTUFF: corr for embedded d/u |
| |
| // QSTUFF |
| // this method binds both, the set clauses applied to the after |
| // image as well as the set clauses applied to the before image |
| // the new set on rollback clause allows an application to modify |
| // the before image. |
| // delete from tab set on rollback x = 1; |
| // update tab set x = 1 set on rollback x = 2; |
| #pragma nowarn(770) // warning elimination |
| void GenericUpdate::bindUpdateExpr(BindWA *bindWA, |
| ItemExpr *recExpr, |
| ItemExprList &assignList, |
| RelExpr *boundView, |
| Scan *scanNode, |
| SET(short) &stoiColumnSet, |
| NABoolean onRollback) |
| { |
| |
| RETDesc *origScope = NULL; |
| |
| ValueIdSet &newRecExpr = |
| (onRollback == TRUE) ? newRecBeforeExpr() : this->newRecExpr(); |
| ValueIdArray &newRecExprArray = |
| (onRollback == TRUE) ? newRecBeforeExprArray() : this->newRecExprArray(); |
| |
| if (onRollback && |
| ((!getTableDesc()->getClusteringIndex()->getNAFileSet()->isAudited()) || |
| (getTableDesc()->getNATable()->hasLobColumn()))) { |
| // SET ON ROLLBACK clause is not allowed on a non-audited table |
| *CmpCommon::diags() << DgSqlCode(-4214) |
| << DgTableName(getTableDesc()->getNATable()->getTableName(). |
| getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return; |
| } |
| |
| CollIndex i, j; |
| CollIndexList colnoList(STMTHEAP); // map of col nums (row positions) |
| CollIndex a = assignList.entries(); |
| |
| const ColumnDescList *viewColumns = NULL; |
| |
| // if this is a view then get the columns of the view |
| if (boundView) { |
| viewColumns = boundView->getRETDesc()->getColumnList(); |
| } |
| |
| // if the GU has a SET ON ROLLBACK clause this method is called |
| // twice: once to bind the columns in the SET clause and a second |
| // time to bind the columns in the SET ON ROLLBACK clause. |
| // Initially the update column list of the stoi_ is empty. |
| // If this is the second call, store the update column list |
| // from the first call. |
| short *stoiColumnList = NULL; |
| CollIndex currColumnCount = 0; |
| if (currColumnCount = stoi_->getStoi()->getColumnListCount()) |
| { |
| stoiColumnList = new (bindWA->wHeap()) short[currColumnCount]; |
| |
| for (i = 0; i < currColumnCount; i++) |
| stoiColumnList[i] = stoi_->getStoi()->getUpdateColumn(i); |
| } |
| |
| stoi_->getStoi()->setColumnList(new (bindWA->wHeap()) short[a + currColumnCount]); |
| |
| for (i = 0; i < a; i++) { |
| CMPASSERT(assignList[i]->getOperatorType() == ITM_ASSIGN); |
| assignList[i]->child(0)->bindNode(bindWA); // LHS |
| if (bindWA->errStatus()) return; |
| const NAColumn *nacol = assignList[i]->child(0).getNAColumn(); |
| if(getOperatorType() == REL_UNARY_UPDATE) |
| { |
| stoi_->getStoi()->setUpdateColumn(i, (short) nacol->getPosition()); |
| stoi_->getStoi()->incColumnListCount(); |
| stoi_->addUpdateColumn(nacol->getPosition()); |
| } |
| const NAType *colType = nacol->getType(); |
| if (!colType->isSupportedType()) { |
| *CmpCommon::diags() << DgSqlCode(-4028) // 4028 table not updatatble |
| << DgTableName(nacol->getNATable()->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return; |
| } |
| |
| // If this is a sequence generator IDENTITY column |
| // with a default type of GENERATED ALWAYS, |
| // then post error -3428. GENERATED ALWAYS |
| // IDENTITY columns may not be updated. |
| if(getOperatorType() == REL_UNARY_UPDATE && |
| CmpCommon::getDefault(COMP_BOOL_210) == DF_ON && |
| nacol->isIdentityColumnAlways()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3428) |
| << DgString0(nacol->getColName()); |
| bindWA->setErrStatus(); |
| return; |
| } |
| |
| colnoList.insert(nacol->getPosition()); // save colno for next loop |
| |
| // in case its not a view we record the column position of the |
| // base table, otherwise that of the view |
| |
| if (NOT boundView) |
| stoiColumnSet.insert((short) nacol->getPosition()); |
| |
| // if this is a view get the positions of the columns |
| // within the view that are being updated. |
| if (boundView) { |
| ValueId vid = assignList[i]->child(0).getValueId(); |
| NABoolean found = FALSE; |
| for (CollIndex k=0; k < viewColumns->entries(); k++) { |
| if ((*viewColumns)[k]->getValueId() == vid) { |
| stoiColumnSet.insert((short) k); |
| found = TRUE; |
| // Updatable views cannot have any underlying basetable column |
| // appear more than once, so it's safe to break out of the loop. |
| break; |
| } |
| } // loop k |
| CMPASSERT(found); |
| } // boundView |
| } // loop i<a |
| |
| // If this is the second call to this method, restore the update |
| // columns bound in the first call |
| if (currColumnCount) |
| { |
| for (i = a; i < (currColumnCount + a); i++) |
| { |
| stoi_->getStoi()->setUpdateColumn(i, stoiColumnList[i-a]); |
| stoi_->addUpdateColumn(stoiColumnList[i-a]); |
| } |
| } |
| |
| // RHS: Bind the right side of the Assigns such that the source expressions |
| // reference the columns of the source table. |
| // |
| //### With a cascade of views, should this be "getRETDesc" as is, |
| //### or "scanNode->getRETDesc" ? --? |
| //### Should I set this->setRD to be the target(new)tbl at the beginning, |
| //### explicitly say "scanNode..." here? --i think not |
| // |
| |
| if (GU_DEBUG) GU_DEBUG_Display(bindWA, this, "u"); |
| |
| origScope = bindWA->getCurrentScope()->getRETDesc(); |
| |
| // this sets the scope to the scan table for the before values |
| // the previous scope was to the "UPDATE" table |
| // we will reset the scope before returning in order not to introduce |
| // hidden side effects but have the generic update explicitely point |
| // to the scan scope |
| |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| |
| //this has to be done after binding the LHS because of triggers |
| //Soln :10-050110-3403 : Don't side-effect the SET on ROLLBACK list |
| //when we come down to process it the next time over.So process only |
| //the assignList |
| ItemExpr* tempExpr = assignList.convertToItemExpr(); |
| tempExpr->convertToValueIdSet(newRecExpr, bindWA, ITM_ITEM_LIST); |
| if (bindWA->errStatus()) return; |
| |
| if (NOT onRollback) |
| { |
| for (ValueId v = newRecExpr.init(); newRecExpr.next(v); |
| newRecExpr.advance(v)) |
| { |
| CMPASSERT(v.getItemExpr()->getOperatorType() == ITM_ASSIGN); |
| |
| // remove all the onrollack expressions |
| if (((Assign *)v.getItemExpr())->onRollback()) |
| { |
| newRecExpr.remove(v); |
| } |
| } |
| } |
| else |
| { |
| for (ValueId v = newRecExpr.init(); newRecExpr.next(v); |
| newRecExpr.advance(v)) |
| { |
| CMPASSERT(v.getItemExpr()->getOperatorType() == ITM_ASSIGN); |
| |
| // remove all the NON-onrollack expressions |
| if ((getOperatorType() == REL_UNARY_UPDATE) && |
| !(((Assign *)v.getItemExpr())->onRollback())) |
| { |
| newRecExpr.remove(v); |
| } |
| } |
| |
| if (getOperatorType() == REL_UNARY_DELETE) |
| { |
| recExpr->convertToValueIdSet(this->newRecExpr(), bindWA, ITM_ITEM_LIST); |
| } |
| } |
| |
| // now we built the RHS |
| // Now we have our colnoList map with which to build a temporary array |
| // (with holes) and get the update columns ordered (eliminating dups). |
| // Actually we store the ids of the bound Assign nodes corresponding |
| // to the columns, of course. |
| // |
| CollIndex totalColCount = getTableDesc()->getColumnList().entries(); |
| #pragma nowarn(1506) // warning elimination |
| ValueIdArray holeyArray(totalColCount); |
| #pragma warn(1506) // warning elimination |
| ValueId assignId; // i'th newRecExpr valueid |
| for (i = 0, assignId = newRecExpr.init(); // bizarre ValueIdSet iter |
| newRecExpr.next(assignId); |
| i++, newRecExpr.advance(assignId)) { |
| j = colnoList[i]; |
| if (holeyArray.used(j)) { |
| const NAColumn *nacol = holeyArray[j].getItemExpr()->child(0).getNAColumn(); |
| //4022 target column multiply specified |
| *CmpCommon::diags() << DgSqlCode(-4022) << DgColumnName(nacol->getColName()); |
| bindWA->setErrStatus(); |
| return; |
| } |
| holeyArray.insertAt(j, assignId); |
| } |
| |
| // |
| // Now we have the holey array. The next loop ignores unused entries |
| // and copies the used entries into newRecExprArray(), with no holes. |
| // It also builds a list of the columns being updated that contain |
| // a column on the right side of the SET assignment expression. |
| // |
| // Entering this loop, i is the number of specified update columns; |
| // exiting, j is. |
| // |
| CMPASSERT(i == a); |
| |
| // we built a map between identifical old and new columns, i.e. columns |
| // which are not updated and thus identical. We insert the resulting |
| // equivalence relationships e.g. old.a = new.a during transformation |
| // into the respective VEGGIES this allows the optimizer to select index |
| // scan for satisfying order requirements specified by an order by clause |
| // on new columns, e.g. |
| // select * from (update t set y = y + 1 return new.a) t order by a; |
| // we cannot get the benefit of this VEG for a merge statement when IM is required |
| // allowing a VEG in this case causes corruption on base table key values because |
| // we use the "old" value of key column from fetchReturnedExpr, which can be junk |
| // in case there is no row to update/delete, and a brand bew row is being inserted |
| NABoolean mergeWithIndex = isMerge() && getTableDesc()->hasSecondaryIndexes() ; |
| if ((NOT onRollback) && (NOT mergeWithIndex)){ |
| for (i = 0;i < totalColCount; i++){ |
| if (!(holeyArray.used(i))){ |
| oldToNewMap().addMapEntry( |
| scanNode->getTableDesc()-> |
| getColumnList()[i].getItemExpr()->getValueId(), |
| getTableDesc()-> |
| getColumnList()[i].getItemExpr()->getValueId()); |
| } |
| } |
| } |
| |
| // when binding a view which contains an embedded update |
| // we must map update valueids to scan value ids |
| // to allow for checking of access rights. |
| for (i = 0; i < getTableDesc()->getColumnList().entries();i++) |
| bindWA->getUpdateToScanValueIds().addMapEntry( |
| getTableDesc()->getColumnList()[i].getItemExpr()->getValueId(), |
| scanNode->getTableDesc()->getColumnList()[i].getItemExpr()->getValueId()); |
| |
| newRecExprArray.resize(i); |
| TableDesc *scanDesc = scanNode->getTableDesc(); |
| NABoolean rightContainsColumn = FALSE; |
| |
| for (i = j = 0; i < totalColCount; i++) { |
| if (holeyArray.used(i)) { |
| ValueId assignExpr = holeyArray[i]; |
| newRecExprArray.insertAt(j++, assignExpr); |
| ItemExpr *right = assignExpr.getItemExpr()->child(1); |
| |
| // even if a column is set to a constant we mark it |
| // as updated to prevent indices covering this column from |
| // being used for access |
| ItemExpr *left = assignExpr.getItemExpr()->child(0); |
| |
| scanDesc->addColUpdated(left->getValueId()); |
| |
| if (right->containsColumn()) |
| rightContainsColumn = TRUE; |
| } |
| } |
| |
| // WITH NO ROLLBACK not supported if rightside of update |
| // contains a column expression. Also this feature is not |
| // supported with the SET ON ROLLBACK feature |
| if (isNoRollback() || |
| (CmpCommon::transMode()->getRollbackMode() == TransMode::NO_ROLLBACK_)) |
| { |
| if ((rightContainsColumn && CmpCommon::getDefault(ALLOW_RISKY_UPDATE_WITH_NO_ROLLBACK) == DF_OFF) || onRollback) |
| { |
| NAString warnMsg = ""; |
| if(rightContainsColumn) |
| { |
| warnMsg = "Suggestion: Set ALLOW_RISKY_UPDATE_WITH_NO_ROLLBACK CQD to ON to allow"; |
| if (getOperatorType() == REL_UNARY_DELETE) |
| warnMsg += " DELETE "; |
| else |
| warnMsg += " UPDATE "; |
| warnMsg += "command with right-hand side SET clause consisting of columns."; |
| } |
| |
| if (getOperatorType() == REL_UNARY_DELETE) |
| *CmpCommon::diags() << DgSqlCode(-3234) << DgString0(warnMsg); |
| else |
| *CmpCommon::diags() << DgSqlCode(-3233) << DgString0(warnMsg); |
| bindWA->setErrStatus(); |
| return ; |
| } |
| } |
| |
| CMPASSERT(j == a); |
| bindWA->getCurrentScope()->setRETDesc(origScope); |
| } |
| #pragma warn(770) // warning elimination |
| |
| void getScanPreds(RelExpr *start, ValueIdSet &preds) |
| { |
| RelExpr *result = start; |
| |
| while (result) { |
| preds += result->selectionPred(); |
| |
| if (result->getOperatorType() == REL_SCAN) break; |
| if (result->getArity() > 1) { |
| return ; |
| } |
| result = result->child(0); |
| } |
| |
| return; |
| } |
| |
| |
| // Note that this is the R2 compatible way to handle Halloween problem. |
| // This update (only insert for now) contains a reference to the |
| // target in the source. This could potentially run into the so |
| // called Halloween problem. Determine if this is a case we may be |
| // able to handle. The cases that we handle are: |
| // |
| // -- The reference to the target is in a subquery |
| // -- There any number of references to the target in the source |
| // -- The subquery cannot be a row subquery. |
| // -- The subquery must contain only one source (the reference to the target) |
| // -- |
| // |
| // Return TRUE if this does represent a Halloween problem and the caller will |
| // then issue the error message |
| // |
| // Return FALSE is this is a case we can handle. Set the |
| // 'avoidHalloweenR2' flag in the subquery and this generic Update so |
| // that the optimizer will pick a plan that is Halloween safe. |
| // |
| NABoolean GenericUpdate::checkForHalloweenR2(Int32 numScansToFind) |
| { |
| |
| // If there are no scans, no problem, return okay (FALSE) |
| // |
| if(numScansToFind == 0) { |
| return FALSE; |
| } |
| |
| // Allow any number of scans |
| |
| // Do not support for general NEO users. |
| if (CmpCommon::getDefault(MODE_SPECIAL_1) == DF_OFF) |
| return TRUE; |
| |
| // Number of scans of the target table found so far. |
| // |
| Int32 numHalloweenScans = 0; |
| |
| // Get the primary source of the generic update. We are looking for |
| // the halloween scans in the predicates of this scan node |
| // |
| ValueIdSet preds; |
| getScanPreds(this, preds); |
| |
| Subquery *subq; |
| |
| // Search the preds of this scan for subqueries. |
| // |
| // ValueIdSet &preds = scanNode->selectionPred(); |
| |
| for(ValueId p = preds.init(); preds.next(p); preds.advance(p)) { |
| ItemExpr *pred = p.getItemExpr(); |
| |
| // If this pred contains a subquery, find the scans |
| // |
| if(pred->containsSubquery()) { |
| ValueIdSet subqPreds; |
| subqPreds += pred->getValueId(); |
| |
| // Search all the preds and their children |
| // |
| while(subqPreds.entries()) { |
| ValueIdSet children; |
| for(ValueId s = subqPreds.init(); |
| subqPreds.next(s); |
| subqPreds.advance(s)) { |
| ItemExpr *term = s.getItemExpr(); |
| |
| // Found a subquery, now look for the scan... |
| // |
| if(term->isASubquery()) { |
| subq = (Subquery *)term; |
| |
| // We don't support row subqueries, keep looking for the scan |
| // in the next subquery. |
| if(!subq->isARowSubquery()) { |
| |
| // Is this the subquery that has the scan of the table |
| // we are updating? |
| // |
| Scan *halloweenScan = subq->getSubquery()->getScanNode(FALSE); |
| if(halloweenScan) { |
| |
| // Is this the scan we are looking for? |
| // |
| if(halloweenScan->getTableDesc()->getNATable() == |
| getTableDesc()->getNATable()) { |
| subq->setAvoidHalloweenR2(this); |
| numHalloweenScans++; |
| } |
| } |
| } |
| } |
| |
| // Follow all the children as well. |
| // |
| for(Int32 i = 0; i < term->getArity(); i++) { |
| children += term->child(i)->getValueId(); |
| } |
| } |
| subqPreds = children; |
| } |
| } |
| } |
| |
| setAvoidHalloweenR2(numScansToFind); |
| |
| // If we found and marked all the halloween scans, then return FALSE (allow). |
| // We have marked the subqueries to avoid the halloween problem. This will |
| // force the optimizer to pick a plan that will be safe. |
| // |
| if(numHalloweenScans == numScansToFind) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| // See ANSI 7.9 SR 12 + 6.3 SR 8 for definition of "updatable" table |
| // references; in particular, note that one of the requirements for a view's |
| // being updatable is that ultimately underlying it (passing through a |
| // whole stack of views) is *exactly one* wbase table -- i.e., no joins |
| // allowed. |
| // |
| RelExpr * GenericUpdate::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // QSTUFF |
| // we indicate that we are in a generic update. If we are |
| // already in a generic update we know that this time we are |
| // binding a generic update within a view. |
| // however be aware of the following scenario. We currently |
| // reject embedded updates and streams in the source but |
| // obviously allow view with embedded updates as a target. |
| // Since its already within a generic update we will only |
| // return the scan node to the insert |
| // |
| // insert into select ... from (update/delete ....) t; |
| // |
| // but not cause the update to be bound in when doing |
| // |
| // insert into viewWithDeleteOrUpdate values(...); |
| // |
| // in both cases we got an insert->update/delete->scan |
| NABoolean inGenericUpdate = FALSE; |
| |
| if (getOperatorType() != REL_UNARY_INSERT) |
| inGenericUpdate = bindWA->setInGenericUpdate(TRUE); |
| |
| NABoolean returnScanNode = |
| (inGenericUpdate && bindWA->inViewExpansion() && |
| ( getOperatorType() == REL_UNARY_DELETE || |
| getOperatorType() == REL_UNARY_UPDATE )); |
| |
| // those group attributes should be set only by the topmost |
| // generic update once we are invoked when already binding |
| // another generic we reset those group attributes since we |
| // already know that we will only return a scan node |
| |
| if ((returnScanNode) && (child(0))) { |
| child(0)->getGroupAttr()->setStream(FALSE); |
| child(0)->getGroupAttr()->setSkipInitialScan(FALSE); |
| child(0)->getGroupAttr()->setEmbeddedIUD(NO_OPERATOR_TYPE); |
| } |
| |
| // if we have no user-specified access options then |
| // get it from nearest enclosing scope that has one (if any) |
| if (!accessOptions().userSpecified()) { |
| StmtLevelAccessOptions *axOpts = bindWA->findUserSpecifiedAccessOption(); |
| if (axOpts) { |
| accessOptions() = *axOpts; |
| } |
| } |
| // The above code is in Scan::bindNode also. |
| // It would be nice to refactor this common code; someday. |
| |
| // Make sure we have the appropriate transaction mode & isolation level |
| // in order to do the update. Genesis 10-970922-3488. |
| // Keep this logic in sync with Generator::verifyUpdatableTransMode()! |
| Lng32 sqlcodeA = 0, sqlcodeB = 0; |
| |
| // fix case 10-040429-7402 by checking our statement level access options |
| // first before declaring any error 3140/3141. |
| TransMode::IsolationLevel il; |
| ActiveSchemaDB()->getDefaults().getIsolationLevel |
| (il, |
| CmpCommon::getDefault(ISOLATION_LEVEL_FOR_UPDATES)); |
| verifyUpdatableTrans(&accessOptions(), CmpCommon::transMode(), |
| il, |
| sqlcodeA, sqlcodeB); |
| |
| if (sqlcodeA || sqlcodeB) { |
| // 3140 The isolation level cannot be READ UNCOMMITTED. |
| // 3141 The transaction access mode must be READ WRITE. |
| if (sqlcodeA) *CmpCommon::diags() << DgSqlCode(sqlcodeA); |
| if (sqlcodeB) *CmpCommon::diags() << DgSqlCode(sqlcodeB); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| Int64 transId=-1; |
| if ((isNoRollback() && |
| (NOT (Get_SqlParser_Flags(INTERNAL_QUERY_FROM_EXEUTIL)))) && |
| ((CmpCommon::transMode()->getAutoCommit() != TransMode::ON_ ) || |
| (NAExecTrans(0, transId)))) { |
| |
| // do not return an error if this is a showplan query being compiled |
| // in the second arkcmp. |
| const NAString * val = |
| ActiveControlDB()->getControlSessionValue("SHOWPLAN"); |
| if (NOT ((val) && (*val == "ON"))) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3231); // Autocommit must be ON, |
| bindWA->setErrStatus(); // if No ROLLBACK is specified in IUD statement syntax |
| return this; |
| } |
| } |
| |
| if (isNoRollback() || |
| (CmpCommon::transMode()->getRollbackMode() == TransMode::NO_ROLLBACK_)) |
| { |
| if ((child(0)->getGroupAttr()->isStream()) || |
| (child(0)->getGroupAttr()->isEmbeddedUpdateOrDelete()) || |
| (updateCurrentOf())) |
| { |
| if (getOperatorType() == REL_UNARY_DELETE) |
| *CmpCommon::diags() << DgSqlCode(-3234); |
| else |
| *CmpCommon::diags() << DgSqlCode(-3233); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // The SQL standard as defined in ISO/IEC JTC 1/SC 32 date: 2009-01-12 |
| // CD 9075-2:200x(E) published by ISO/IEC JTC 1/SC 32/WG 3 |
| // "Information technology -- Database languages -- SQL -- |
| // Part2: Foundation (SQL/Foundation)", page 920, section 14.14, |
| // page 918, section 14.13, page 900, section 14.9, page 898, section 14.8 |
| // does allow correlation names in update & delete statements. |
| // Therefore, we delete this unnecessary restriction as part of the fix |
| // for genesis solution 10-090921-4747: |
| // Many places in this method assume the specified target table |
| // has no correlation name -- indeed, Ansi syntax does not allow one -- |
| // this assert is to catch any future syntax-extensions we may do. |
| // |
| // E.g., see code marked |
| // ##SQLMP-SYNTAX-KLUDGE## |
| // in SqlParser.y + SqlParserAux.cpp, |
| // which add a non-Ansi corr name to all table refs |
| // when they really only should add to SELECTed tables. |
| // So here, in an INSERT/UPDATE/DELETEd table, |
| // we UNDO that kludge. |
| // |
| //if (!getTableName().getCorrNameAsString().isNull()) { |
| //CMPASSERT(SqlParser_NAMETYPE == DF_NSK || |
| // HasMPLocPrefix(getTableName().getQualifiedNameObj().getCatalogName())); |
| //getTableName().setCorrName(""); // UNDO that kludge! |
| //} |
| |
| // Genesis 10-980831-4973 |
| if (((getTableName().isLocationNameSpecified() || |
| getTableName().isPartitionNameSpecified()) && |
| (!Get_SqlParser_Flags(ALLOW_SPECIALTABLETYPE))) && |
| (getOperatorType() != REL_UNARY_DELETE)) { |
| *CmpCommon::diags() << DgSqlCode(-4061); // 4061 a partn not ins/upd'able |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // -- Triggers |
| // If this node is part of the action of a trigger, |
| // then don't count the rows that are affected. |
| if (bindWA->findNextScopeWithTriggerInfo() != NULL) |
| { |
| rowsAffected_ = DO_NOT_COMPUTE_ROWSAFFECTED; |
| |
| // Does the table name match the name of one of the transition tables? |
| if (updatedTableName_.isATriggerTransitionName(bindWA)) |
| { |
| // 11020 Ambiguous or illegal use of transition name $0~string0. |
| *CmpCommon::diags() << DgSqlCode(-11020) |
| << DgString0(getTableName().getQualifiedNameAsString()); |
| |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // Get the NATable for this object, and an initial ref count. |
| // Set up stoi. |
| // |
| // We do not suppress mixed name checking in getNATable for R1 |
| // from here, because prototype name executes through here. We |
| // want to check prototype name. |
| |
| const NATable *naTable = bindWA->getNATable(getTableName()); |
| if (bindWA->errStatus()) return this; |
| |
| if (naTable && naTable->isHbaseTable()) |
| hbaseOper() = TRUE; |
| |
| if (naTable && naTable->isHbaseMapTable() && |
| (CmpCommon::getDefault(TRAF_HBASE_MAPPED_TABLES_IUD) == DF_OFF)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4223) |
| << DgString0("Insert/Update/Delete on HBase mapped tables is"); |
| |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if ((CmpCommon::getDefault(ALLOW_DML_ON_NONAUDITED_TABLE) == DF_OFF) && |
| naTable && naTable->getClusteringIndex() && |
| (!naTable->getClusteringIndex()->isAudited()) |
| // && !bindWA->isBindingMvRefresh() // uncomment if non-audit MVs are ever supported |
| ) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4211) |
| << DgTableName( |
| naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| Int32 beforeRefcount = naTable->getReferenceCount(); |
| |
| OptSqlTableOpenInfo *listedStoi |
| = setupStoi(stoi_, bindWA, this, naTable, getTableName()); |
| |
| if (getOperatorType() == REL_UNARY_INSERT && |
| NOT naTable->isInsertable()) { |
| *CmpCommon::diags() << DgSqlCode(-4027) // 4027 table not insertable |
| << DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (NOT naTable->isUpdatable()) { |
| *CmpCommon::diags() << DgSqlCode(-4028) // 4028 table not updatable |
| << DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (naTable->isVerticalPartition()) { |
| // LCOV_EXCL_START - cnu |
| // On attempt to update an individual VP, say: 4082 table not accessible |
| *CmpCommon::diags() << DgSqlCode(-4082) << |
| DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| // LCOV_EXCL_STOP |
| } |
| |
| |
| if (naTable->isAnMV()) |
| { |
| // we currently don't allow updating (deleting) MVs in a trigger action |
| if (bindWA->inDDL() && bindWA->isInTrigger ()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-11051); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // This table is a materialized view. Are we allowed to change it? |
| if ((getTableName().getSpecialType() != ExtendedQualName::MV_TABLE) && |
| (getTableName().getSpecialType() != ExtendedQualName::GHOST_MV_TABLE)) |
| { |
| // The special syntax flag was not used - |
| // Only on request MV allows direct DELETE operations by the user. |
| MVInfoForDML *mvInfo = ((NATable *)naTable)->getMVInfo(bindWA); |
| if (mvInfo->getRefreshType() == COM_ON_REQUEST && |
| getOperatorType() == REL_UNARY_DELETE) |
| { |
| // Set NOLOG flag. |
| setNoLogOperation(); |
| } |
| else |
| { |
| // Direct update is only allowed for User Maintainable MVs. |
| if (mvInfo->getRefreshType() != COM_BY_USER) |
| { |
| // A Materialized View cannot be directly updated. |
| *CmpCommon::diags() << DgSqlCode(-12074); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } |
| |
| // If this is not an INTERNAL REFRESH command, make sure the MV is |
| // initialized and available. |
| // If this is FastDelete using parallel purgedata, do not enforce |
| // that MV is initialized. |
| if (!bindWA->isBindingMvRefresh()) |
| { |
| if (NOT ((getOperatorType() == REL_UNARY_DELETE) && |
| (((Delete*)this)->isFastDelete()))) |
| { |
| if (naTable->verifyMvIsInitializedAndAvailable(bindWA)) |
| return NULL; |
| } |
| } |
| } |
| |
| if (naTable->isAnMVMetaData() && |
| getTableName().getSpecialType() != ExtendedQualName::MVS_UMD) |
| { |
| if (getTableName().getPrototype() == NULL || |
| getTableName().getPrototype()->getSpecialType() != ExtendedQualName::MVS_UMD) |
| { // ERROR 12075: A Materialized View Metadata Table cannot be directly updated. |
| *CmpCommon::diags() << DgSqlCode(-12075); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| if ((naTable->isSeabaseTable()) && |
| (naTable->isSeabaseMDTable() || |
| naTable->isSeabasePrivSchemaTable()) && |
| (NOT naTable->isUserUpdatableSeabaseMDTable()) && |
| (NOT Get_SqlParser_Flags(INTERNAL_QUERY_FROM_EXEUTIL))) |
| { |
| // IUD on hbase metadata is only allowed for internal queries. |
| *CmpCommon::diags() << DgSqlCode(-1391) |
| << DgString0(naTable->getTableName().getQualifiedNameAsAnsiString()) |
| << DgString1("metadata"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| else if ((naTable->isSeabaseTable()) && |
| (naTable->getTableName().getSchemaName() == SEABASE_REPOS_SCHEMA) && |
| (NOT Get_SqlParser_Flags(INTERNAL_QUERY_FROM_EXEUTIL))) |
| { |
| // IUD on hbase metadata is only allowed for internal queries. |
| *CmpCommon::diags() << DgSqlCode(-1391) |
| << DgString0(naTable->getTableName().getQualifiedNameAsAnsiString()) |
| << DgString1("repository"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if ((naTable->isHbaseTable()) && |
| (naTable->isHbaseCellTable() || naTable->isHbaseRowTable()) && |
| (CmpCommon::getDefault(HBASE_NATIVE_IUD) == DF_OFF)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4223) |
| << DgString0("Insert/Update/Delete on native HBase tables or in CELL/ROW format is"); |
| |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| NABoolean insertFromValuesList = |
| (getOperatorType() == REL_UNARY_INSERT && |
| (child(0)->getOperatorType() == REL_TUPLE || // VALUES(1,'b') |
| child(0)->getOperatorType() == REL_TUPLE_LIST || // VALUES(1,'b'),(2,'Y') |
| child(0)->getOperatorType() == REL_UNION)) || // VALUES..(with subquery inside the list) |
| getOperatorType() == REL_LEAF_INSERT; // index type of inserts |
| |
| if((!insertFromValuesList) && (getOperatorType() == REL_UNARY_INSERT)) |
| bindWA->setInsertSelectStatement(TRUE); |
| |
| // an update/delete node is created as an update/delete with child |
| // of a scan node by parser. If this is the case, then no security |
| // checks are needed on child Scan node. |
| if ((getOperatorType() == REL_UNARY_UPDATE || |
| getOperatorType() == REL_UNARY_DELETE) && |
| (child(0) && (child(0)->getOperatorType() == REL_SCAN))) { |
| Scan * scanNode = (Scan *)(child(0)->castToRelExpr()); |
| scanNode->setNoSecurityCheck(TRUE); |
| } |
| |
| // Setting the begin index for TableViewUsageList to zero, instead |
| // of the bindWA->tableViewUsageList().entries(); Becasue |
| // bindWA->tableViewUsageList().entries() sets the index to the current |
| //entry in the list, which excludes previous statements executed in a CS. |
| CollIndex begSrcUsgIx = 0; |
| if (!insertFromValuesList) { |
| // |
| // Create a new table name scope for the source table (child node). |
| // Bind the source. |
| // Reset scope context/naming. |
| // |
| bindWA->getCurrentScope()->xtnmStack()->createXTNM(); |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| bindWA->getCurrentScope()->xtnmStack()->removeXTNM(); |
| |
| // QSTUFF |
| |
| // we currently don't support streams and embedded updates |
| // for "insert into select from" statements. |
| if (getOperatorType() == REL_UNARY_INSERT){ |
| |
| if (child(0)->getGroupAttr()->isStream()){ |
| *CmpCommon::diags() << DgSqlCode(-4170); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (child(0)->getGroupAttr()->isEmbeddedUpdateOrDelete() || |
| child(0)->getGroupAttr()->isEmbeddedInsert()){ |
| *CmpCommon::diags() << DgSqlCode(-4171) |
| << DgString0(getGroupAttr()->getOperationWithinGroup()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // binding a generic update within a generic update |
| // can only occur when binding an updatable view containing |
| // an embedded delete or embedded update. We don't continue |
| // binding the generic update and but return the bound scan node. |
| // the scan node may be either a base table scan or a RenameTable |
| // node in case we are updating a view |
| // Since an embedded generic update may have referred to the OLD |
| // and NEW table we set a binder flag causing the table name to |
| // be changed to the name of the underlying scan table in the |
| // RelRoot on top of the generic update. Since we |
| // know that the normalizer has checked before allowing an update |
| // on the view that not both, i.e.new and old column values have been |
| // referred this is a safe operation. |
| |
| if (returnScanNode){ |
| // this line is a hack to get through Update::bindNode on the return |
| setTableDesc(getScanNode()->getTableDesc()); |
| |
| bindWA->setInGenericUpdate(inGenericUpdate); |
| bindWA->setRenameToScanTable (TRUE); |
| NATable *nTable = bindWA->getNATable(getTableName()); |
| |
| // decr once for just getting it here |
| // and again to compensate for the reference taken out |
| // previously which becomes obsolete since we just return a scan node |
| nTable->decrReferenceCount(); |
| nTable->decrReferenceCount(); |
| |
| return getViewScanNode(); |
| } |
| // QSTUFF |
| } |
| else { |
| // else, Insert::bindNode does VALUES(...) in its Assign::bindNode loop |
| // in particular, it does VALUES(..,DEFAULT,..) |
| } |
| |
| #ifndef NDEBUG |
| GU_DEBUG_Display(bindWA, this, "incoming", NULL, TRUE); |
| #endif |
| |
| // QSTUFF |
| // in case of an insert operation we don't set it initially in order |
| // to prevent that an embedded update or delete may be accidentially |
| // removed from a source view. However we need it for binding the |
| // target because it may be a view and its embedded updates have to |
| // be removed. |
| |
| if (getOperatorType() == REL_UNARY_INSERT) |
| inGenericUpdate = bindWA->setInGenericUpdate(TRUE); |
| |
| CMPASSERT(NOT(updateCurrentOf() && |
| getGroupAttr()->isEmbeddedUpdateOrDelete())); |
| |
| // this is a patch to allow for embedded updates in view definitions |
| ParNameLocList * pLoc = NULL; |
| if (getGroupAttr()->isEmbeddedUpdate()) { |
| pLoc = bindWA->getNameLocListPtr(); |
| bindWA->setNameLocListPtr(NULL); |
| } |
| // QSTUFF |
| |
| // Allocate a TableDesc and attach it to the node. |
| // |
| // Note that for Update/Delete, which always have a Scan node attached |
| // (see below), we cannot reuse the Scan's TableDesc: |
| // GenMapTable.C doesn't find the proper ValueIds when processing an |
| // update/delete on a table with an index. |
| // So we must always create a new (target) TableDesc, always a base table. |
| // |
| // Note that bindWA->getCurrentScope()->setRETDesc() is implicitly called: |
| // 1) by createTableDesc, setting it to this new (target) base table; |
| // 2) by bindView (if called), resetting it to the view's RenameTable RETDesc |
| // atop the new (target) table. |
| // |
| const NATable *naTableTop = naTable; |
| NABoolean isView = naTable->getViewText() != NULL; |
| RelExpr *boundView = NULL; // ## delete when done with it? |
| Scan *scanNode = NULL; |
| |
| if (getOperatorType() == REL_UNARY_INSERT || |
| getOperatorType() == REL_LEAF_INSERT) { |
| if (isView) { // INSERT into a VIEW: |
| // |
| // Expand the view definition as if it were a Scan child of the Insert |
| // (like all children, must have its own table name scope). |
| // |
| bindWA->getCurrentScope()->xtnmStack()->createXTNM(); |
| boundView = bindWA->bindView(getTableName(), |
| naTable, |
| accessOptions(), |
| removeSelPredTree(), |
| getGroupAttr()); |
| #ifndef NDEBUG |
| GU_DEBUG_Display(bindWA, this, "bv1", boundView); |
| #endif |
| if (bindWA->errStatus()) return this; |
| scanNode = boundView->getScanNode(); |
| bindWA->getCurrentScope()->xtnmStack()->removeXTNM(); |
| } |
| } |
| else if (getOperatorType() == REL_UNARY_UPDATE || |
| getOperatorType() == REL_UNARY_DELETE) { |
| scanNode = getScanNode(); |
| } |
| |
| if (updateCurrentOf()) { |
| CMPASSERT(scanNode); |
| scanNode->bindUpdateCurrentOf(bindWA, |
| (getOperatorType() == REL_UNARY_UPDATE)); |
| if (bindWA->errStatus()) return this; |
| } |
| |
| // As previous comments indicated, we're creating a TableDesc for the target, |
| // the underlying base table. Here we go and do it: |
| |
| NABoolean isScanOnDifferentTable = FALSE; |
| if (isView) { |
| // This binding of the view sets up the target RETDesc. |
| // This is the first bindView for UPDATE and DELETE on a view, |
| // and the second for INSERT into a view (yes, we *do* need to do it again). |
| boundView = bindWA->bindView(getTableName(), |
| naTable, |
| accessOptions(), |
| removeSelPredTree(), |
| getGroupAttr(), |
| TRUE); // QSTUFF |
| setTableDesc(boundView->getScanNode()->getTableDesc()); |
| if ((getOperatorType() == REL_INSERT)|| |
| (getOperatorType() == REL_UNARY_INSERT) || |
| (getOperatorType() == REL_LEAF_INSERT)) |
| { |
| ((Insert *)this)->setBoundView(boundView); |
| } |
| // for triggers |
| if (scanNode) |
| { |
| const NATable *naTableLocal = scanNode->getTableDesc()->getNATable(); |
| if ((naTableLocal != naTable) && (naTableLocal->getSpecialType() == ExtendedQualName::TRIGTEMP_TABLE)) |
| isScanOnDifferentTable = TRUE; |
| } |
| } else if (NOT (getUpdateCKorUniqueIndexKey() && (getOperatorType() == REL_UNARY_INSERT))) { |
| // an insert that is introduced to implement a phase of update primary key already |
| // has the right tabledesc (obtained from the update that it is replacing), so |
| // do not create another tablesdesc for such an insert. |
| if (scanNode) |
| naTable = scanNode->getTableDesc()->getNATable(); |
| CorrName tempName(naTableTop->getTableName(), |
| bindWA->wHeap(), |
| "", |
| getTableName().getLocationName(), |
| getTableName().getPrototype()); |
| |
| tempName.setUgivenName(getTableName().getUgivenName()); |
| |
| tempName.setSpecialType(getTableName().getSpecialType()); |
| // tempName.setIsVolatile(getTableName().isVolatile()); |
| TableDesc * naTableToptableDesc = bindWA->createTableDesc( |
| naTableTop, |
| tempName); |
| |
| if(naTableToptableDesc) |
| { |
| naTableToptableDesc->setSelectivityHint(NULL); |
| naTableToptableDesc->setCardinalityHint(NULL); |
| } |
| |
| setTableDesc(naTableToptableDesc); |
| |
| // Now naTable has the Scan's table, and naTableTop has the GU's table. |
| isScanOnDifferentTable = (naTable != naTableTop); |
| } |
| |
| if (bindWA->errStatus()) |
| return this; |
| |
| |
| // QSTUFF |
| // in case of a delete or update we may have to bind set clauses. |
| // first we bind the left target column, second we bind the right hand side |
| // we also have to separate the set on rollback clauses in a separate |
| // list. The set clauses generate a newRecExpr list, the set on rollback |
| // clause generate a newRecBeforeExpr list. |
| |
| // we add the old to new valueid map as it allows us to generate |
| // a subset operator in the presence of order by. the compiler |
| // needs to understand that the old and new valueids are identical |
| |
| // inlined trigger may update and scan different tables |
| if ((getOperatorType() == REL_UNARY_DELETE) && |
| (!isScanOnDifferentTable && !getUpdateCKorUniqueIndexKey())) { |
| const ValueIdList &dkeys = |
| getTableDesc()->getClusteringIndex()->getClusteringKeyCols(); |
| const ValueIdList &skeys = |
| scanNode->getTableDesc()->getClusteringIndex()->getClusteringKeyCols(); |
| CollIndex j = skeys.entries(); |
| for (CollIndex i = 0; i < j; i++) { |
| oldToNewMap().addMapEntry(skeys[i].getItemExpr()->getValueId(), |
| dkeys[i].getItemExpr()->getValueId()); |
| } |
| } |
| |
| ItemExpr *recExpr = removeNewRecExprTree(); |
| |
| if (recExpr && |
| (getOperatorType() == REL_UNARY_DELETE || |
| getOperatorType() == REL_UNARY_UPDATE)) { |
| |
| ItemExprList recList(recExpr, bindWA->wHeap()); |
| ItemExprList recBeforeList(bindWA->wHeap()); |
| SET(short) stoiColumnSet(bindWA->wHeap()); |
| |
| // in case a delete statement has a recEpxr, set on rollback |
| // clauses have been defined and need to be bound |
| |
| // as part of binding any set on rollback clause we have check |
| // that no contraints are defined for the specific clauses; otherwise |
| // the statement is rejected. |
| // the target columns are bound to the update table, the source |
| // columns are bound to the scan table |
| |
| if (getOperatorType() == REL_UNARY_DELETE){ |
| recBeforeList.insert(recList); |
| bindUpdateExpr(bindWA,recExpr,recBeforeList,boundView,scanNode,stoiColumnSet,TRUE); |
| if (bindWA->errStatus()) return this; |
| } |
| |
| // in case of an update operator we have to separate the set and |
| // set on rollback clauses |
| |
| if (getOperatorType() == REL_UNARY_UPDATE) { |
| CMPASSERT(recList.entries()); |
| |
| NABoolean leftIsList = FALSE; |
| NABoolean rightIsList = FALSE; |
| NABoolean legalSubqUdfExpr = FALSE; |
| |
| for (CollIndex i = 0;i < recList.entries(); i++){ |
| |
| CMPASSERT(recList[i]->getOperatorType() == ITM_ASSIGN); |
| |
| if (recList[i]->child(0)->getOperatorType() == ITM_ITEM_LIST) |
| leftIsList = TRUE; |
| if (recList[i]->child(1)->getOperatorType() == ITM_ITEM_LIST) |
| rightIsList = TRUE; |
| |
| if (((Assign *)recList[i])->onRollback()){ |
| |
| // On rollback clause currently not allowed with update lists. |
| if ((leftIsList) || (rightIsList)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3242) |
| << DgString0(" ON ROLLBACK not supported with SET lists."); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // CMPASSERT((NOT leftIsList) && (NOT rightIsList)) |
| |
| recBeforeList.insert(recList[i]); |
| recList.removeAt(i); |
| i--; |
| } |
| } |
| |
| |
| if ((leftIsList) && |
| (NOT rightIsList) && |
| (recList.entries() == 1) && |
| ((recList[0]->child(1)->getOperatorType() == ITM_ROW_SUBQUERY) || |
| (recList[0]->child(1)->getOperatorType() == ITM_USER_DEF_FUNCTION))) |
| { |
| ItemExpr * expr = NULL; |
| |
| // Both Subqueries and UDFs are now using the ValueIdProxy |
| // to carry the each of the valueIds representing the select list |
| // or UDF outputs. The transformation of the ValueIdProxy will do the |
| // right thing, and we don't need setSubqInUpdateAssing() anymore. |
| |
| // Bind the subquery |
| if (recList[0]->child(1)->getOperatorType() == ITM_ROW_SUBQUERY) |
| { |
| RowSubquery * rs = |
| (RowSubquery*)(recList[0]->child(1)->castToItemExpr()); |
| |
| // Not sure that we ever have a subquery without a REL_ROOT |
| // left this additional check from the old code. |
| if (rs->getSubquery()->getOperatorType() == REL_ROOT) |
| { |
| rs = (RowSubquery *) rs->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| legalSubqUdfExpr = TRUE; |
| expr = (ItemExpr *) rs; |
| } |
| |
| } |
| else |
| { |
| UDFunction * rudf = |
| (UDFunction*)(recList[0]->child(1)->castToItemExpr()); |
| |
| // Need to bind the UDFunction to get its outputs. |
| rudf = (UDFunction *) rudf->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| legalSubqUdfExpr = TRUE; |
| expr = (ItemExpr *) rudf; |
| |
| } |
| |
| // Update the recList with the bound itemExpr |
| recList[0]->child(1) = expr; |
| |
| |
| // Use the ItemExprList to flatten the Subquery or UDF |
| ItemExprList *exprList = (ItemExprList *) new(bindWA->wHeap()) |
| ItemExprList(expr,bindWA->wHeap()); |
| |
| // Convert the ItemExprList to a Tree |
| ItemExpr * ie = exprList->convertToItemExpr(); |
| ie = ie->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| Assign * assignNode = (Assign *)recList[0]; |
| assignNode->child(1) = ie; |
| |
| rightIsList = TRUE; |
| } |
| |
| if ((leftIsList) || (rightIsList)) // some elements as lists |
| { |
| ItemExprList newRecList(bindWA->wHeap()); |
| for (CollIndex i = 0; i < recList.entries(); i++) |
| { |
| Assign * assignNode = (Assign *)recList[i]; |
| |
| // Need to bind any UDFs or Subqieries in the expression |
| // so that we know the degree before we expand the list. |
| assignNode->child(0) = |
| assignNode->child(0)->bindUDFsOrSubqueries(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Need to bind any UDFs or Subqieries in the expression |
| // so that we know the degree before we expand the list. |
| assignNode->child(1) = |
| assignNode->child(1)->bindUDFsOrSubqueries(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| ItemExprList leftList(assignNode->child(0), bindWA->wHeap()); |
| ItemExprList rightList(assignNode->child(1), bindWA->wHeap()); |
| Lng32 numLeftElements = (Lng32) leftList.entries(); |
| Lng32 numRightElements = (Lng32) rightList.entries(); |
| |
| // See if ALLOW_SUBQ_IN_SET is enabled. It is enabled if |
| // the default is ON, or if the default is SYSTEM and |
| // ALLOW_UDF is ON. |
| NABoolean allowSubqInSet_Enabled = FALSE; |
| DefaultToken allowSubqTok = |
| CmpCommon::getDefault(ALLOW_SUBQ_IN_SET); |
| if ((allowSubqTok == DF_ON) || |
| (allowSubqTok == DF_SYSTEM)) |
| allowSubqInSet_Enabled = TRUE; |
| |
| if (!allowSubqInSet_Enabled) |
| { |
| for (CollIndex j = 0; j < rightList.entries(); j++) |
| { |
| if (((numLeftElements > 1) || |
| (numRightElements > 1)) && |
| (((rightList[j]->getOperatorType() == ITM_ROW_SUBQUERY) || |
| (rightList[j]->getOperatorType() == ITM_VALUEID_PROXY)) && |
| (legalSubqUdfExpr == FALSE))) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3242) |
| << DgString0(" Multiple elements or multiple subqueries are not allowed in this SET clause."); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } |
| |
| if (numLeftElements != numRightElements) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4023) |
| << DgInt0(numRightElements) |
| << DgInt1(numLeftElements); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // create newRecList with one Assign node for each element. |
| for (CollIndex k = 0; k < leftList.entries(); k++) |
| { |
| ItemExpr * leftIE = leftList[k]; |
| ItemExpr * rightIE = rightList[k]; |
| Assign *assign = new (bindWA->wHeap()) |
| Assign(leftIE, rightIE); |
| // We do not bind the above Assign as it will be done |
| // in bindUpdateExpr below. (bug #1893) |
| newRecList.insert(assign); |
| } |
| |
| } // for |
| |
| bindUpdateExpr(bindWA,recExpr,newRecList,boundView,scanNode,stoiColumnSet); |
| if (bindWA->errStatus()) |
| return this; |
| } // some elements as lists |
| else |
| { // no elements as lists |
| if (recList.entries()){ |
| bindUpdateExpr(bindWA,recExpr,recList,boundView,scanNode,stoiColumnSet); |
| if (bindWA->errStatus()) return this; |
| } |
| } |
| |
| if (recBeforeList.entries()){ |
| bindUpdateExpr(bindWA,recExpr,recBeforeList,boundView,scanNode,stoiColumnSet,TRUE); |
| if (bindWA->errStatus()) return this; |
| } |
| |
| } // UNARY_UPDATE |
| |
| // now we record the columns updated for the SqlTableOpenInfo |
| if (listedStoi) { |
| listedStoi->getStoi()->setColumnListCount((short)stoiColumnSet.entries()); |
| |
| short *stoiColumnList = new (bindWA->wHeap()) |
| short[stoiColumnSet.entries()]; |
| |
| for (CollIndex i = 0; i < stoiColumnSet.entries(); i++) |
| { |
| stoiColumnList[i] = stoiColumnSet[i]; |
| listedStoi->addUpdateColumn(stoiColumnSet[i]); |
| } |
| |
| listedStoi->getStoi()->setColumnList(stoiColumnList); |
| } |
| |
| // the previous implementation assumed that the scope points |
| // to the scan table; we don't want to disturb the code and |
| // make that happen -- |
| |
| #ifndef NDEBUG |
| GU_DEBUG_Display(bindWA, this, "u"); |
| #endif |
| |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| } |
| // QSTUFFF |
| |
| CollIndex endSrcUsgIx = bindWA->tableViewUsageList().entries(); |
| |
| if ((!isScanOnDifferentTable) && |
| (((getOperatorType() == REL_UNARY_INSERT) && |
| !insertFromValuesList && !getGroupAttr()->isEmbeddedInsert()) || |
| (getOperatorType() == REL_UNARY_UPDATE) || |
| (getOperatorType() == REL_UNARY_DELETE))){ |
| |
| // Special handling of statements that could suffer the |
| // Halloween problem, e.g., "insert into t select from t" |
| // or "insert into v select from t", if v references t |
| |
| DBG( if (getenv("TVUSG_DEBUG")) bindWA->tableViewUsageList().display(); ) |
| |
| const NATable *naTableBase = naTable; |
| const QualifiedName *viewName = NULL; |
| if (isView) { |
| // Currently, per Ansi rules, we can only insert through a view if |
| // there is a single underlying base table without joins or unions. |
| // Since we are binding the view twice for INSERTS, |
| // the variable beforeRefcount for the *single* base table has to be 2. |
| // |
| beforeRefcount = beforeRefcount + 1; |
| naTableBase = getTableDesc()->getNATable(); |
| viewName = &naTable->getTableName(); |
| } |
| if ((getOperatorType() == REL_UNARY_UPDATE || |
| getOperatorType() == REL_UNARY_DELETE) && |
| (child(0)->getOperatorType() == REL_SCAN)) { |
| // The table is referenced twice; once for the update/delete and |
| // the second time for the scan below it. |
| beforeRefcount = beforeRefcount + 1; |
| } |
| |
| const QualifiedName &tableBaseName = naTableBase->getTableName(); |
| Int32 afterRefcount = naTableBase->getReferenceCount(); |
| NABoolean isSGTableType = getTableName().getSpecialType() == ExtendedQualName::SG_TABLE; |
| |
| NAString viewFmtdList(bindWA->wHeap()); |
| Int32 baseSeenInSrc = 0; |
| |
| // The views on the table do not need to be obtained |
| // if the table type is a SEQUENCE GENERATOR |
| |
| if (!isSGTableType) |
| baseSeenInSrc = bindWA->tableViewUsageList().getViewsOnTable( |
| begSrcUsgIx, endSrcUsgIx, |
| bindWA->viewCount(), |
| tableBaseName, |
| getTableName().getSpecialType(), |
| viewName, |
| viewFmtdList); |
| |
| NABoolean halloween = FALSE; |
| if (CmpCommon::getDefault(R2_HALLOWEEN_SUPPORT) == DF_ON) { |
| |
| if (beforeRefcount != afterRefcount) { |
| |
| // Check to see if we can support this update. |
| // |
| if(checkForHalloweenR2(afterRefcount - beforeRefcount)) { |
| halloween = TRUE; |
| } |
| } |
| else { |
| Scan *scanSrc = getScanNode(FALSE/*no assert*/); |
| if ((baseSeenInSrc > beforeRefcount) && |
| ((scanSrc && scanSrc->getTableName().isLocationNameSpecified())|| |
| (getTableName().isLocationNameSpecified()))) { |
| halloween = TRUE; |
| } |
| if (Get_SqlParser_Flags(ALLOW_SPECIALTABLETYPE)) { |
| if ((scanSrc && scanSrc->getTableName().isLocationNameSpecified())|| |
| (getTableName().isLocationNameSpecified())){ |
| // Do not enforce Halloween check if it is a |
| // partition only operation. |
| // We assume the programmer knows what he's doing |
| // -- hopefully, by doing insert/update/delete |
| // operations as part of Partition Management |
| // (Move Partition Boundary or Split Partition or |
| // Merge Partition. See TEST057 and TEST058) |
| halloween = FALSE; |
| } |
| } |
| } |
| if (halloween) { |
| CMPASSERT(!(isView && viewFmtdList.isNull())); |
| *CmpCommon::diags() << DgSqlCode(viewFmtdList.isNull() ? -4026 : -4060) |
| << DgTableName( |
| tableBaseName.getQualifiedNameAsAnsiString()) |
| << DgString0(viewFmtdList); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| else { |
| // Support for self-referencing updates/Halloween problem. |
| if (beforeRefcount != afterRefcount) { |
| |
| setAvoidHalloween(TRUE); |
| |
| bindWA->getTopRoot()->setAvoidHalloween(TRUE); |
| |
| // Decide if access mode (default or specified) is compatible |
| // with the use of DP2 locks. If access mode was specified, |
| // it is a property of the naTableBase. |
| NABoolean cannotUseDP2Locks = |
| naTableBase->getRefsIncompatibleDP2Halloween(); |
| |
| // Now check the transaction isolation level, which can override |
| // the access mode. Note that il was initialized above for the |
| // check for an updatable trans, i.e., errors 3140 and 3141. |
| if((CmpCommon::transMode()->ILtoAT(il) == REPEATABLE_ ) || |
| (CmpCommon::transMode()->ILtoAT(il) == STABLE_ ) || |
| (CmpCommon::transMode()->ILtoAT(il) == BROWSE_ )) |
| cannotUseDP2Locks = TRUE; |
| |
| // Save the result with this GenericUpdate object. It will be |
| // used when the nextSubstitute methods of TSJFlowRule or TSJRule |
| // call GenericUpdate::configTSJforHalloween. |
| if (NOT getHalloweenCannotUseDP2Locks()) |
| setHalloweenCannotUseDP2Locks(cannotUseDP2Locks); |
| |
| // Keep track of which table in the query is the self-ref table. |
| // This is a part of the fix for solution 10-071204-9253. |
| ((NATable *)naTableBase)->setIsHalloweenTable(); |
| } |
| else { |
| Scan *scanSrc = getScanNode(FALSE/*no assert*/); |
| if ((baseSeenInSrc > beforeRefcount) && |
| ((scanSrc && scanSrc->getTableName().isLocationNameSpecified())|| |
| (getTableName().isLocationNameSpecified()))) { |
| halloween = TRUE; |
| } |
| if (Get_SqlParser_Flags(ALLOW_SPECIALTABLETYPE)) { |
| if ((scanSrc && scanSrc->getTableName().isLocationNameSpecified())|| |
| (getTableName().isLocationNameSpecified())){ |
| // Do not enforce Halloween check if it is a |
| // partition only operation. |
| // We assume the programmer knows what he's doing |
| // -- hopefully, by doing insert/update/delete |
| // operations as part of Partition Management |
| // (Move Partition Boundary or Split Partition or |
| // Merge Partition. See TEST057 and TEST058) |
| halloween = FALSE; |
| } |
| } |
| if (halloween) { |
| CMPASSERT(!(isView && viewFmtdList.isNull())); |
| *CmpCommon::diags() << DgSqlCode(viewFmtdList.isNull() ? -4026 : -4060) |
| << DgTableName( |
| tableBaseName.getQualifiedNameAsAnsiString()) |
| << DgString0(viewFmtdList); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } |
| } |
| |
| // Bind the base class. |
| // Allocate an empty RETDesc and attach it to this node, *but* leave the |
| // currently scoped RETDesc (that of naTableTop) as is, for further binding |
| // in caller Insert::bindNode or LeafInsert/LeafDelete::bindNode. |
| // |
| RelExpr *boundExpr = bindSelf(bindWA); |
| CMPASSERT(boundExpr == this); // assumed by RETDesc/RI/IM code below |
| if (bindWA->errStatus()) return boundExpr; |
| setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA)); |
| |
| // Copy the check constraints to the private memory of the GenericUpdate. |
| // |
| checkConstraints() = getTableDesc()->getCheckConstraints(); |
| |
| // Create a key expression for the table to be updated. |
| // The code specific to the Insert node is handled in Insert::bindNode. |
| // |
| if (getOperatorType() == REL_UNARY_UPDATE || |
| getOperatorType() == REL_UNARY_DELETE) { |
| |
| if (getTableDesc()->getNATable()->isHiveTable()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4223) |
| << DgString0("Update/Delete on Hive table is"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // SQL syntax requires (and the parser ensures) that a direct descendant |
| // (passing thru views) of an update/delete node is a scan node on the |
| // same table that is being updated (note that normalizer transformations |
| // may change this at a later time). |
| // An exception to this rule happens when before triggers are inlined. |
| // In this case, the update/delete on the subject table is driven by |
| // a Scan on a temp table. The primary key columns of the subject table are |
| // a subset of the primary key columns of the temp table, and using the |
| // same column names, but not neccessarily in the same order. |
| // |
| // Update/Delete nodes require expressions in their newRecExpr that can |
| // be used to form the primary key of the table to update/delete. |
| // |
| const NAColumnArray &keyColArray = |
| getTableDesc()->getNATable()->getClusteringIndex()->getIndexKeyColumns(); |
| CollIndex numKeyCols = keyColArray.entries(); |
| const NAColumnArray &scanColArray = |
| scanNode->getTableDesc()->getNATable()->getNAColumnArray(); |
| |
| for (CollIndex i = 0; i < numKeyCols; i++) { |
| // The scan node and the update/delete node both use the SAME table, |
| // so their column names are also the same. |
| // |
| Lng32 colPos = keyColArray[i]->getPosition(); |
| ItemExpr *guCol = getTableDesc()->getColumnList()[colPos].getItemExpr(); |
| ItemExpr *scanCol; // - Triggers |
| if (!isScanOnDifferentTable) |
| scanCol = scanNode->getTableDesc()->getColumnList()[colPos].getItemExpr(); |
| else |
| { |
| // Make sure this is a BaseColumn. |
| CMPASSERT(guCol->getOperatorType() == ITM_BASECOLUMN); |
| // Find the column name. |
| const NAString& colName = ((BaseColumn *)guCol)->getColName(); |
| // Find a column with the same name, in the table from the Scan node. |
| // SYSKEY is an exception since its name in the temp table is "@SYSKEY" |
| ExtendedQualName::SpecialTableType tableType = |
| scanNode->getTableDesc()->getCorrNameObj().getSpecialType(); |
| NAColumn *scanNaCol = NULL; |
| if (ExtendedQualName::TRIGTEMP_TABLE == tableType && colName == "SYSKEY") |
| { |
| scanNaCol = scanColArray.getColumn("@SYSKEY"); |
| } |
| else |
| { |
| scanNaCol = scanColArray.getColumn(colName); |
| } |
| CMPASSERT(scanNaCol != NULL) |
| // Get the position of this column in the Scan table. |
| Lng32 scanColPos = scanNaCol->getPosition(); |
| // Get the Scan BaseColumn. |
| scanCol = scanNode->getTableDesc()->getColumnList()[scanColPos].getItemExpr(); |
| } |
| ItemExpr *newKeyPred = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, guCol, scanCol); |
| newKeyPred->bindNode(bindWA); |
| beginKeyPred().insert(newKeyPred->getValueId()); |
| |
| updateToSelectMap().addMapEntry( |
| newKeyPred->child(0)->getValueId(), |
| newKeyPred->child(1)->getValueId()); |
| |
| } // loop over key columns |
| |
| // All of the indexes also require expressions that can be used to |
| // form the primary key of the index to update/delete. Create these |
| // item expressions here. |
| // (From here to the end of the loop over indexes structurally resembles |
| // GenericUpdate::imBindAllIndexes(), but has significant differences.) |
| // |
| // Remember the value ID's of the scan node index columns for |
| // code generation time. |
| // |
| |
| if ((this->getOperatorType() == REL_UNARY_UPDATE) && isScanOnDifferentTable) |
| { |
| setScanIndexDesc(NULL); // for triggers |
| } |
| else |
| { |
| setScanIndexDesc(scanNode->getTableDesc()->getClusteringIndex()); |
| } |
| } // REL_UNARY_UPDATE or REL_UNARY_DELETE |
| |
| |
| // QSTUFF |
| // we need to check whether this code is executed as part of a create view |
| // ddl operation using bindWA->inDDL() and prevent indices, contraints and |
| // triggers to be added as the catalog manager binding functions cannot |
| // handle it right now |
| // QSTUFF |
| |
| // QSTUFF hack ! |
| if (getGroupAttr()->isEmbeddedUpdate()) |
| bindWA->setNameLocListPtr(pLoc); |
| bindWA->setInGenericUpdate(inGenericUpdate); |
| // QSTUFF |
| |
| // set flag that we are binding an Insert/Update/Delete operation |
| // Used to disable Join optimization when necessary |
| bindWA->setBindingIUD(); |
| |
| return boundExpr; |
| } // GenericUpdate::bindNode() |
| |
| NABoolean GenericUpdate::checkForMergeRestrictions(BindWA *bindWA) |
| { |
| if (!isMerge()) |
| return FALSE; |
| |
| |
| ValueIdList tempVIDlist; |
| getTableDesc()->getIdentityColumn(tempVIDlist); |
| NAColumn *identityCol = NULL; |
| if (tempVIDlist.entries() > 0) |
| { |
| ValueId valId = tempVIDlist[0]; |
| identityCol = valId.getNAColumn(); |
| } |
| |
| // MERGE on a table with BLOB columns is not supported |
| if (getTableDesc()->getNATable()->hasLobColumn()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" LOB column not allowed."); |
| bindWA->setErrStatus(); |
| return TRUE; |
| |
| } |
| |
| if (getTableDesc()->hasUniqueIndexes() && |
| (CmpCommon::getDefault(MERGE_WITH_UNIQUE_INDEX) == DF_OFF)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" unique indexes not allowed."); |
| bindWA->setErrStatus(); |
| return TRUE; |
| } |
| |
| if ((accessOptions().accessType() == SKIP_CONFLICT_) || |
| (getGroupAttr()->isStream()) || |
| (newRecBeforeExprArray().entries() > 0)) // set on rollback |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" Stream, skip conflict or SET ON ROLLBACK not allowed."); |
| bindWA->setErrStatus(); |
| return TRUE; |
| } |
| |
| if (getGroupAttr()->isEmbeddedUpdateOrDelete()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" Embedded update/deletes not allowed."); |
| bindWA->setErrStatus(); |
| return TRUE; |
| } |
| |
| if ((getInliningInfo().hasInlinedActions()) || |
| (getInliningInfo().isEffectiveGU())) |
| { |
| if (getInliningInfo().hasTriggers()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3241) |
| << DgString0(" Triggers not allowed."); |
| bindWA->setErrStatus(); |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| // This class LeafInsert and its companion LeafDelete |
| // are currently used only by Index Maintenance, |
| // but we ought not make any assumptions. |
| // ##IM: It might be useful to add a flag such as GenericUpdate::isIndexTable_ |
| // ##IM: and set it to TRUE in createIMNode(). |
| // |
| RelExpr *LeafInsert::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| #ifndef NDEBUG |
| if (GU_DEBUG) cerr << "\nLeafInsert " << getUpdTableNameText() << endl; |
| #endif |
| |
| setInUpdateOrInsert(bindWA, this, REL_INSERT); |
| |
| if (getPreconditionTree()) { |
| ValueIdSet pc; |
| |
| getPreconditionTree()->convertToValueIdSet(pc, bindWA, ITM_AND); |
| if (bindWA->errStatus()) |
| return this; |
| |
| setPreconditionTree(NULL); |
| setPrecondition(pc); |
| } |
| |
| RelExpr *boundExpr = GenericUpdate::bindNode(bindWA); |
| if (bindWA->errStatus()) return boundExpr; |
| |
| // Make newRecExprArray_ be an ordered set of assign nodes of the form |
| // "ixcol1 = basetblcol1, ixcol2 = basecol2, ..." (for Index Maintenance) |
| |
| // Note: For SQL/MP tables, ixcol0 is the keytag, and will need to be |
| // handled differently from other columns. |
| |
| |
| const ValueIdList &tgtcols = getTableDesc()->getColumnList(); |
| |
| CMPASSERT(tgtcols.entries() == baseColRefs().entries()); |
| for (CollIndex i = 0; i < tgtcols.entries(); i++) { |
| Assign *assign; |
| assign = new (bindWA->wHeap()) |
| Assign(tgtcols[i].getItemExpr(), baseColRefs()[i], FALSE); |
| assign->bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| newRecExprArray().insertAt(i, assign->getValueId()); |
| newRecExpr().insert(assign->getValueId()); |
| updateToSelectMap().addMapEntry(assign->getTarget(), assign->getSource()); |
| } |
| |
| if (getReferencedMergeIUDIndicator() != NULL_VALUE_ID) |
| bindWA->getCurrentScope()->addOuterRef(getReferencedMergeIUDIndicator()); |
| // RelExpr::bindSelf (in GenericUpdate::bindNode) has done this line, but now |
| // any outer refs discovered in bindNode's in the above loop must be added. |
| // For Index Maintenance, these must be exactly the set of baseColRefs vids |
| // (all the target index cols are from the locally-scoped RETDesc left by |
| // the GenericUpdate::bindNode), plus the merge IUD indicator, if used. |
| getGroupAttr()->addCharacteristicInputs(bindWA->getCurrentScope()->getOuterRefs()); |
| |
| // The NATable of getTableName() had been set to INDEX_TABLE so that |
| // getNATable would search the right namespace. |
| // Now we make the Optimizer treat this as a regular table, not an index |
| // (in particular, don't have it choose VSBB sidetree-insert). |
| // |
| // The TableDesc setting may be redundant/unnecessary, but we do it |
| // for completeness and safety. |
| // |
| // -- Triggers |
| // If it is NOT an index table (like maybe a TRIGTEMP_TABLE), leave it alone |
| if (getTableName().getSpecialType() == ExtendedQualName::INDEX_TABLE) |
| { |
| getTableName().setSpecialType(ExtendedQualName::NORMAL_TABLE); |
| getTableDesc()->getCorrNameObj().setSpecialType(ExtendedQualName::NORMAL_TABLE); |
| } |
| |
| setInUpdateOrInsert(bindWA); |
| return boundExpr; |
| |
| } // LeafInsert::bindNode() |
| |
| RelExpr *LeafDelete::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| #ifndef NDEBUG |
| if (GU_DEBUG) cerr << "\nLeafDelete " << getUpdTableNameText() << endl; |
| #endif |
| |
| if (getPreconditionTree()) { |
| ValueIdSet pc; |
| |
| getPreconditionTree()->convertToValueIdSet(pc, bindWA, ITM_AND); |
| if (bindWA->errStatus()) |
| return this; |
| |
| setPreconditionTree(NULL); |
| setPrecondition(pc); |
| } |
| |
| RelExpr *boundExpr = GenericUpdate::bindNode(bindWA); |
| if (bindWA->errStatus()) return boundExpr; |
| |
| //Set the beginKeyPred |
| if (TriggersTempTable *tempTableObj = getTrigTemp()) |
| { |
| |
| const ValueIdList &keycols = getTableDesc()->getClusteringIndex()->getIndexKey(); |
| ItemExpr *keyExpr; |
| |
| // Normal case - use the UniqueExecuteId builtin function. |
| keyExpr = new(bindWA->wHeap()) UniqueExecuteId(); |
| |
| ItemExpr *tempKeyPred = new(bindWA->wHeap()) BiRelat(ITM_EQUAL, keycols[0].getItemExpr(), keyExpr); |
| tempKeyPred->bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| beginKeyPred().insert(tempKeyPred->getValueId()); |
| // Create the ItemExpr for the constant UniqueIudNum |
| ItemExpr *col2 = new(bindWA->wHeap()) |
| ColReference(new(bindWA->wHeap()) ColRefName(UNIQUEIUD_COLUMN)); |
| |
| // Compare it to the correct offset. |
| BindWA::uniqueIudNumOffset offset = BindWA::uniqueIudNumForInsert ; |
| |
| ItemExpr *iudConst = new(bindWA->wHeap()) ConstValue(bindWA->getUniqueIudNum(offset)); |
| ItemExpr *predIudId = new(bindWA->wHeap()) BiRelat(ITM_EQUAL, keycols[1].getItemExpr(), iudConst); |
| predIudId->bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| beginKeyPred().insert(predIudId->getValueId()); |
| |
| |
| for (CollIndex i = 2; i<keycols.entries(); i++) |
| { |
| ItemExpr *keyPred = NULL; |
| ItemExpr *keyItemExpr = keycols[i].getItemExpr(); |
| ItemExpr *baseItemExpr = NULL; |
| Lng32 keyColPos = keycols[i].getNAColumn()->getPosition(); |
| baseItemExpr = baseColRefs()[keyColPos]; |
| keyPred = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, keyItemExpr, baseItemExpr); |
| |
| keyPred->bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| beginKeyPred().insert(keyPred->getValueId()); |
| } |
| } |
| |
| else |
| { |
| |
| |
| const ValueIdList &keycols = getTableDesc()->getClusteringIndex()->getIndexKey(); |
| |
| for (CollIndex i = 0; i < keycols.entries() ; i++) |
| { |
| ItemExpr *keyPred = 0; |
| ItemExpr *keyItemExpr = keycols[i].getItemExpr(); |
| Lng32 keyColPos = keycols[i].getNAColumn()->getPosition(); |
| |
| ItemExpr *baseItemExpr = NULL; |
| // For a unique index (for undo) we are passing in all the index |
| // columns in baseColRefs. So we need to find the index key col |
| // position in the index col list and compare the key columns with |
| // it's corresponding column in the index column list |
| if (isUndoUniqueIndex()) |
| baseItemExpr = baseColRefs()[keyColPos]; |
| else |
| baseItemExpr = baseColRefs()[i]; |
| |
| keyPred = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, keyItemExpr, baseItemExpr); |
| |
| keyPred->bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| beginKeyPred().insert(keyPred->getValueId()); |
| } |
| } |
| |
| |
| if (isUndoUniqueIndex()) |
| { |
| setUpExecPredForUndoUniqueIndex(bindWA) ; |
| } |
| |
| if (getTrigTemp()) |
| { |
| setUpExecPredForUndoTempTable(bindWA); |
| } |
| |
| // See LeafInsert::bindNode for comments on remainder of this method. |
| |
| if (getReferencedMergeIUDIndicator() != NULL_VALUE_ID) |
| bindWA->getCurrentScope()->addOuterRef(getReferencedMergeIUDIndicator()); |
| getGroupAttr()->addCharacteristicInputs(bindWA->getCurrentScope()->getOuterRefs()); |
| |
| getTableName().setSpecialType(ExtendedQualName::NORMAL_TABLE); |
| getTableDesc()->getCorrNameObj().setSpecialType(ExtendedQualName::NORMAL_TABLE); |
| |
| return boundExpr; |
| |
| } // LeafDelete::bindNode() |
| |
| void LeafDelete::setUpExecPredForUndoUniqueIndex(BindWA *bindWA) |
| { |
| // Set up the executor predicate . Used in the case of Undo to undo the |
| // exact row that caused an error.Note that if we used only the key |
| // columns to undo, we may end up undoing existing rows . |
| // This is done only for unique indexes |
| ItemExpr *execPred = NULL; |
| |
| |
| const ValueIdList &indexCols = getTableDesc()->getClusteringIndex()->getIndexColumns(); |
| for ( CollIndex i = 0; i < indexCols.entries(); i++) |
| { |
| execPred = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, indexCols[i].getItemExpr(), baseColRefs()[i]); |
| execPred->bindNode(bindWA); |
| if (bindWA->errStatus()) return ; |
| executorPred() += execPred->getValueId(); |
| } |
| return; |
| } |
| void LeafDelete::setUpExecPredForUndoTempTable(BindWA *bindWA) |
| { |
| |
| ItemExpr *execPred = NULL; |
| |
| const ValueIdList &tempCols = getTableDesc()->getClusteringIndex()->getIndexColumns(); |
| for ( CollIndex i = 0; i < tempCols.entries(); i++) |
| { |
| NAString colName(tempCols[i].getNAColumn()->getColName()); |
| if (colName.data()[0] == '@' && colName.compareTo("@SYSKEY")) |
| continue; |
| execPred = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, tempCols[i].getItemExpr(), baseColRefs()[i]); |
| execPred->bindNode(bindWA); |
| if (bindWA->errStatus()) return; |
| executorPred() += execPred->getValueId(); |
| } |
| |
| return; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // RelRoutine |
| // ----------------------------------------------------------------------- |
| // LCOV_EXCL_START - rfi |
| RelExpr *RelRoutine::bindNode(BindWA *bindWA) |
| { |
| CMPASSERT(0); // For the time being, all classes above implement their own. |
| |
| // |
| // Allocate an RETDesc and attach it to this and the BindScope. |
| // Needs to occur in later classes when we know if we are at table |
| // type or not.. |
| // XXX setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA, getTableDesc())); |
| // bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| // |
| // Bind the base class. |
| // |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) return boundExpr; |
| // |
| // Assign the set of columns that belong to the virtual table |
| // as the output values that can be produced by this node. |
| // |
| // XXX done in later clasees |
| // getGroupAttr()->addCharacteristicOutputs(getTableDesc()->getColumnList()); |
| return boundExpr; |
| } // RelRoutine::bindNode() |
| // LCOV_EXCL_STOP |
| |
| // ----------------------------------------------------------------------- |
| // BuiltinTableValuedFunction |
| // will be called by |
| // ExplainFunc and StatisticsFunc |
| // Rely on function implementation in TableValuedFunction |
| // ----------------------------------------------------------------------- |
| |
| // ----------------------------------------------------------------------- |
| // Explain/Statistics/HiveMD Func |
| // ----------------------------------------------------------------------- |
| RelExpr *BuiltinTableValuedFunction::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // |
| // Bind the child nodes. |
| // |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // |
| // Check if there is already an NATable for the Explain/Statistics table. |
| // |
| if (getOperatorType() == REL_EXPLAIN || |
| getOperatorType() == REL_STATISTICS || |
| getOperatorType() == REL_HIVEMD_ACCESS || |
| getOperatorType() == REL_HBASE_ACCESS) |
| { |
| NATable *naTable = NULL; |
| |
| if (getOperatorType() == REL_HBASE_ACCESS) |
| { |
| // should not reach here |
| CMPASSERT(0); |
| } |
| else |
| { |
| CorrName corrName(getVirtualTableName()); |
| corrName.setSpecialType(ExtendedQualName::VIRTUAL_TABLE); |
| NATable *naTable = bindWA->getSchemaDB()->getNATableDB()-> |
| get(&corrName.getExtendedQualNameObj()); |
| |
| if (NOT naTable) |
| { |
| TrafDesc *tableDesc = createVirtualTableDesc(); |
| if (tableDesc) |
| naTable = bindWA->getNATable(corrName, FALSE/*catmanUsages*/, tableDesc); |
| |
| if ( ! tableDesc || bindWA->errStatus() ) |
| return this; |
| } |
| |
| // Allocate a TableDesc and attach it to this. |
| // |
| TableDesc * td = bindWA->createTableDesc(naTable, corrName); |
| if (! td || bindWA->errStatus()) |
| return this; |
| |
| setTableDesc(td); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| |
| if (getProcAllParamsTree()) |
| { |
| ((ItemExpr *)getProcAllParamsTree())->convertToValueIdList(getProcAllParamsVids(), bindWA, ITM_ITEM_LIST); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Clear the Tree since we now have gotten vids for all the parameters. |
| setProcAllParamsTree(NULL); |
| |
| Lng32 sqlcode = 0; |
| if (getProcAllParamsVids().entries() != numParams()) |
| { |
| sqlcode = -4067; |
| |
| // 4067 Explain/Statistics requires two operands, of type character. |
| *CmpCommon::diags() << DgSqlCode(sqlcode) << DgString0(getTextForError()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // type any param arguments to fixed char since runtime explain |
| // expects arguments to be fixed char. |
| Lng32 len = (Lng32)CmpCommon::getDefaultNumeric(VARCHAR_PARAM_DEFAULT_SIZE); |
| SQLChar c(len); |
| |
| for (Lng32 i = 0; i < numParams(); i++) |
| { |
| getProcAllParamsVids()[i].coerceType(c, NA_CHARACTER_TYPE); |
| if (getProcAllParamsVids()[i].getType().getTypeQualifier() != NA_CHARACTER_TYPE) |
| { |
| sqlcode = -4067; |
| |
| // 4067 Explain/Statistics requires two operands, of type character. |
| *CmpCommon::diags() << DgSqlCode(sqlcode) << DgString0(getTextForError()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| const NAType &typ = getProcAllParamsVids()[i].getType(); |
| |
| CharInfo::CharSet chld_cs = ((const CharType&)typ).getCharSet(); |
| ItemExpr *ie; |
| if ( chld_cs == CharInfo::UNICODE ) |
| { |
| ie = new (bindWA->wHeap()) Translate( |
| getProcAllParamsVids()[i].getItemExpr(), |
| Translate::UNICODE_TO_ISO88591); |
| ie = ie->bindNode(bindWA); |
| getProcAllParamsVids()[i] = ie->getValueId(); |
| } |
| |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| // For Explain and Statistics all parameters are inputs |
| getProcInputParamsVids().insert(getProcAllParamsVids()); |
| } // for |
| } |
| } // if |
| |
| return TableValuedFunction::bindNode(bindWA); |
| |
| } |
| |
| // ----------------------------------------------------------------------- |
| // TableValuedFunction |
| // ----------------------------------------------------------------------- |
| RelExpr *TableValuedFunction::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // |
| // Bind the child nodes. |
| // |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| |
| // |
| // Allocate an RETDesc and attach it to this and the BindScope. |
| // |
| setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA, getTableDesc())); |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| // |
| // Bind the base class. |
| // |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) return boundExpr; |
| // |
| // Assign the set of columns that belong to the virtual table |
| // as the output values that can be produced by this node. |
| // |
| getGroupAttr()->addCharacteristicOutputs(getTableDesc()->getColumnList()); |
| return boundExpr; |
| } // TableValuedFunction::bindNode() |
| |
| |
| // ----------------------------------------------------------------------- |
| // Member functions for classes Control* |
| // must be written allowing for a NULL BindWA to be passed in! |
| // |
| // This happens when called from the SQLC/SQLCO Preprocessor, |
| // which needs to bind certain "static-only" statements -- |
| // those which evaluate to STATIC_ONLY_WITH_WORK_FOR_PREPROCESSOR -- |
| // see ControlAbstractClass::isAStaticOnlyStatement(). |
| // ----------------------------------------------------------------------- |
| |
| RelExpr * ControlAbstractClass::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) return this; |
| |
| // Early return if called by SQLC/SQLCO Preprocessor |
| if (!bindWA) return this; |
| |
| // Allocate an empty RETDesc and attach it to this node and the BindScope. |
| setRETDesc(new(bindWA->wHeap()) RETDesc(bindWA)); |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| |
| return bindSelf(bindWA); |
| } // ControlAbstractClass::bindNode() |
| |
| |
| RelExpr * ControlQueryShape::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // remember the required shape in the control table |
| if (alterArkcmpEnvNow()) |
| { |
| if (getShape()) |
| ActiveControlDB()->setRequiredShape(this); |
| else |
| { |
| // no shape passed in. Hold or Restore. |
| if (holdShape()) |
| ActiveControlDB()->saveCurrentCQS(); |
| else |
| ActiveControlDB()->restoreCurrentCQS(); |
| if (ActiveControlDB()->getRequiredShape()) |
| ActiveControlDB()->getRequiredShape()->holdShape() = holdShape(); |
| } |
| } |
| |
| return ControlAbstractClass::bindNode(bindWA); |
| } // ControlQueryShape::bindNode() |
| |
| |
| RelExpr * ControlQueryDefault::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // Alter the current Defaults settings if this is a static CQD. |
| // |
| // "AffectYourself" is coming to you courtesy of the Staple Singers: |
| // 'Affect yourself, na na na, na na na na, affect yourself, re re re re.' |
| // It's neat to find such Binder-relevant lyrics, eh? |
| // |
| NABoolean affectYourself = alterArkcmpEnvNow(); |
| |
| assert(!bindWA || bindWA->getSchemaDB() == ActiveSchemaDB()); |
| NADefaults &defs = ActiveSchemaDB()->getDefaults(); |
| defs.setState(NADefaults::SET_BY_CQD); |
| |
| if ( defs.isReadonlyAttribute(token_) == TRUE ) |
| { |
| Int32 attrNum = defs.lookupAttrName(token_); |
| |
| if (stricmp(value_, defs.getValue(attrNum)) != 0 ) |
| { |
| if (CmpCommon::getDefault(DISABLE_READ_ONLY) == DF_OFF) |
| { |
| if (bindWA) bindWA->setErrStatus(); |
| *CmpCommon::diags() << DgSqlCode(-4130) << DgString0(token_); |
| return NULL; |
| } |
| } |
| } |
| |
| if (holdOrRestoreCQD_ == 0) |
| { |
| attrEnum_ = affectYourself ? defs.validateAndInsert(token_, value_, reset_) |
| : defs.validate (token_, value_, reset_); |
| if (attrEnum_ < 0) |
| { |
| if (bindWA) bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // remember this control in the control table |
| if (affectYourself) |
| ActiveControlDB()->setControlDefault(this); |
| } |
| else if ((holdOrRestoreCQD_ > 0) && (affectYourself)) |
| { |
| attrEnum_ = defs.holdOrRestore(token_, holdOrRestoreCQD_); |
| if (attrEnum_ < 0) |
| { |
| if (bindWA) bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| |
| return ControlAbstractClass::bindNode(bindWA); |
| } // ControlQueryDefault::bindNode() |
| |
| |
| RelExpr * ControlTable::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) return this; |
| |
| CMPASSERT(bindWA); // can't handle it yet if called from SQLC Preprocessor |
| |
| // remember this control in the control table |
| tableName_->applyDefaults(bindWA, bindWA->getDefaultSchema()); |
| |
| NABoolean ok = alterArkcmpEnvNow() ? |
| ActiveControlDB()->setControlTableValue(this) : |
| ActiveControlDB()->validate(this); |
| if (NOT ok) |
| { |
| if (bindWA) bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| return ControlAbstractClass::bindNode(bindWA); |
| } // ControlTable::bindNode() |
| |
| |
| RelExpr * ControlSession::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) return this; |
| |
| // remember this control in the control session |
| NABoolean ok = alterArkcmpEnvNow() ? |
| ActiveControlDB()->setControlSessionValue(this) : |
| ActiveControlDB()->validate(this); |
| if (NOT ok) |
| { |
| if (bindWA) bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| return ControlAbstractClass::bindNode(bindWA); |
| } // ControlSession::bindNode() |
| |
| |
| RelExpr * SetSessionDefault::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| if (getOperatorType() == REL_SET_SESSION_DEFAULT) |
| { |
| // trim leading and trailing spaces from token_ and value_ |
| // and upcase token |
| token_ = token_.strip(NAString::both); |
| value_ = value_.strip(NAString::both); |
| token_.toUpper(); |
| |
| // TBD: perhaps add a component privilege that allows others |
| // to set parserflags |
| if ((token_ == "SET_PARSERFLAGS") || |
| (token_ == "RESET_PARSERFLAGS")) |
| { |
| if (!ComUser::isRootUserID()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-1017); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| } |
| |
| return ControlAbstractClass::bindNode(bindWA); |
| } // SetSessionDefault::bindNode() |
| |
| |
| |
| // ----------------------------------------------------------------------- |
| // member function for class RelSetTimeout |
| // ----------------------------------------------------------------------- |
| |
| RelExpr * RelSetTimeout::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) return this; |
| |
| // Allocate an empty RETDesc and attach it to this node and the BindScope. |
| setRETDesc(new(bindWA->wHeap()) RETDesc(bindWA)); |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| |
| if (timeoutValueExpr_) { // bind the timeout-value expression |
| timeoutValueExpr_->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| } |
| |
| if ( ! strcmp("*", userTableName_.getCorrNameAsString()) ) |
| isForAllTables_ = TRUE ; |
| |
| HostVar *proto = userTableName_.getPrototype() ; |
| |
| // Check for the not-supported "SET STREAM TIMEOUT" on a specific stream |
| if ( isStream_ && ! isForAllTables_ ) { |
| *CmpCommon::diags() << DgSqlCode(-3187); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if ( isForAllTables_ ) { /* do nothing */ } |
| else if ( proto ) { // it is a HOSTVAR or DEFINE |
| userTableName_.applyDefaults(bindWA, bindWA->getDefaultSchema()); |
| CMPASSERT ( proto->isPrototypeValid() ) ; |
| userTableName_.getPrototype()->bindNode(bindWA); |
| } else { // i.e., an explicit table name was specified |
| // Get the NATable for this table. |
| NATable *naTable = bindWA->getNATable(userTableName_, FALSE); |
| if (bindWA->errStatus()) return this; // e.g. error: table does not exist |
| |
| if ( naTable->getViewText() ) { // can not set lock timeout on a view |
| *CmpCommon::diags() << DgSqlCode(-3189); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Extract and keep the physical file name |
| const NAFileSet * clstInd = naTable->getClusteringIndex() ; |
| setPhysicalFileName( clstInd->getFileSetName().getQualifiedNameAsString().data() ); |
| } |
| |
| // Bind the base class. |
| return bindSelf(bindWA); |
| } |
| // ----------------------------------------------------------------------- |
| // member functions for class Describe |
| // (see sqlcomp/CmpDescribe.cpp for execution of the request) |
| // ----------------------------------------------------------------------- |
| |
| RelExpr *Describe::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // SHOWCONTROL DEFAULT "magic string"; -- see ShowSchema.h and ExSqlComp.cpp |
| if (getFormat() == CONTROL_DEFAULTS_) { |
| if (getDescribedTableName().getQualifiedNameObj().getObjectName() == |
| ShowSchema::ShowControlDefaultSchemaMagic()) |
| { |
| // Return info in an error message (a warning msg doesn't cut it). |
| const SchemaName &catsch = bindWA->getDefaultSchema(); |
| NAString cat(catsch.getCatalogNameAsAnsiString(),bindWA->wHeap()); |
| NAString sch(catsch.getUnqualifiedSchemaNameAsAnsiString(),bindWA->wHeap()); |
| // |
| if (SqlParser_NAMETYPE == DF_NSK) { |
| // LCOV_EXCL_START - nsk |
| // The cat & sch from the BindWA are really from MPLOC. |
| // Get the real ANSI cat & sch, prepending them to the strings |
| // and put the MPLOC info in parens. |
| const SchemaName &csAnsi = ActiveSchemaDB()->getDefaultSchema(); |
| NAString cAnsi(csAnsi.getCatalogNameAsAnsiString(),bindWA->wHeap()); |
| NAString sAnsi(csAnsi.getUnqualifiedSchemaNameAsAnsiString(),bindWA->wHeap()); |
| cat.prepend(cAnsi + " ("); |
| cat += ")"; |
| sch.prepend(sAnsi + " ("); |
| sch += ")"; |
| // LCOV_EXCL_STOP |
| } |
| *CmpCommon::diags() << DgSqlCode(-ABS(ShowSchema::DiagSqlCode())) |
| << DgCatalogName(cat) << DgSchemaName (sch); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (getDescribedTableName().getQualifiedNameObj().getObjectName() == |
| GetControlDefaults::GetExternalizedDefaultsMagic()) |
| { |
| // Return info in an error message (a warning msg doesn't cut it). |
| NAString cqdPairs(bindWA->wHeap()); |
| size_t lenN, lenV; |
| char lenbufN[10], lenbufV[10]; |
| const char *nam, *val; |
| NADefaults &defs = bindWA->getSchemaDB()->getDefaults(); |
| for (CollIndex i = 0; i < defs.numDefaultAttributes(); i++ ) { |
| if (defs.getCurrentDefaultsAttrNameAndValue(i, nam, val, TRUE)) { |
| lenN = strlen(nam); |
| lenV = strlen(val); |
| CMPASSERT(lenN <= 999 && lenV <= 999); // %3d coming up next |
| sprintf(lenbufN, "%3d", (UInt32)lenN); |
| sprintf(lenbufV, "%3d", (UInt32)lenV); |
| cqdPairs += NAString(lenbufN) + nam + lenbufV + val; |
| } |
| } |
| *CmpCommon::diags() |
| << DgSqlCode(-ABS(GetControlDefaults::DiagSqlCode())) |
| << DgString0(cqdPairs); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // Create a descriptor for a virtual table to look like this: |
| // |
| // CREATE TABLE DESCRIBE__ (DESCRIBE__COL VARCHAR(3000) NOT NULL); |
| // For SeaQuest Unicode: |
| // CREATE TABLE DESCRIBE__ (DESCRIBE__COL VARCHAR(3000 BYTES) CHARACTER SET UTF8 NOT NULL); |
| // |
| #define MAX_DESCRIBE_LEN 3000 // e.g., SQL/MP Views.ViewText column |
| |
| // TrafAllocateDDLdesc requires that HEAP (STMTHEAP) be used for new's! |
| |
| TrafDesc * table_desc = TrafAllocateDDLdesc(DESC_TABLE_TYPE, NULL); |
| table_desc->tableDesc()->tablename = new HEAP char[strlen("DESCRIBE__")+1]; |
| strcpy(table_desc->tableDesc()->tablename, "DESCRIBE__"); |
| |
| // see nearly identical code below for indexes file desc |
| TrafDesc * files_desc = TrafAllocateDDLdesc(DESC_FILES_TYPE, NULL); |
| table_desc->tableDesc()->files_desc = files_desc; |
| |
| Lng32 colnumber = 0, offset = 0; |
| TrafDesc * column_desc = TrafMakeColumnDesc( |
| table_desc->tableDesc()->tablename, |
| "DESCRIBE__COL", |
| colnumber, // INOUT |
| REC_BYTE_V_ASCII, |
| MAX_DESCRIBE_LEN, |
| offset, // INOUT |
| FALSE/*not null*/, |
| SQLCHARSETCODE_UNKNOWN, |
| NULL); |
| column_desc->columnsDesc()->character_set = CharInfo::UTF8; |
| column_desc->columnsDesc()->encoding_charset = CharInfo::UTF8; |
| |
| table_desc->tableDesc()->colcount = colnumber; |
| table_desc->tableDesc()->record_length = offset; |
| |
| TrafDesc * index_desc = TrafAllocateDDLdesc(DESC_INDEXES_TYPE, NULL); |
| index_desc->indexesDesc()->tablename = table_desc->tableDesc()->tablename; |
| index_desc->indexesDesc()->indexname = table_desc->tableDesc()->tablename; |
| index_desc->indexesDesc()->keytag = 0; // primary index |
| index_desc->indexesDesc()->record_length = table_desc->tableDesc()->record_length; |
| index_desc->indexesDesc()->colcount = table_desc->tableDesc()->colcount; |
| index_desc->indexesDesc()->blocksize = 4096; // anything > 0 |
| |
| // Cannot simply point to same files desc as the table one, |
| // because then ReadTableDef::deleteTree frees same memory twice (error) |
| TrafDesc * i_files_desc = TrafAllocateDDLdesc(DESC_FILES_TYPE, NULL); |
| index_desc->indexesDesc()->files_desc = i_files_desc; |
| |
| TrafDesc * key_desc = TrafAllocateDDLdesc(DESC_KEYS_TYPE, NULL); |
| key_desc->keysDesc()->keyseqnumber = 1; |
| key_desc->keysDesc()->tablecolnumber = 0; |
| key_desc->keysDesc()->setDescending(FALSE); |
| |
| index_desc->indexesDesc()->keys_desc = key_desc; |
| table_desc->tableDesc()->columns_desc = column_desc; |
| table_desc->tableDesc()->indexes_desc = index_desc; |
| |
| // |
| // Get the NATable for this object. |
| // |
| CorrName corrName("DESCRIBE__"); |
| corrName.setSpecialType(ExtendedQualName::VIRTUAL_TABLE); |
| NATable *naTable = bindWA->getNATable(corrName, FALSE/*CatBind*/, table_desc); |
| if (bindWA->errStatus()) |
| return this; |
| // |
| // Allocate a TableDesc (which is not the table_desc we just constructed) |
| // and attach it to the Scan node. |
| // |
| setTableDesc(bindWA->createTableDesc(naTable, corrName)); |
| if (bindWA->errStatus()) |
| return this; |
| // |
| // Allocate an RETDesc and attach it to the Scan node and the BindScope. |
| // |
| setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA, getTableDesc())); |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| // |
| // Bind the described table CorrName member, the children, and the base class. |
| // |
| |
| if (! describedTableName_.getQualifiedNameObj().getObjectName().isNull()) |
| { |
| if ((getFormat() >= CONTROL_FIRST_) && |
| (getFormat() <= CONTROL_LAST_)) |
| { |
| describedTableName_.applyDefaults(bindWA, bindWA->getDefaultSchema()); |
| } |
| else |
| { |
| // do not override schema for showddl |
| bindWA->setToOverrideSchema(FALSE); |
| |
| // if this is a showlabel command on a resource fork, |
| // but the describedTableName |
| // is not a fully qualified rfork name, then get the rfork name |
| // for the specified table. |
| if ((getFormat() == Describe::LABEL_) && |
| (describedTableName_.getSpecialType() == ExtendedQualName::RESOURCE_FORK) && |
| (describedTableName_.getLocationName().isNull())) |
| { |
| describedTableName_.setSpecialType(ExtendedQualName::NORMAL_TABLE); |
| NATable *naTable = bindWA->getNATable(describedTableName_); |
| if (NOT bindWA->errStatus()) |
| { |
| // replace the describedTableName with its rfork name. |
| describedTableName_.setSpecialType(ExtendedQualName::RESOURCE_FORK); |
| |
| NAString rforkName = naTable->getClusteringIndex()->getFileSetName().getQualifiedNameAsString(); |
| char * rforkNameData = (char*)(rforkName.data()); |
| rforkNameData[rforkName.length()-1] += 1; |
| describedTableName_.setLocationName(rforkName); |
| } |
| } |
| |
| // check if we need to consider public schema before |
| // describedTableName_ is qualified by getNATable |
| if (describedTableName_.getQualifiedNameObj().getSchemaName().isNull()) |
| setToTryPublicSchema(TRUE); |
| |
| bindWA->getNATable(describedTableName_); |
| if (bindWA->errStatus()) |
| { |
| // if volatile related error, return it. |
| // Otherwise, clear diags and let this error be caught |
| // when describe is executed. |
| if ((CmpCommon::diags()->mainSQLCODE() == -4190) || |
| (CmpCommon::diags()->mainSQLCODE() == -4191) || |
| (CmpCommon::diags()->mainSQLCODE() == -4192) || |
| (CmpCommon::diags()->mainSQLCODE() == -4193) || |
| (CmpCommon::diags()->mainSQLCODE() == -4155) || // define not supported |
| (CmpCommon::diags()->mainSQLCODE() == -4086) || // catch Define Not Found error |
| (CmpCommon::diags()->mainSQLCODE() == -30044)|| // default schema access error |
| (CmpCommon::diags()->mainSQLCODE() == -4261) || // reserved schema |
| (CmpCommon::diags()->mainSQLCODE() == -1398)) // uninit hbase |
| return this; |
| |
| CmpCommon::diags()->clear(); |
| bindWA->resetErrStatus(); |
| } |
| } |
| if (pUUDFName_ NEQ NULL AND NOT pUUDFName_->getObjectName().isNull()) |
| { |
| pUUDFName_->applyDefaults(bindWA->getDefaultSchema()); |
| } |
| } |
| |
| bindChildren(bindWA); |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) return boundExpr; |
| // |
| // Assign the set of columns that belong to the table to be scanned |
| // as the output values that can be produced by this scan. |
| // |
| getGroupAttr()->addCharacteristicOutputs(getTableDesc()->getColumnList()); |
| return boundExpr; |
| } // Describe::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class RelLock |
| // ----------------------------------------------------------------------- |
| |
| RelExpr * RelLock::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // do not do override schema for this |
| bindWA->setToOverrideSchema(FALSE); |
| |
| // Get the NATable for this object. |
| NATable *naTable = bindWA->getNATable(getTableName()); |
| if (bindWA->errStatus()) |
| return this; |
| |
| NABoolean isView = !!naTable->getViewText(); |
| if (isView && !naTable->isAnMV()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4222) |
| << DgString0("Views"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| else |
| { |
| baseTableNameList_.insert((CorrName *)getPtrToTableName()); |
| } |
| |
| Int32 locSpec = 0; |
| NAString tabNames(bindWA->wHeap()); |
| |
| for (CollIndex i = 0; i < baseTableNameList_.entries(); i++) { |
| naTable = bindWA->getNATable(*baseTableNameList_[i]); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Genesis 10-990212-6908: |
| // Ignore the user-specified correlation name -- |
| // use just the 3-part tblname (and any LOCATION clause, etc). |
| // Then, insert only unique names into tabIds_ -- |
| // to prevent XTNM duplicates (errmsg 4056) |
| // when multiple layered views reference the same table or corr-name. |
| |
| CorrName bt(*baseTableNameList_[i]); |
| bt.setCorrName(""); |
| |
| NABoolean haveTDforThisBT = FALSE; |
| for (CollIndex j = 0; j < tabIds_.entries(); j++) { |
| if (bt == tabIds_[j]->getCorrNameObj()) { |
| haveTDforThisBT = TRUE; |
| break; |
| } |
| } |
| |
| if (!haveTDforThisBT) { |
| if (bt.isLocationNameSpecified()) locSpec++; |
| tabNames += NAString(", ") + |
| bt.getQualifiedNameObj().getQualifiedNameAsAnsiString(); |
| tabIds_.insert(bindWA->createTableDesc(naTable, bt)); |
| if (bindWA->errStatus()) return this; |
| } |
| } |
| |
| if (tabIds_.entries() > 1) { |
| CMPASSERT(locSpec == 0); |
| tabNames.remove(0, 2); // remove leading ", " |
| // Warning 4124: More than one table will be locked: $0~String0. |
| // (warning, so user realizes the effects of this command |
| // when run on a view which joins tables...). |
| *CmpCommon::diags() << DgSqlCode(+4124) << DgString0(tabNames); |
| } |
| |
| if ((isView) || |
| (tabIds_.entries() > 1) || |
| (baseTableNameList_.entries() > 1) || |
| (CmpCommon::getDefault(ATTEMPT_ESP_PARALLELISM) == DF_OFF)) |
| { |
| parallelExecution_ = FALSE; |
| } |
| |
| // Allocate an empty RETDesc and attach it to this node and the BindScope. |
| setRETDesc(new(bindWA->wHeap()) RETDesc(bindWA)); |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| |
| // Bind the base class. |
| return bindSelf(bindWA); |
| } // RelLock::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class RelTransaction |
| // ----------------------------------------------------------------------- |
| |
| RelExpr * RelTransaction::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // Allocate an empty RETDesc and attach it to this node and the BindScope. |
| setRETDesc(new(bindWA->wHeap()) RETDesc(bindWA)); |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| |
| if (diagAreaSizeExpr_) { |
| diagAreaSizeExpr_->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| } |
| |
| // "mode_" is NULL if BEGIN/COMMIT/ROLLBACK WORK, nonNULL if SET TRANSACTION. |
| if (mode_) { |
| if ((mode_->autoCommit() != TransMode::AC_NOT_SPECIFIED_) || |
| (mode_->getAutoBeginOn() != 0) || |
| (mode_->getAutoBeginOff() != 0)) |
| { |
| CMPASSERT(mode_->isolationLevel() == TransMode::IL_NOT_SPECIFIED_ && |
| mode_->accessMode() == TransMode::AM_NOT_SPECIFIED_); |
| } |
| else |
| { |
| // See Ansi 14.1, especially SR 4. |
| // Similar code must be maintained in |
| // comexe/ExControlArea::addControl() and NADefaults::validateAndInsert(). |
| |
| // SET TRANSACTION w/o specifying ISOLATION LEVEL reverts TransMode to |
| // the NADefaults setting of ISOLATION_LEVEL |
| // (which the user should set to SERIALIZABLE if they want |
| // SET TRANSACTION to be Ansi conformant). |
| |
| if (mode_->isolationLevel() == TransMode::IL_NOT_SPECIFIED_) |
| { |
| if (CmpCommon::getDefault(ISOLATION_LEVEL_FOR_UPDATES) == DF_NONE) |
| bindWA->getSchemaDB()->getDefaults().getIsolationLevel( |
| mode_->isolationLevel()); // short int |
| else |
| bindWA->getSchemaDB()->getDefaults().getIsolationLevel( |
| mode_->isolationLevel(), // short int |
| CmpCommon::getDefault(ISOLATION_LEVEL_FOR_UPDATES)); |
| } |
| |
| if (mode_->accessMode() == TransMode::AM_NOT_SPECIFIED_) |
| mode_->updateAccessModeFromIsolationLevel( |
| mode_->getIsolationLevel()); // enum |
| |
| // 3114 Transaction access mode RW is incompatible with isolation level RU |
| if (mode_->accessMode() == TransMode::READ_WRITE_ && |
| mode_->isolationLevel() == TransMode::READ_UNCOMMITTED_) { |
| *CmpCommon::diags() << DgSqlCode(-3114); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (mode_->rollbackMode() == TransMode::ROLLBACK_MODE_NOT_SPECIFIED_) |
| mode_->rollbackMode() = TransMode::ROLLBACK_MODE_WAITED_ ; |
| // 4352 - |
| if (mode_->multiCommit() == TransMode::MC_ON_) |
| { |
| if (mode_->invalidMultiCommitCompatibility()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4352); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| } |
| } |
| } // SET TRANSACTION stmt |
| |
| // Bind the base class. |
| return bindSelf(bindWA); |
| } |
| |
| |
| // Transpose::bindNode - Bind the transpose node. |
| // Coming into the node (from the parser) there are two ItemExpr Trees: |
| // |
| // keyCol_: The ItemExpr contains a ColReference to the key column which |
| // is added by the transpose node. This pointer ia set to NULL by bindNode. |
| // If keyCol_ is NULL coming into the bindNode, then no key Column is |
| // generated for this transpose. |
| // |
| // transValsTree_: This ItemExpr tree contains a list of pairs which is |
| // NULL terminated (for ease of processing). Each pair contains in child(0), |
| // a list of transpose items for a given transpose set and in child(1), a |
| // list of ColReferences to the new value columns associated with this |
| // transpose set. A transpose item is a list of value expressions. |
| // This pointer is set to NULL by bindNode. |
| // |
| // For Example: |
| // |
| // SELECT * |
| // FROM Table |
| // TRANSPOSE A,B AS C1 |
| // X,Y,Z as C2 |
| // (1,'hello'),(2,'world') AS (C3, C4) |
| // KEY BY K1 |
| // |
| // For the above query, after parsing, the TRANSPOSE node will look like: |
| // |
| // TRANSPOSE |
| // keyCol_ transValsTree_ |
| // | | |
| // K1 O------O---------O---NULL |
| // | | | |
| // O O O--O |
| // |\ |\ | |\ |
| // O C1 O C2 | C3 C4 |
| // |\ |\ O---------O---NULL |
| // A O X O | | |
| // |\ |\ O O |
| // B NULL Y O |\ |\ |
| // |\ 1 'hello' 2 'world' |
| // Z NULL |
| // |
| // O - represent ITM_LIST nodes. |
| // |
| // bindNode binds this structure to form a new structure contained in |
| // the vector of ValueIdLists, transUnionVector_. |
| // |
| // transUnionVector_: This is a vector of ValueIdLists. There is one entry |
| // for each transpose set, plus one entry for the key values. Each entry |
| // contains a list of ValueIdUnion Nodes. The first entry contains a list |
| // with one ValueIdUnion node. This node is for the Const. Values (1 - N) |
| // representing the Key Values. The other entries contain lists of |
| // ValueIdUnion nodes for the Transposed Values. Each of these entries of |
| // the vector represent a transpose set. If the transpose set contains a |
| // list of values, then there will be only one ValueIdUnion node in the |
| // list. If the transpose set contains a list of lists of values, then |
| // there will be as many ValueIdUnion nodes as there are items in the |
| // sublists. (see example below.) |
| // transUnionVector_ is generated in bindNode(). |
| // |
| // transUnionVectorSize_: This is the number of entries in transUnionVector_. |
| // |
| // For the above query, after binding, the TRANSPOSE node will look like: |
| // |
| // TRANSPOSE |
| // transUnionVectorSize_: 4 |
| // transUnionVector_: |
| // ValueIdUnion(1,2,3,4,5,6,7) |
| // ValueIdUnion(A,B) |
| // ValueIdUnion(X,Y,Z) |
| // ValueIdUnion(1,2) , ValueIdUnion('hello','world') |
| // |
| // |
| RelExpr *Transpose::bindNode(BindWA *bindWA) |
| { |
| |
| // If this node has already been bound, we are done. |
| // |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| BindContext *curContext = bindWA->getCurrentScope()->context(); |
| curContext->inTransposeClause() = TRUE; |
| |
| // Bind the child nodes. |
| // |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // At this point the Transpose relational operator has two or three |
| // expressions: |
| // keyCol_ --- A ColReference to the new keyCol. (possibly NULL) |
| // transValsTree_ --- expressions for the transposed values and their |
| // ColReferences. |
| // |
| // transpose::bindNode() performs the following steps: |
| // |
| // 1 - Construct a list of transpose set expressions |
| // and a list of ColReferences associated with each transpose set |
| // expression. |
| // |
| // 2 - Allocate a return descriptor and add the columns from the |
| // childs descriptor to it. |
| // |
| // 3 - Allocate the transUnionVector_ |
| // |
| // 4 - Construct a ValueIdUnion node for the Key Values. Bind this node. |
| // Add the keyColName to the return descriptor with the valueId of this |
| // node. Add the valueId of this node as the first entry of |
| // a ValueIdList in the first entry of transUnionVector_. |
| // |
| // 5 - For each transpose set, Construct as many ValueIdUnion nodes as |
| // there are values in each item of the transpose set. Within a |
| // given transpose set, the number of values per item must be the |
| // same. In the example above, the third transpose set contains the |
| // items (1, 'hello') and (2, 'world'). These both have two values per |
| // item. The others all have 1 value per item. The ValueIdUnions |
| // generated will contain the i'th value from each item. Bind each |
| // of these ValueUnionId nodes. Add the value column name to the |
| // return descriptor with the valueId of this node. Add the valueId |
| // of this node the ValueIdList in the proper entry of |
| // transUnionVector_. |
| // |
| // 6 - Set the return descriptor, and bindSelf. |
| // |
| |
| CollIndex i, j, k; |
| CollIndex numTransSets = 0; |
| |
| // Get a pointer to the head of this list of pairs. |
| // This is the last time we will have to reference this tree. |
| // |
| ItemExpr *transTree = (ItemExpr *)removeTransValsTree(); |
| |
| // Allocate two ItemExpr Lists. One for the list of lists of (lists of) |
| // expressions. And the other for the list of (lists of) ColReferences. |
| // |
| ItemExprList transSetsList(bindWA->wHeap()); |
| ItemExprList newColsList(bindWA->wHeap()); |
| |
| // Populate these lists and |
| // determine how many transpose sets there are in this tree. |
| // In the example above, there should be three. |
| // |
| while (transTree) { |
| transSetsList.insert(transTree->child(0)->child(0)); |
| newColsList.insert(transTree->child(0)->child(1)); |
| numTransSets++; |
| transTree = transTree->child(1); |
| } |
| |
| // Must have at least one value expression in the transpose values list. |
| // |
| CMPASSERT(numTransSets > 0); |
| |
| // Using the example above, at this point: |
| // |
| // transSetsList newColsList |
| // | | | | | | |
| // O O O---------O---NULL C1 C2 O |
| // |\ |\ | | |\ |
| // A O X O O O C3 C4 |
| // |\ |\ |\ |\ |
| // B NULL Y O 1 'hello' 2 'world' |
| // |\ |
| // Z NULL |
| // |
| // Allocate the return descriptor. This will contain the |
| // columns of the child node as well as the new columns added |
| // by the transpose operator. The column order is: |
| // |
| // [childs columns][keyCol][valCol1][valCol2] ... |
| // |
| // Using the example, this would be: |
| // |
| // [childs columns], K1, C1, C2, C3, C4 |
| // |
| RETDesc *resultTable = new(bindWA->wHeap()) RETDesc(bindWA); |
| |
| // Add the columns from the child to the RETDesc. |
| // |
| const RETDesc &childTable = *child(0)->getRETDesc(); |
| resultTable->addColumns(bindWA, childTable); |
| |
| transUnionVectorSize_ = numTransSets + 1; |
| transUnionVector() = new(bindWA->wHeap()) |
| ValueIdList[transUnionVectorSize_]; |
| //If there is a lob column return error. Transpose not allowed on lob columns. |
| |
| for (i = 0; i < resultTable->getDegree(); i++) |
| { |
| if ((resultTable->getType(i)).getFSDatatype() == REC_BLOB || |
| (resultTable->getType(i)).getFSDatatype() == REC_CLOB) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4322); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| |
| |
| // Get the key column reference |
| // This is the last time we need this ItemExpr. |
| // |
| ColReference *keyColumn = (ColReference *)removeKeyCol(); |
| |
| // If no key column has been specified, then no key col will be |
| // generated. |
| // |
| if (keyColumn) { |
| |
| //Get the key column name. |
| // |
| NAString keyColName(keyColumn->getColRefNameObj().getColName(), bindWA->wHeap()); |
| |
| // Construct and Bind the ValueIdUnion node as the union of constants |
| // from 1 to the total number of transpose expressions. In the above |
| // example this will be from 1 to 9, since there are 3 transpose sets |
| // and each set has 3 expressions. |
| // |
| ValueIdList constVals; |
| ItemExpr *constExpr; |
| |
| CollIndex keyVal; |
| |
| // For each expression in each transpose set. |
| // |
| for (i = 0, keyVal = 1; i < numTransSets; i++) { |
| |
| // Determine how many expressions are in each transpose set. |
| // |
| CollIndex numTransItems = 0; |
| |
| ItemExpr *transSet = transSetsList[i]; |
| |
| while (transSet) { |
| numTransItems++; |
| transSet = transSet->child(1); |
| } |
| |
| for (j = 0; j < numTransItems; j++, keyVal++) { |
| |
| // Construct the constant value |
| // |
| #pragma nowarn(1506) // warning elimination |
| constExpr = new(bindWA->wHeap()) SystemLiteral(keyVal); |
| #pragma warn(1506) // warning elimination |
| |
| // Bind the constant value. |
| // |
| constExpr->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // Insert the valueId into the list |
| // |
| constVals.insert(constExpr->getValueId()); |
| } |
| } |
| |
| // Construct the ValueIdUnion node which will represent the key Col. |
| // |
| ValueIdUnion *keyVidu = new(bindWA->wHeap()) |
| ValueIdUnion(constVals, NULL_VALUE_ID); |
| |
| // Bind the ValueIdUnion node. |
| // |
| keyVidu->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // Add the key column to the RETDesc (as the union of all the constants) |
| // |
| resultTable->addColumn(bindWA, keyColName, keyVidu->getValueId()); |
| |
| // The ValueIdUnion for the Key Values is the first entry in |
| // the ValueIdList of the first entry of transUnionVector_. |
| // |
| transUnionVector()[0].insert(keyVidu->getValueId()); |
| } |
| |
| // For each transpose set, |
| // - bind the list of expressions. |
| // - Construct a ValueIdUnion node containing the resulting valueIds. |
| // - Bind this ValueIdUnion node |
| // - Add the associate column name to the return descriptor with the |
| // valueId of the ValueIdUnion node. |
| // |
| ValueIdList transVals; |
| |
| for (i = 0; i < numTransSets; i++) { |
| |
| // The column(s) associated with this transpose set. |
| // (will be used below, within the inner loop) |
| // |
| ItemExprList newCols(newColsList[i], bindWA->wHeap()); |
| |
| // Determine how many expressions are in each transpose set. |
| // |
| CollIndex numTransItems = 0; |
| |
| ItemExpr *transSet = transSetsList[i]; |
| |
| ItemExprList transItemList(bindWA->wHeap()); |
| |
| // Populate this list. |
| // |
| while (transSet) { |
| transItemList.insert(transSet->child(0)); |
| numTransItems++; |
| transSet = transSet->child(1); |
| } |
| |
| ItemExprList transItem(transItemList[0], bindWA->wHeap()); |
| |
| CollIndex numTransVals = transItem.entries(); |
| |
| // For a given transpose set, the number of new columns declared |
| // must be the same as the number of items per value. In the example |
| // above, the third transpose set contains the items (1, 'hello') and |
| // the columns (C3,C4) both have two entries. |
| // |
| if (numTransVals != newCols.entries()) { |
| *CmpCommon::diags() << DgSqlCode(-4088); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| for (k = 0; k < numTransVals; k++) { |
| |
| ItemExpr *transValueUnionExpr = NULL; |
| |
| for (j = 0; j < numTransItems; j++) { |
| |
| transItem.clear(); |
| transItem.insertTree(transItemList[j], ITM_ITEM_LIST); |
| |
| // Within a given transpose set, the number of values per item |
| // must be the same. In the example above, the third transpose |
| // set contains the items (1, 'hello') and (2, 'world'). These |
| // both have two values per item. The others all have 1 value |
| // per item. |
| // |
| |
| if (numTransVals != transItem.entries()) { |
| *CmpCommon::diags() << DgSqlCode(-4088); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (transValueUnionExpr == NULL) { |
| transValueUnionExpr = transItem[k]; |
| } |
| else |
| { |
| transValueUnionExpr = new (bindWA->wHeap()) |
| ItemList(transValueUnionExpr, transItem[k]); |
| } |
| } |
| |
| |
| // Bind the Transpose Values expressions. Get the expression value Id's |
| // |
| transVals.clear(); |
| if(transValueUnionExpr != NULL ) |
| transValueUnionExpr->convertToValueIdList(transVals, |
| bindWA, |
| ITM_ITEM_LIST); |
| if (bindWA->errStatus()) return this; |
| |
| // If there are more than one transpose set, the value columns |
| // generated by transpose can be NULL. So, make sure the typing is |
| // done properly. This is done by setting the first in the list to |
| // be nullable, then the ValueIdUnion will be nullable and the new |
| // column will be nullable. This is not done on the ValueIdUnion |
| // node itself, since it will add an Null Instantiate node, and |
| // we later assume that this node will always be a ValueIdUnion |
| // node. |
| // |
| if (numTransSets > 1) { |
| ValueId valId = transVals[0]; |
| |
| transVals[0] = valId.nullInstantiate(bindWA, FALSE); |
| } |
| |
| // Construct and Bind the ValueIdUnion node for the transpose vals. |
| // |
| ValueIdUnion *valVidu = new(bindWA->wHeap()) |
| ValueIdUnion(transVals, NULL_VALUE_ID); |
| |
| valVidu->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // Insert this valueIdUnion node into the list of valueIdUnions |
| // in the proper entry in transUnionVector_ |
| // |
| transUnionVector()[i + 1].insert(valVidu->getValueId()); |
| |
| // Get the val column reference |
| // |
| ColReference *valCol = (ColReference *)newCols[k]; |
| |
| // Must have Column Refs to val column. |
| // |
| CMPASSERT(valCol); |
| |
| //Get the val column name. |
| // |
| NAString valColName( valCol->getColRefNameObj().getColName(), bindWA->wHeap()); |
| |
| // Add the transpose column |
| // (as the union of all of the transposed value columns) |
| // |
| resultTable->addColumn(bindWA, valColName, valVidu->getValueId()); |
| } |
| } |
| |
| // Set the return descriptor |
| // |
| setRETDesc(resultTable); |
| bindWA->getCurrentScope()->setRETDesc(resultTable); |
| |
| // |
| // Bind the base class. |
| // |
| return bindSelf(bindWA); |
| } // Transpose::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // The Pack node binds itself by componsing its packing expression from |
| // all the columns available in its child's RETDesc. The packed columns |
| // produced by the packing expression are then made available in the Pack |
| // node's own RETDesc. |
| // ----------------------------------------------------------------------- |
| RelExpr* Pack::bindNode(BindWA* bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // --------------------------------------------------------------------- |
| // The Pack node has a packing expression stored as packingExprTree_ |
| // before binding. If packingExprTree_ is NULL, we are just going to |
| // pick up all the columns from the output of its child. During binding, |
| // this tree is converted into a value id list. |
| // --------------------------------------------------------------------- |
| |
| // Create and bind the packing factor item expression. |
| #pragma nowarn(1506) // warning elimination |
| ItemExpr* pfie = new (bindWA->wHeap()) SystemLiteral(packingFactorLong()); |
| #pragma warn(1506) // warning elimination |
| pfie->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // Insert vid of bound constant into packingFactor valueIdSet. |
| packingFactor().clear(); |
| packingFactor().insert(pfie->getValueId()); |
| |
| // Create my RETDesc to hold the packed columns. |
| RETDesc* resultTable = new (bindWA->wHeap()) RETDesc (bindWA); |
| |
| // Bind the tree if its present. |
| if (packingExprTree_) |
| { |
| ItemExpr* packExprTree = removePackingExprTree(); |
| packExprTree->convertToValueIdList(packingExpr(), bindWA, ITM_ITEM_LIST); |
| if (bindWA->errStatus()) return this; |
| |
| for (CollIndex i = 0; i < packingExpr().entries(); i++) |
| { |
| // Add all columns to result table. |
| NAString packedColName( "PACKEDCOL_", bindWA->wHeap()); |
| packedColName += bindWA->fabricateUniqueName(); |
| #pragma nowarn(1506) // warning elimination |
| Int32 length = packedColName.length(); |
| #pragma warn(1506) // warning elimination |
| char * colName = new (bindWA->wHeap()) char[length + 1]; |
| colName[length] = 0; |
| #pragma nowarn(1506) // warning elimination |
| str_cpy_all(colName, packedColName, packedColName.length()); |
| #pragma warn(1506) // warning elimination |
| |
| ColRefName colRefName(colName); |
| resultTable->addColumn(bindWA, |
| colRefName, |
| packingExpr().at(i), |
| USER_COLUMN, |
| colName); |
| } |
| } |
| else // no packing expr tree, get all the columns from child. |
| { |
| // Get RETDesc from child which is assumed to be a RelRoot. too strict? |
| const RETDesc& childTable = *child(0)->getRETDesc(); |
| ValueIdList childTableVidList; |
| |
| // These are only the user columns. Are SYS columns important? |
| childTable.getValueIdList(childTableVidList); |
| |
| // Initialize packing expression. |
| packingExpr().clear(); |
| |
| // For each column in child's RETDesc, put a PackFunc() on top of it. |
| for (CollIndex i = 0; i < childTableVidList.entries(); i++) |
| { |
| ItemExpr* childItemExpr = childTableVidList[i].getItemExpr(); |
| PackFunc* packedItemExpr = new (bindWA->wHeap()) |
| PackFunc(childItemExpr,pfie); |
| // Bind the packed column. |
| packedItemExpr->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // Insert into both the result table and my packingExpr_. |
| packingExpr().insert(packedItemExpr->getValueId()); |
| |
| // $$$ Any implications of this? Needed to be seen. |
| // Use the original column name as the packed column name. The index |
| // is on USER columns only. SYS columns matter? |
| ColRefName colRefName = childTable.getColRefNameObj(i); |
| const char* heading = childTable.getHeading(i); |
| |
| // Insert into RETDesc for RelRoot above it to pick up as select-list. |
| resultTable->addColumn(bindWA, |
| colRefName, |
| packedItemExpr->getValueId(), |
| USER_COLUMN, |
| heading); |
| // $$$ |
| // OR: start with making a copy of child's RETDesc and change each col |
| // to point to the vid for the packed column instead of the original. |
| } |
| } |
| |
| // Set the result table, bind self and return. |
| setRETDesc(resultTable); |
| bindWA->getCurrentScope()->setRETDesc(resultTable); |
| bindSelf(bindWA); |
| |
| // To test packing. Add a unpack node on top of this pack node to check. |
| char* env = getenv("PACKING_FACTOR"); |
| if (env && atol(env) > 0) |
| { |
| Lng32 pf = atol(env); |
| |
| ItemExpr* unPackExpr = NULL; |
| ItemExpr* rowFilter = NULL; |
| ItemExpr* unPackItem; |
| ItemExpr* numRows; |
| const NAType* typeInt = new(bindWA->wHeap()) SQLInt(TRUE,FALSE); |
| |
| ValueIdList packedCols; |
| resultTable->getValueIdList(packedCols); |
| |
| NAString hostVarName("_sys_UnPackIndex", bindWA->wHeap()); |
| hostVarName += bindWA->fabricateUniqueName(); |
| ItemExpr* indexHostVar = new(bindWA->wHeap()) |
| HostVar(hostVarName,new(bindWA->wHeap()) SQLInt(TRUE,FALSE),TRUE); |
| indexHostVar->synthTypeAndValueId(); |
| |
| for (CollIndex i=0; i < packedCols.entries(); i++) |
| { |
| const NAType* colType = |
| &(packedCols[i].getItemExpr()->child(0)->getValueId().getType()); |
| |
| Lng32 width = colType->getNominalSize(); |
| #pragma nowarn(1506) // warning elimination |
| Lng32 base = (colType->supportsSQLnullPhysical() ? (pf-1)/CHAR_BIT +1 : 0) |
| + sizeof(Int32); |
| #pragma warn(1506) // warning elimination |
| |
| // $$$ Some duplicate code to be moved to PackColDesc later. |
| ColRefName colRefName; |
| colRefName = resultTable->getColRefNameObj(i); |
| unPackItem = new(bindWA->wHeap()) |
| UnPackCol(packedCols[i].getItemExpr(), |
| indexHostVar, |
| width, |
| base, |
| colType->supportsSQLnull(), |
| colType); |
| |
| numRows = new(bindWA->wHeap()) |
| UnPackCol(packedCols[i].getItemExpr(), |
| new(bindWA->wHeap()) SystemLiteral(0), |
| typeInt->getNominalSize(), |
| 0, |
| FALSE, |
| typeInt); |
| |
| unPackExpr = (unPackExpr ? |
| new(bindWA->wHeap()) ItemList(unPackExpr,unPackItem) : |
| unPackItem); |
| |
| rowFilter = (rowFilter ? |
| new(bindWA->wHeap()) ItemList(rowFilter,numRows) : |
| numRows); |
| } |
| |
| RelExpr* unpack = |
| new(bindWA->wHeap()) UnPackRows(pf,unPackExpr,rowFilter,NULL, |
| this, indexHostVar->getValueId()); |
| return unpack->bindNode(bindWA); |
| } |
| |
| return this; |
| |
| } // Pack::bindNode() |
| |
| RelExpr * Rowset::bindNode(BindWA* bindWA) |
| { |
| // If this node has already been bound, we are done. |
| if (nodeIsBound()) |
| return this->transformRelexpr_; |
| |
| if (bindWA->getHostArraysArea()) { |
| bindWA->getHostArraysArea()->done() = TRUE; |
| } |
| |
| // |
| // Bind the child nodes. |
| // |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // Transform current node into a new subtree which performs access to |
| // RowSet based on the unpacking and tuple node expression operators. |
| // The formed tuple is composed of all input RowSet host variables: |
| // Rowset-tuple: array_hv1, array_hv2, ... array_hvN. |
| // The Unpack expression is used to retrieve the elements of the Rowset |
| // with an indexed operator. For example, retrieve values for index two |
| // for each Rowset host variable. |
| // The transformed subtree has the following structure |
| // |
| // UNPACK |
| // | |
| // TUPLE |
| // |
| // Note that the original Rowset relational expression has a rename node |
| // on top. |
| // |
| |
| // First find the maxRowSetSize and its rowsetSizeExpr. The rowset size is |
| // the smallest declared dimension of the arrays composing the rowset. |
| // If a constant rowset size was given in the SQL statement, it must be |
| // samaller than the computed value. |
| |
| NABoolean hasDifferentSizes = FALSE; |
| Lng32 maxRowsetSize = 0; /* Maximum number of Rows in Rowset */ |
| ItemExpr *rowsetSizeExpr; |
| ItemExpr *hostVarTree; |
| |
| // We get the list of input host vars, which is stored in the root of the |
| // parse tree |
| HostArraysWA *arrayArea = bindWA->getHostArraysArea(); |
| RelRoot *root = bindWA->getTopRoot(); |
| |
| // Do any extra checking at this moment. |
| for (hostVarTree = inputHostvars_; |
| hostVarTree; |
| hostVarTree = hostVarTree->child(1)) { |
| CMPASSERT(hostVarTree->getOperatorType() == ITM_ITEM_LIST); |
| HostVar *hostVar = (HostVar *)hostVarTree->getChild(0); |
| |
| if (hostVar->getOperatorType() != ITM_HOSTVAR || |
| hostVar->getType()->getTypeQualifier() != NA_ROWSET_TYPE) { |
| // 30001 A rowset must be composed of host variable arrays |
| *CmpCommon::diags() << DgSqlCode(-30001); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // Get the smallest dimension for rowset size |
| |
| SQLRowset* hostVarType = (SQLRowset *)hostVar->getType(); |
| |
| if (hostVarType->getNumElements() <= 0) { |
| // 30004 The dimesion of the arrays composing the RowSet must be greater |
| // than zero. A value of $0~Int0 was given |
| *CmpCommon::diags() << DgSqlCode(-30004) |
| << DgInt0((Int32)hostVarType->getNumElements()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (maxRowsetSize == 0) |
| maxRowsetSize = hostVarType->getNumElements(); |
| else if (hostVarType->getNumElements() != maxRowsetSize) { |
| // 30005 The dimensions of the arrays composing the RowSet are |
| // different. The smallest dimesnion is assumed. |
| // This is just a warning |
| |
| // Give the warning only once |
| if (hasDifferentSizes == FALSE) { |
| if (arrayArea->hasDynamicRowsets()) { |
| // 30015 The dimesion of the arrays composing the RowSet must be same |
| // in dynamic SQL |
| *CmpCommon::diags() << DgSqlCode(-30015) ; |
| bindWA->setErrStatus(); |
| return NULL; |
| } // for static SQL this is only a warning. |
| hasDifferentSizes = TRUE; |
| *CmpCommon::diags() << DgSqlCode(30005); |
| } |
| // Pick the smallest one |
| if (hostVarType->getNumElements() < maxRowsetSize) |
| maxRowsetSize = hostVarType->getNumElements(); |
| } |
| |
| // Make sure that the element type null indicator and the corresponding |
| // rowset array are both nullable or not nullable. That is, force it |
| |
| NAType* hostVarElemType = hostVarType->getElementType(); |
| NABoolean hostVarElemNullInd = !(hostVar->getIndName().isNull()); |
| // If hostVarType is Unknown then this a dynamic param that has been |
| // converted into a hostvar. For dynamic params there is no null |
| // indicator variable/param specified in the query text, so the previous |
| // check will always return FALSE. We will set all dynamic params to be |
| // nullable and let type synthesis infer nullability later on. |
| if (hostVarElemType->getTypeQualifier() == NA_UNKNOWN_TYPE) |
| hostVarElemNullInd = TRUE; |
| |
| hostVarElemType->setNullable(hostVarElemNullInd); |
| } |
| |
| // If a rowset size expression was produced during parsing, it is used |
| // to restrict the rowset size during execution. The expression must be |
| // an numeric literal (known at compile time) or an integer host variable |
| // (known at execution time). We do not allow other type of expression |
| // since the rowset size must be know before the statement is executed to |
| // avoid copying a lot when the host variable arrays are sent down the |
| // execution queue |
| |
| // If there is no size specification of the form ROWSET <size> ( <list> ) then |
| // we take the size from ROWSET FOR INPUT SIZE <size> |
| if (!sizeExpr_ && bindWA->getHostArraysArea()) { |
| sizeExpr_ = bindWA->getHostArraysArea()->inputSize(); |
| if ((bindWA->getHostArraysArea()->getInputArrayMaxSize() > 0) && |
| (!sizeExpr_ )) { |
| // ODBC process is performing a bulk insert and we need to create |
| // an input parameter to simulate the functionality of ROWSET FOR INPUT |
| // SIZE ... syntax. |
| |
| NAString name = "__arrayinputsize" ; |
| HostVar *node = new (bindWA->wHeap()) |
| HostVar(name, |
| new(bindWA->wHeap()) SQLInt(TRUE,FALSE), |
| TRUE); |
| node->setHVRowsetForInputSize(); |
| root->addAtTopOfInputVarTree(node); |
| sizeExpr_ = (ItemExpr *) node ; |
| } |
| } |
| |
| if (sizeExpr_) { |
| if (sizeExpr_->getOperatorType() == ITM_CONSTANT) { |
| if (((ConstValue *)sizeExpr_)->getType()->getTypeQualifier() |
| != NA_NUMERIC_TYPE) { |
| // 30003 Rowset size must be an integer host variable or an |
| // integer constant |
| *CmpCommon::diags() << DgSqlCode(-30003); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (((ConstValue *)sizeExpr_)->getExactNumericValue() <= 0) { |
| // 30004 The dimesion of the arrays composing the RowSet must be |
| // greater than zero. A value of $0~Int0 was given |
| *CmpCommon::diags() << DgSqlCode(-30004) |
| << DgInt0((Int32) (((ConstValue *)sizeExpr_) |
| ->getExactNumericValue())); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (((ConstValue *)sizeExpr_)->getExactNumericValue() > maxRowsetSize) { |
| // 30002 The given RowSet size ($0~Int0) must be smaller or |
| // equal to the smallest dimension ($1Int1) of the |
| // arrays composing the rowset |
| *CmpCommon::diags() << DgSqlCode(-30002) |
| << DgInt0((Int32) |
| ((ConstValue *)sizeExpr_) |
| ->getExactNumericValue()) |
| << DgInt1(maxRowsetSize); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| else { |
| maxRowsetSize = (Lng32)((ConstValue *)sizeExpr_)->getExactNumericValue() ; |
| } |
| } |
| else |
| if (!((sizeExpr_->getOperatorType() == ITM_HOSTVAR && |
| ((HostVar *)sizeExpr_)->getType()->getTypeQualifier() |
| == NA_NUMERIC_TYPE) || |
| (sizeExpr_->getOperatorType() == ITM_DYN_PARAM ) || |
| ((sizeExpr_->getOperatorType() == ITM_CAST) && |
| (sizeExpr_->child(0)->getOperatorType() == ITM_DYN_PARAM)))) |
| { |
| // 30003 Rowset size must be an integer host variable or an |
| // integer constant |
| *CmpCommon::diags() << DgSqlCode(-30014); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // We return a -1 if the execution time rowset size exceeds the maximum |
| // declared size |
| ItemExpr *maxSize = new (bindWA->wHeap()) SystemLiteral(maxRowsetSize); |
| ItemExpr *neg = new (bindWA->wHeap()) SystemLiteral(-1); |
| ItemExpr *constrPred = new (bindWA->wHeap()) |
| BiRelat(ITM_GREATER, sizeExpr_, maxSize); |
| rowsetSizeExpr = new (bindWA->wHeap()) |
| IfThenElse(constrPred, neg, sizeExpr_); |
| |
| // IfThenElse only works if Case is its parent. |
| rowsetSizeExpr = new (bindWA->wHeap()) Case (NULL, rowsetSizeExpr); |
| |
| // At code generation time, it is assumed that the size expression |
| // is of size integer, so we do this cast. We do not allow null |
| // values. |
| rowsetSizeExpr = new (bindWA->wHeap()) |
| Cast(rowsetSizeExpr, new (bindWA->wHeap()) SQLInt(TRUE,FALSE)); |
| |
| // For dynamic rowsets, the parameter specifying rowset for input size |
| // must be typed as an non-nullable integer. |
| if (sizeExpr_->getOperatorType() == ITM_DYN_PARAM ) { |
| sizeExpr_->synthTypeAndValueId(); |
| SQLInt intType(TRUE,FALSE); // TRUE -> allow neagtive values, FALSE -> not nullable |
| (sizeExpr_->getValueId()).coerceType(intType, NA_NUMERIC_TYPE); |
| } |
| |
| |
| } |
| else |
| { |
| rowsetSizeExpr = new (bindWA->wHeap()) SystemLiteral(maxRowsetSize); |
| } |
| |
| // Construct an index host variable to iterate over the elements of the |
| // rowset. The name of the host variable must be unique (fabricated |
| // by calling fabricateUniqueName). This host variable is bound since it |
| // is not an input of the parse tree. Call synthTypeAndValueId() |
| // which does the minimum binding. |
| |
| NAString indexName(bindWA->wHeap()); |
| if (indexExpr_) { |
| // Get the name. |
| indexName = ((ColReference *)indexExpr_)->getColRefNameObj().getColName(); |
| } else { |
| indexName = "_sys_rowset_index" + bindWA->fabricateUniqueName(); |
| } |
| |
| const NAType *indexType = new (bindWA->wHeap()) SQLInt(TRUE, FALSE); |
| ItemExpr *indexHostVar = new (bindWA->wHeap()) |
| HostVar(indexName, indexType, |
| TRUE // is system-generated |
| ); |
| |
| indexHostVar->synthTypeAndValueId(); |
| |
| // Generate the RowsetArrayScan expressions which are used to extract |
| // an element value of the rowset array given an index. |
| |
| ItemExpr *unPackExpr = NULL; |
| |
| for (hostVarTree = inputHostvars_; |
| hostVarTree; |
| hostVarTree = hostVarTree->child(1)) { |
| HostVar *hostVar = (HostVar *)hostVarTree->getChild(0); |
| SQLRowset* hostVarType = (SQLRowset *)hostVar->getType(); |
| NAType* hostVarElemType = hostVarType->getElementType(); |
| Lng32 hostVarElemSize = hostVarElemType->getTotalSize(); |
| NABoolean hostVarElemNullInd = !(hostVar->getIndName().isNull()); |
| |
| // Force all host variable to have the same number of elements which was |
| // found previously |
| hostVarType->setNumElements(maxRowsetSize); |
| |
| // The element size must be align |
| hostVarElemSize = ALIGN(hostVarElemSize, |
| hostVarElemType->getDataAlignment()); |
| |
| // Assign a valueId for this Host variable. UnPackRows node will need |
| // this valueId during its binding. |
| //hostVar->synthTypeAndValueId(); |
| hostVar->bindNode(bindWA); |
| |
| ItemExpr *unPackCol = |
| new (bindWA->wHeap()) |
| RowsetArrayScan(hostVar, // Rowset Host Var array |
| indexHostVar, // Index |
| maxRowsetSize, // Cannot go over this size |
| hostVarElemSize, // Element size in bytes |
| hostVarElemNullInd, |
| hostVarElemType |
| ); |
| |
| // Construct a list of expressions to extract the Data value from |
| // the packed row. During normalization, this list (or a ValueIdList |
| // representing this list) will be reduced to the minimum required. |
| |
| // This should be a NULL terminated list, unfortunately, there are |
| // many parts in the SQL/MX code that loops over the arity instead |
| // of checking for NULL terminated list...the effect a segmentation |
| // violation. |
| |
| unPackExpr = (unPackExpr |
| ? new (bindWA->wHeap()) ItemList(unPackExpr, unPackCol) |
| : unPackCol); |
| |
| } |
| |
| // enable rowsetrowcount for rowset update and deletes |
| // if the user has not turned the feature OFF. |
| // if we have rowsets in where clause and are not in a select |
| // then we have either rowset ypdate or delete, for direct rowsets. |
| if (arrayArea && |
| (!(arrayArea->getHasDerivedRowsets())) && |
| arrayArea->hasHostArraysInWhereClause() && |
| (arrayArea->hasInputRowsetsInSelectPredicate() != HostArraysWA::YES_) && |
| (CmpCommon::getDefault(ROWSET_ROW_COUNT) == DF_ON)) { |
| arrayArea->setRowsetRowCountArraySize(maxRowsetSize); |
| } |
| |
| if (indexExpr_) { |
| /* |
| * Create and item expression to obtain the index |
| */ |
| ItemExpr *unPackCol = |
| new (bindWA->wHeap()) |
| RowsetArrayScan(indexHostVar, // Index |
| indexHostVar, // Index |
| maxRowsetSize, // Cannot go over this size |
| indexType->getTotalSize(), |
| 0, |
| indexType, |
| ITM_ROWSETARRAY_ROWID |
| ); |
| |
| unPackExpr = (unPackExpr |
| ? new (bindWA->wHeap()) ItemList(unPackExpr, unPackCol) |
| : unPackCol); |
| } |
| |
| // Now create a Tuple node to hang the children and input values of the |
| // actual Rowset Node to it. Make sure to copy the RelExpr part of Rowset |
| // to tuple. |
| |
| // Kludge up a dummy child for the index |
| ItemExpr *inputs = ((indexExpr_) |
| ? new (bindWA->wHeap()) ItemList(inputHostvars_, |
| indexHostVar) |
| : inputHostvars_); |
| |
| Tuple *tupleExpr = new (bindWA->wHeap()) Tuple(inputs); |
| tupleExpr->setBlockStmt(isinBlockStmt()); |
| |
| copyTopNode(tupleExpr); |
| |
| // Construct the replacement tree for the Rowset operator. |
| RelExpr *newSubTree = (new (bindWA->wHeap()) |
| UnPackRows(maxRowsetSize, |
| unPackExpr, |
| rowsetSizeExpr, |
| NULL, |
| tupleExpr, |
| indexHostVar->getValueId())); |
| |
| newSubTree->setBlockStmt(isinBlockStmt()); |
| |
| // do not set this flag for derived rowsets. This flag is used in generator to determine |
| // in onlj and TF TDB must set rownumber when encountering a execution time rowset error. |
| if (arrayArea && |
| (!(arrayArea->getHasDerivedRowsets())) && |
| (arrayArea->hasInputRowsetsInSelectPredicate() != HostArraysWA::YES_)) |
| { |
| newSubTree->setRowsetIterator(TRUE); |
| } |
| |
| // Move any predicate on the packed table to be on the result |
| // of unpacking. |
| newSubTree->addSelPredTree(removeSelPredTree()); |
| |
| // Remember the transform tree, just in case someone is trying to bind this |
| // node again. |
| transformRelexpr_ = newSubTree; |
| |
| // Bind the new generated subtree. |
| return newSubTree->bindNode(bindWA); |
| |
| } // Rowset::bindNode() |
| |
| RelExpr * RowsetRowwise::bindNode(BindWA* bindWA) |
| { |
| // If this node has already been bound, we are done. |
| if (nodeIsBound()) |
| return this->transformRelexpr_; |
| |
| if (bindWA->getHostArraysArea()) { |
| bindWA->getHostArraysArea()->done() = TRUE; |
| } |
| |
| // |
| // Bind the child nodes. |
| // |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Transform current node into a new subtree which performs access to |
| // RowSet based on the unpacking. |
| // UNPACK |
| // | |
| // TUPLE |
| // |
| |
| // We get the list of input host vars, which is stored in the root of the |
| // parse tree |
| HostArraysWA *arrayArea = bindWA->getHostArraysArea(); |
| |
| if ((arrayArea->rwrsMaxSize()->getOperatorType() != ITM_CONSTANT) || |
| (((ConstValue *)arrayArea->rwrsMaxSize())->getType()->getTypeQualifier() |
| != NA_NUMERIC_TYPE)) |
| { |
| // 30003 Rowset size must be an integer host variable or an |
| // integer constant |
| *CmpCommon::diags() << DgSqlCode(-30003); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // if partition number has been specified, then we don't unpack |
| // rows. The whole buffer is shipped to the specified partition. |
| if (arrayArea->partnNum()) |
| return child(0)->castToRelExpr(); |
| |
| Lng32 maxRowsetSize = |
| (Lng32)((ConstValue *)arrayArea->rwrsMaxSize())->getExactNumericValue() ; |
| |
| NAType * typ = new(bindWA->wHeap()) SQLInt(FALSE, FALSE); |
| ItemExpr * rwrsInputSizeExpr = |
| new(bindWA->wHeap()) Cast(arrayArea->inputSize(), typ); |
| if (bindWA->errStatus()) |
| return this; |
| |
| ItemExpr * rwrsMaxInputRowlenExpr = |
| new(bindWA->wHeap()) Cast(arrayArea->rwrsMaxInputRowlen(), typ); |
| if (bindWA->errStatus()) |
| return this; |
| |
| ItemExpr * rwrsBufferAddrExpr = arrayArea->rwrsBuffer(); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Construct the replacement tree for the Rowset operator. |
| RelExpr *newSubTree = (new (bindWA->wHeap()) |
| UnPackRows(maxRowsetSize, |
| rwrsInputSizeExpr, |
| rwrsMaxInputRowlenExpr, |
| rwrsBufferAddrExpr, |
| child(0))); |
| |
| // Remember the transform tree, just in case someone is trying to bind this |
| // node again. |
| transformRelexpr_ = newSubTree; |
| |
| // Bind the new generated subtree. |
| return newSubTree->bindNode(bindWA); |
| } // RowsetRowwise::bindNode() |
| |
| // LCOV_EXCL_START - rfi |
| RelExpr * RowsetFor::bindNode(BindWA* bindWA) |
| { |
| // Binding of this node should not happen. It should have been eliminated |
| // by now by the pre-binding step. Its content is used to populate the |
| // RowSet node with options. |
| |
| CMPASSERT(0); |
| return NULL; |
| } |
| // LCOV_EXCL_STOP |
| |
| RelExpr * RowsetInto::bindNode(BindWA* bindWA) |
| { |
| // If this node has already been bound, we are done. |
| if (nodeIsBound()) |
| return this->transformRelexpr_; |
| |
| // |
| // Bind the child nodes. |
| // |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| NABoolean hasDifferentSizes = FALSE; |
| Lng32 maxRowsetSize = 0; /* Maximum number of Rows in Rowset */ |
| ULng32 numOutputHostvars = 0; |
| |
| ItemExpr *rowsetSizeExpr; |
| ItemExpr *hostVarTree; |
| |
| // Do any extra checking at this moment. |
| for (hostVarTree = outputHostvars_; |
| hostVarTree; |
| hostVarTree = hostVarTree->child(1)) { |
| numOutputHostvars++; |
| CMPASSERT(hostVarTree->getOperatorType() == ITM_ITEM_LIST); |
| HostVar *hostVar = (HostVar *)hostVarTree->getChild(0); |
| |
| if (hostVar->getOperatorType() != ITM_HOSTVAR || |
| hostVar->getType()->getTypeQualifier() != NA_ROWSET_TYPE) { |
| // 30001 A rowset must be composed of host variable arrays |
| *CmpCommon::diags() << DgSqlCode(-30001); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // Get the smallest dimension for rowset size |
| |
| |
| SQLRowset* hostVarType = (SQLRowset *)hostVar->getType(); |
| |
| if (hostVarType->getNumElements() <= 0) { |
| // 30004 The dimesion of the arrays composing the RowSet must be greater |
| // than zero. A value of $0~Int0 was given |
| *CmpCommon::diags() << DgSqlCode(-30004) |
| << DgInt0((Int32)hostVarType->getNumElements()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (maxRowsetSize == 0) |
| maxRowsetSize = hostVarType->getNumElements(); |
| else if (hostVarType->getNumElements() != maxRowsetSize) { |
| // 30005 Warning: the dimensions of the arrays composing the RowSet are |
| // different. The smallest dimesnion is assumed. |
| // This is just a warning |
| |
| // Give the warning only once |
| if (hasDifferentSizes == FALSE) { |
| hasDifferentSizes = TRUE; |
| *CmpCommon::diags() << DgSqlCode(30005); |
| } |
| // Pick the smallest one |
| if (hostVarType->getNumElements() < maxRowsetSize) |
| maxRowsetSize = hostVarType->getNumElements(); |
| } |
| |
| // Make sure that the element type null indicator and the corresponding |
| // rowset array are both nullable or not nullable. That is, force it |
| |
| NAType* hostVarElemType = hostVarType->getElementType(); |
| NABoolean hostVarElemNullInd = !(hostVar->getIndName().isNull()); |
| |
| hostVarElemType->setNullable(hostVarElemNullInd); |
| } |
| |
| // If a rowset size expression was produced during parsing, it is used |
| // to restrict the rowset size during execution. The expression must be |
| // an numeric literal (known at compile time) or an integer host variable |
| // (known at execution time). We do not allow other type of expression |
| // since the rowset size must be know before the statement is executed to |
| // avoid copying a lot when the host variable arrays are sent down the |
| // execution queue |
| |
| if (sizeExpr_) { |
| if (sizeExpr_->getOperatorType() == ITM_CONSTANT) { |
| if (((ConstValue *)sizeExpr_)->getType()->getTypeQualifier() |
| != NA_NUMERIC_TYPE) { |
| // 30003 Rowset size must be an integer host variable or an |
| // integer constant |
| *CmpCommon::diags() << DgSqlCode(-30003); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (((ConstValue *)sizeExpr_)->getExactNumericValue() > maxRowsetSize) { |
| // 30002 The given RowSet size ($0~Int0) must be smaller or |
| // equal to the smallest dimension ($1Int1) of the |
| // arrays composing the rowset |
| *CmpCommon::diags() << DgSqlCode(-30002) |
| << DgInt0((Int32) |
| ((ConstValue *)sizeExpr_) |
| ->getExactNumericValue()) |
| << DgInt1(maxRowsetSize); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| else |
| if (!(sizeExpr_->getOperatorType() == ITM_HOSTVAR && |
| ((HostVar *)sizeExpr_)->getType()->getFSDatatype() |
| == REC_BIN32_SIGNED)) { |
| // 30003 Rowset size must be an integer host variable or an |
| // integer constant |
| *CmpCommon::diags() << DgSqlCode(-30003); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| rowsetSizeExpr = sizeExpr_; |
| } |
| else |
| rowsetSizeExpr = new (bindWA->wHeap()) SystemLiteral(maxRowsetSize); |
| |
| |
| if (getGroupAttr()->isEmbeddedUpdateOrDelete()){ |
| // 30020 Embedded update/delete cannot be used with SELECT...INTO and rowset. |
| *CmpCommon::diags() << DgSqlCode(-30020); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // Generate the RowsetArrayInto expressions which are used to append |
| // an element value to the rowset array. |
| |
| // Get RETDesc from its only child one which must be RelRoot type. |
| const RETDesc& childTable = *child(0)->getRETDesc(); |
| ValueIdList childTableVidList; |
| |
| childTable.getValueIdList(childTableVidList); |
| |
| if (numOutputHostvars != childTableVidList.entries()) { |
| // 4094 The number of output host vars ($0) must equal the number of cols |
| *CmpCommon::diags() << DgSqlCode(-4094) |
| #pragma nowarn(1506) // warning elimination |
| << DgInt0(numOutputHostvars) << DgInt1(childTableVidList.entries()); |
| #pragma warn(1506) // warning elimination |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| ItemExpr *packExpr = NULL; |
| Lng32 i; |
| |
| for (hostVarTree = outputHostvars_, i = 0; |
| hostVarTree; |
| hostVarTree = hostVarTree->child(1), i++) { |
| HostVar *hostVar = (HostVar *)hostVarTree->getChild(0); |
| |
| |
| SQLRowset* hostVarType = (SQLRowset *)hostVar->getType(); |
| NAType* hostVarElemType = hostVarType->getElementType(); |
| // hostVarElemType->setNullable(TRUE); |
| Lng32 hostVarElemSize = hostVarElemType->getTotalSize(); |
| NABoolean hostVarElemNullInd = !(hostVar->getIndName().isNull()); |
| ItemExpr* sourceExpr = childTableVidList[i].getItemExpr(); |
| |
| ValueId sourceId = childTableVidList[i]; |
| const NAType& targetType = *hostVarElemType; |
| sourceId.coerceType(targetType); |
| const NAType& sourceType = sourceId.getType(); |
| |
| NABoolean relaxCharTypeMatchingRule = FALSE; |
| |
| // We make sure that the types that are coming from below this |
| // node match properly with the types it has |
| if (NOT targetType.isCompatible(sourceType)) { |
| // JQ |
| // Relaxing Characet Data Type mismatching rule. |
| if ( targetType.getTypeQualifier() == NA_CHARACTER_TYPE && |
| sourceType.getTypeQualifier() == NA_CHARACTER_TYPE && |
| ((const CharType&)targetType).getCharSet() == CharInfo::UNICODE && |
| ((const CharType&)sourceType).getCharSet() == CharInfo::ISO88591 |
| ) |
| { |
| relaxCharTypeMatchingRule = TRUE; |
| } |
| |
| if ( !relaxCharTypeMatchingRule ) { |
| // Incompatible assignment from type $0~String0 to type $1~String1 |
| *CmpCommon::diags() << DgSqlCode(-30007) |
| << DgString0(sourceType.getTypeSQLname(TRUE /*terse*/)) |
| << DgString1(targetType.getTypeSQLname(TRUE /*terse*/)); |
| bindWA->setErrStatus(); |
| return FALSE; |
| } |
| } |
| |
| // Force all host variable to have the same number of elements which was |
| // found previously |
| hostVarType->setNumElements(maxRowsetSize); |
| |
| // The element size must be align |
| hostVarElemSize = ALIGN(hostVarElemSize, |
| hostVarElemType->getDataAlignment()); |
| |
| // Preserve the length that is coming from the node below this one |
| if (hostVarElemType->getTypeQualifier() == NA_CHARACTER_TYPE && |
| sourceType.getTypeQualifier() == NA_CHARACTER_TYPE) { |
| Int32 sourceSize = ((CharType *) &sourceType)->getDataStorageSize(); |
| Int32 targetSize = ((CharType *) hostVarElemType)->getDataStorageSize(); |
| if (sourceSize > targetSize ) { |
| // Adjust the layout size instead of changing the element size? |
| ((CharType *) hostVarElemType)->setDataStorageSize(sourceSize); |
| } |
| } |
| |
| if ( relaxCharTypeMatchingRule == TRUE ) |
| sourceExpr = new (bindWA->wHeap()) |
| Translate(sourceExpr, Translate::ISO88591_TO_UNICODE); |
| |
| // If the type is external (for instance, decimal or varchar), we must first |
| // convert to our internal equivalent type |
| if (hostVarElemType->isExternalType()) { |
| NAType *internalType = hostVarElemType->equivalentType(); |
| sourceExpr = new (bindWA->wHeap()) Cast(sourceExpr, internalType); |
| } |
| |
| sourceExpr = new (bindWA->wHeap()) Cast(sourceExpr, hostVarElemType); |
| |
| ItemExpr *packCol = |
| new (bindWA->wHeap()) |
| RowsetArrayInto(sourceExpr, |
| rowsetSizeExpr, // Runtime size |
| maxRowsetSize, // Cannot go over this size |
| hostVarElemSize, // Element size in bytes |
| hostVarElemNullInd, |
| hostVarType |
| ); |
| |
| // Construct a list of expressions to append the Data value to the |
| // RowSet array. This list should be a NULL terminated list, |
| // unfortunately, there are many parts in the SQL/MX code that |
| // loops over the arity instead of checking for NULL terminated |
| // list...the effect a segmentation violation. |
| |
| packExpr = (packExpr |
| ? new (bindWA->wHeap()) ItemList(packExpr, packCol) |
| : packCol); |
| } |
| |
| // Construct the replacement tree for the RowsetInto operator. |
| RelExpr *newSubTree = (new (bindWA->wHeap()) |
| Pack(maxRowsetSize, |
| child(0)->castToRelExpr(), |
| packExpr)); |
| |
| newSubTree->setFirstNRows(getFirstNRows()); |
| |
| // If we have an ORDER BY when there is an INTO :array, then we |
| // add the requirement that the tuples that this Pack node will |
| // receive must be sorted |
| |
| ValueIdList *ptrReqOrder; |
| |
| ptrReqOrder = new (bindWA->wHeap()) |
| ValueIdList(((RelRoot *) (RelExpr *) newSubTree->child(0))->reqdOrder()); |
| |
| ((Pack *) newSubTree)->setRequiredOrder(*ptrReqOrder); |
| |
| |
| // Remember the transform tree, just in case someone is trying to bind this |
| // node again. |
| transformRelexpr_ = newSubTree; |
| |
| // Bind the new generated subtree. |
| return newSubTree->bindNode(bindWA); |
| |
| } // RowsetInto::bindNode |
| |
| RelExpr * |
| IsolatedScalarUDF::bindNode (BindWA *bindWA) |
| { |
| |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // If we have a RoutineDesc, it means we got transformed from a |
| // a UDFunction ItemExpr, and do NOT need to check all the metadata |
| // params etc. |
| if (getRoutineDesc() == NULL ) |
| { |
| // If we get here, we created a IsolatedScalarUDF some other way |
| // than through the transformation of UDFunction. Either that or |
| // we have someone walking over our memory... |
| |
| CMPASSERT(0); |
| bindWA->setErrStatus(); |
| return this; |
| |
| |
| } |
| else |
| { |
| markAsBound(); |
| } |
| |
| |
| return this; |
| } // IsolatedScalarUDF::bindNode () |
| |
| /* |
| * This method performs binder functions for the CALLSP node |
| * It performs semantic checks on the called stored procedure |
| * creates a Tuple child and allocates ValueIds for the parameters |
| * It also provides support for the CLI layer processing for OUT |
| * parameter processing. |
| */ |
| RelExpr *CallSP::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| if (bindWA->getHoldableType() == SQLCLIDEV_ANSI_HOLDABLE) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4382); |
| bindWA->setErrStatus(); |
| bindWA->setBindingCall (FALSE); |
| return this; |
| } |
| |
| bindWA->setBindingCall (TRUE); |
| bindWA->setCurrOrdinalPosition (1); |
| bindWA->setCurrParamMode (COM_UNKNOWN_DIRECTION); |
| bindWA->clearHVorDPinSPDups (); |
| bindWA->setDupWarning (FALSE); |
| bindWA->setMaxResultSets(0); |
| |
| // try PUBLIC SCHEMA only when no schema was specified |
| // and CQD PUBLIC_DEFAULT_SCHEMA is specified |
| NAString pSchema = |
| ActiveSchemaDB()->getDefaults().getValue(PUBLIC_SCHEMA_NAME); |
| ComSchemaName pubSchema(pSchema); |
| NAString pubSchemaIntName = ""; |
| if ( (getRoutineName().getSchemaName().isNull()) && |
| (!pubSchema.getSchemaNamePart().isEmpty()) ) |
| { |
| pubSchemaIntName = pubSchema.getSchemaNamePart().getInternalName(); |
| } |
| |
| // Invoke GetNARoutine () to retrieve the corresponding NARoutine from |
| // NARoutineDB_ |
| QualifiedName name = getRoutineName(); |
| const SchemaName &defaultSchema = |
| bindWA->getSchemaDB ()->getDefaultSchema(); |
| name.applyDefaults(defaultSchema); |
| setRoutineName(name); |
| bindWA->setCurrSPName(&name); |
| |
| // in open source, only the SEABASE catalog is allowed. |
| // Return an error if some other catalog is being used. |
| if ((NOT name.isSeabase()) && (NOT name.isHive())) |
| { |
| *CmpCommon::diags() |
| << DgSqlCode(-1002) |
| << DgCatalogName(name.getCatalogName()); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| |
| CmpSeabaseDDL cmpSBD((NAHeap*)bindWA->wHeap()); |
| TrafDesc *catRoutine = |
| cmpSBD.getSeabaseRoutineDesc( |
| name.getCatalogName(), |
| name.getSchemaName(), |
| name.getObjectName()); |
| |
| // try public schema |
| if ( !catRoutine && |
| !pubSchemaIntName.isNull() ) |
| { |
| getRoutineName().setSchemaName(pubSchemaIntName); |
| if ( !pubSchema.getCatalogNamePart().isEmpty() ) |
| { |
| getRoutineName().setCatalogName(pubSchema.getCatalogNamePart().getInternalName()); |
| } |
| |
| // in open source, only the SEABASE catalog is allowed. |
| // Return an error if some other catalog is being used. |
| if ((NOT getRoutineName().isSeabase()) && (NOT getRoutineName().isHive())) |
| { |
| *CmpCommon::diags() |
| << DgSqlCode(-1002) |
| << DgCatalogName(getRoutineName().getCatalogName()); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| bindWA->resetErrStatus(); |
| catRoutine = |
| cmpSBD.getSeabaseRoutineDesc( |
| getRoutineName().getCatalogName(), |
| getRoutineName().getSchemaName(), |
| getRoutineName().getObjectName()); |
| if ( !bindWA->errStatus() && catRoutine ) |
| { // if found in public schema, do not show previous error |
| CmpCommon::diags()->clear(); |
| } |
| } |
| |
| if (bindWA->violateAccessDefaultSchemaOnly(getRoutineName())) |
| return NULL; |
| |
| if ( NULL == catRoutine ) |
| { |
| // Diagnostic error is set by the readRoutineDef, we just need to |
| // make sure the rest of the compiler knows that an error occurred. |
| bindWA->setBindingCall (FALSE); |
| bindWA->setErrStatus (); |
| return this; |
| } |
| |
| // Create a new NARoutine object |
| Int32 error = FALSE; |
| NARoutine *routine = new (bindWA->wHeap()) NARoutine ( getRoutineName(), |
| catRoutine, |
| bindWA, |
| error ); |
| |
| if ( bindWA->errStatus () ) |
| { |
| // Error |
| bindWA->setBindingCall (FALSE); |
| bindWA->setErrStatus (); |
| return this; |
| } |
| |
| NABoolean createRETDesc=TRUE; |
| RoutineDesc *rDesc = new (bindWA->wHeap()) RoutineDesc(bindWA, routine); |
| if (rDesc == NULL || bindWA->errStatus ()) |
| { |
| // Error |
| bindWA->setBindingCall (FALSE); |
| bindWA->setErrStatus (); |
| return this; |
| } |
| |
| if (rDesc->populateRoutineDesc(bindWA, createRETDesc) == FALSE ) |
| { |
| bindWA->setBindingCall (FALSE); |
| bindWA->setErrStatus (); |
| return this; |
| } |
| |
| setRoutineDesc(rDesc); |
| |
| |
| // |
| // Semantic checks |
| // |
| |
| // if in trigger and during DDL make sure to Fix up the name |
| // location list so that the name is fully qualified when stored |
| // in the TEXT metadata table |
| if ( bindWA->inDDL() && bindWA->isInTrigger () ) |
| { |
| ParNameLocList *pNameLocList = bindWA->getNameLocListPtr(); |
| if (pNameLocList) |
| { |
| ParNameLoc * pNameLoc |
| = pNameLocList->getNameLocPtr(getRoutineName().getNamePosition()); |
| CMPASSERT(pNameLoc); |
| pNameLoc->setExpandedName(getRoutineName().getQualifiedNameAsAnsiString()); |
| } |
| } |
| |
| // Cannot support result sets or out params when |
| // SP is invoked within a trigger |
| if ( bindWA->isInTrigger () && |
| getNARoutine()->hasOutParams ()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-UDR_BINDER_OUTPARAM_IN_TRIGGER) |
| << DgTableName (getRoutineName().getQualifiedNameAsString()); |
| bindWA->setErrStatus (); |
| bindWA->setBindingCall (FALSE); |
| return this; |
| } |
| |
| if ( bindWA->isInTrigger () && |
| getNARoutine()->hasResultSets ()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-UDR_BINDER_RESULTSETS_IN_TRIGGER) |
| << DgTableName (getRoutineName().getQualifiedNameAsString()); |
| bindWA->setErrStatus (); |
| bindWA->setBindingCall (FALSE); |
| return this; |
| } |
| |
| const NAColumnArray ¶ms = getNARoutine()->getParams (); |
| CollIndex i = 0; |
| CollIndex numParams = getNARoutine()->getParamCount (); |
| |
| CollIndex numSuppliedParams = countSuppliedParams (getRWProcAllParamsTree()); |
| if (numSuppliedParams != numParams) |
| { |
| *CmpCommon::diags() << DgSqlCode(-UDR_BINDER_INCORRECT_PARAM_COUNT) |
| << DgTableName(getRoutineName().getQualifiedNameAsString()) |
| << DgInt0((Lng32) numParams) |
| << DgInt1((Lng32) numSuppliedParams); |
| bindWA->setErrStatus (); |
| bindWA->setBindingCall (FALSE); |
| return this; |
| } |
| |
| short numResultSets = (short) getNARoutine()->getMaxResults(); |
| bindWA->setMaxResultSets(numResultSets); |
| |
| // On to the binding |
| // Invoke populateAndBindItemExpr, set up needed data structures |
| |
| // Set up a RETDesc if we don't already have one. |
| RETDesc *resultTable = getRETDesc(); |
| if (resultTable == NULL) |
| { |
| resultTable = new (bindWA->wHeap()) RETDesc(bindWA); |
| setRETDesc(resultTable); |
| } |
| |
| populateAndBindItemExpr ( getRWProcAllParamsTree(), |
| bindWA ); |
| if ( bindWA->errStatus ()) |
| { |
| bindWA->setBindingCall (FALSE); |
| return this; |
| } |
| |
| // Clear the Tree since we now have gotten vids for all the parameters. |
| setProcAllParamsTree(NULL); |
| |
| // Now fix the param index value of the dynamic params or host vars |
| LIST (ItemExpr *) &bWA_HVorDPs = bindWA->getSpHVDPs(); |
| CollIndex numHVorDPs = bWA_HVorDPs.entries(); |
| |
| ARRAY(ItemExpr *) local_HVorDPs(HEAP, numHVorDPs); |
| CollIndex idx, idx1, idx2; |
| |
| // Sort the ItemExpr in the order they appeared in the stmt |
| for (idx = 0; idx < numHVorDPs; idx++) |
| { |
| // Copy ItemExpr ptrs to a sorted Array. |
| local_HVorDPs.insertAt(bWA_HVorDPs[idx]->getHVorDPIndex() - 1, |
| bWA_HVorDPs[idx]); |
| } |
| |
| // The following code goes through the list of Exprs and |
| // sets index values. The rules are: |
| // 1. When a DP or HV is repeated, all of them get the same |
| // index value which is equal to the index of the first occurrence |
| // 2. Two DPs or HVs are same if their names and the modes are same. |
| Int32 currParamIndex = 1; |
| for (idx1 = 0; idx1 < numHVorDPs; idx1++) |
| { |
| ItemExpr *src = local_HVorDPs[idx1]; |
| const NAString &name1 = (src->getOperatorType() == ITM_HOSTVAR) ? |
| ((HostVar *)src)->getName() : ((DynamicParam *)src)->getName(); |
| ComColumnDirection mode1 = src->getParamMode(); |
| |
| NABoolean encounteredElement = FALSE; |
| |
| for (idx2 = idx1; idx2 < numHVorDPs; idx2++) |
| { |
| ItemExpr *dest = local_HVorDPs[idx2]; |
| |
| if (!encounteredElement && dest->getHVorDPIndex() >= currParamIndex) |
| { |
| // The parameter is encountered the first time |
| encounteredElement = TRUE; |
| dest->setPMOrdPosAndIndex(dest->getParamMode(), |
| dest->getOrdinalPosition(), |
| currParamIndex); |
| continue; |
| } |
| |
| // The parameter is already corrected |
| if (dest->getHVorDPIndex() < currParamIndex) |
| continue; |
| |
| const NAString &name2 = (dest->getOperatorType() == ITM_HOSTVAR) ? |
| ((HostVar *)dest)->getName() : ((DynamicParam *)dest)->getName(); |
| ComColumnDirection mode2 = dest->getParamMode(); |
| |
| if (name2.compareTo("") == 0) |
| continue; |
| |
| if (name1.compareTo(name2) == 0 && mode1 == mode2) |
| { |
| dest->setPMOrdPosAndIndex(dest->getParamMode(), |
| dest->getOrdinalPosition(), |
| currParamIndex); |
| } |
| } |
| |
| if (encounteredElement) |
| currParamIndex++; |
| } |
| |
| // Restore the bindWA's HVorDP list since it might be needed |
| // while binding the root node in case of HVs. |
| bindWA->clearHVorDPinSPDups(); |
| for (idx = 0; idx < numHVorDPs; idx++) |
| bindWA->addHVorDPToSPDups(local_HVorDPs[idx]); |
| |
| // Create a tuple child for any subqueries or UDF inputs |
| // The hasSubquery() / hasUDF() flag gets set in setInOrOutParam if any of our |
| // passed in parameters is a subquery. |
| |
| |
| if ((getProcInputParamsVids().entries() != 0) && |
| (hasSubquery() || hasUDF())) |
| { |
| Tuple *inTuple = new (bindWA->wHeap()) |
| Tuple(getProcInputParamsVids().rebuildExprTree(ITM_ITEM_LIST), |
| bindWA->wHeap()); |
| |
| if ( inTuple ) |
| { |
| // Now set and bind the Tuple child |
| setChild (0, inTuple); |
| |
| // Bind this Tuple child |
| inTuple->bindNode (bindWA); |
| if ( bindWA->errStatus ()) |
| { |
| bindWA->setBindingCall (FALSE); |
| return this; |
| } |
| |
| // Get each IN entry from the Tuple and put it in |
| //the super's list |
| |
| // Need to clear the list to avoid duplicates |
| getProcInputParamsVids().clear(); |
| |
| // Now reinitialize the inputs based on the Tuple processing. |
| inTuple->getRETDesc ()->getValueIdList (getProcInputParamsVids()); |
| |
| } // if inTuple |
| else |
| { |
| // Out of memory ... |
| bindWA->setBindingCall (FALSE); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } // if getProcInputParamVids().entries() |
| else |
| { |
| |
| // If we dont have a subquery parameter, we dont need to go thru |
| // Optimization time rules and transformations, hence mark this |
| // as a physical node. |
| |
| isPhysical_ = TRUE; |
| } |
| |
| |
| // |
| // Not sure whether we need to set the currently scoped RETDesc |
| // before binding the base class. Tuple::bindNode() does not do it |
| // so we won't either (for now) |
| // |
| //bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| |
| // add the routine to the UdrStoiList. The UdrStoi list is used |
| // to check valid privileges |
| LIST(OptUdrOpenInfo *) udrList = bindWA->getUdrStoiList (); |
| ULng32 numUdrs = udrList.entries(); |
| NABoolean udrReferenced = FALSE; |
| |
| // See if UDR already exists |
| for (ULng32 stoiIndex = 0; stoiIndex < numUdrs; stoiIndex++) |
| { |
| if ( 0 == |
| udrList[stoiIndex]->getUdrName().compareTo( |
| getRoutineName().getQualifiedNameAsAnsiString() |
| ) |
| ) |
| { |
| udrReferenced = TRUE; |
| break; |
| } |
| } |
| |
| // UDR has not been defined, go ahead an add one |
| if ( FALSE == udrReferenced ) |
| { |
| SqlTableOpenInfo *udrStoi = new (bindWA->wHeap ())SqlTableOpenInfo (); |
| udrStoi->setAnsiName ( convertNAString( |
| getRoutineName().getQualifiedNameAsAnsiString(), |
| bindWA->wHeap ()) |
| ); |
| |
| OptUdrOpenInfo *udrOpenInfo = new (bindWA->wHeap ()) |
| OptUdrOpenInfo( udrStoi |
| , getRoutineName().getQualifiedNameAsAnsiString() |
| , (NARoutine *)getNARoutine() |
| ); |
| bindWA->getUdrStoiList().insert(udrOpenInfo); |
| } |
| |
| |
| // |
| // Bind the base class |
| // |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) |
| { |
| bindWA->setBindingCall (FALSE); |
| return boundExpr; |
| } |
| |
| // Our characteristic inputs get set for us, we don't need to do it |
| // ourselves, however, we need to set our characteristic outputs |
| getGroupAttr()->addCharacteristicOutputs(getProcOutputParamsVids()); |
| |
| if (getNARoutine()->isProcedure()) |
| bindWA->setHasCallStmts(TRUE); |
| |
| bindWA->setBindingCall (FALSE); |
| return boundExpr; |
| } // CallSP::bindNode() |
| |
| |
| |
| // This is the main entry point to walking the ItemExpr tree built by the |
| // parser, separating the IN and OUT parameters, setting appropriate |
| // characteristics of the IN/OUT parameters and binding them |
| // Currently only CallSP uses this code. If this routine needs to be shared |
| void IsolatedNonTableUDR::populateAndBindItemExpr ( ItemExpr *param, |
| BindWA *bindWA ) |
| { |
| // This method is called recursively |
| CollIndex numParams = getEffectiveNARoutine()->getParamCount (); |
| CollIndex ordinalPosition = bindWA->getCurrOrdinalPosition (); |
| |
| // No parameters, or we are done with the leaf node |
| if ( NULL == param ) |
| { |
| return; |
| } |
| |
| ComColumnDirection mode = |
| getEffectiveNARoutine()->getParams()[ordinalPosition-1]->getColumnMode (); |
| |
| // This is the structure of the ItemExpr tree |
| // For 1 param |
| // ItemExpr |
| // |
| // 2 params |
| // ItemList |
| // / \ |
| // Param1 Param2 |
| // |
| // > 2 params |
| // ItemList |
| // / \ |
| // Param1 ItemList |
| // / \ |
| // Param2 ItemList |
| // ... ... |
| // ... ... |
| // / / \ |
| // Param (N-2) / \ |
| // / \ |
| // Param(N-1) Param(N) |
| if ( ITM_ITEM_LIST == param->getOperatorType ()) |
| { |
| // Use left child |
| CMPASSERT ((ItemExpr *) NULL != (*param).child (0)); |
| |
| populateAndBindItemExpr ( (*param).child(0), |
| bindWA ); |
| |
| if ( bindWA->errStatus ()) |
| return; |
| |
| // Now for the right child |
| CMPASSERT ((ItemExpr *) NULL != (*param).child (1)); |
| populateAndBindItemExpr ( (*param).child(1), |
| bindWA ); |
| |
| return; |
| |
| } // if ITM_ITEM_LIST == param->getOperatorType () |
| |
| // For all leaf nodes we must come here (see the recursive call to |
| // populateAndBindItemExp above) |
| // Set the bindWA's current ordinal position and parameter mode |
| // Let HV and DynamicParam's bindNode take care of the |
| // settings. To ensure this, do a bindNode here |
| bindWA->setCurrParamMode (mode); |
| param->bindNode (bindWA); |
| if (bindWA->errStatus ()) |
| return; |
| |
| // Add the IN or OUT params to their respective lists |
| // and also create and bind a new ItemExpr for INOUT and OUT |
| // params. |
| // Also bump up the ordinalPosition count since we are done with this |
| // parameter. |
| setInOrOutParam (param,/* ordinalPosition,*/ mode, bindWA); |
| if ( bindWA->errStatus ()) |
| return; |
| |
| bindWA->setCurrOrdinalPosition (bindWA->getCurrOrdinalPosition () + 1); |
| |
| } // PopulateAndBindItemExpr |
| |
| |
| |
| // LCOV_EXCL_START - rfi |
| void |
| IsolatedNonTableUDR::setInOrOutParam (ItemExpr *expr, |
| ComColumnDirection paramMode, |
| BindWA *bindWA) |
| { |
| // Should not get here.. |
| CMPASSERT(FALSE); |
| } |
| // LCOV_EXCL_STOP |
| |
| |
| // This method separates the IN and OUT parameters Each IN/INOUT param |
| // is cast to the formal type (from NARoutine). This Cast'ed item expr |
| // is added to an ItemList tree to be passed to the Tuple () |
| // constructor. For each OUT/INOUT, we create a NATypeToItem |
| // ItemExpr, bind it and add it to procOutParams_. |
| // |
| // This method is called once for each CALL statement argument. If an |
| // input argument to a CALL is an expression tree such as "? + ?" or |
| // "abs(:x)" then this method is called once for the entire tree. |
| // |
| // Side Effects: OUT: hasSubquery_ |
| // neededValueIds_ |
| // procAllParamsVids_ |
| // procInputParamsVids_ |
| // procOutputParamsVids_ |
| void CallSP::setInOrOutParam ( ItemExpr *expr, |
| ComColumnDirection paramMode, |
| BindWA *bindWA) |
| { |
| |
| // Depending on whether this is an IN or OUT parameter, we need to |
| // take different actions. |
| |
| // For an IN (and INOUT) param, do the following |
| // Cast the parameter to its formal type and add it to the list of |
| // IN params. This will be used later to create a Tuple child and |
| // be bound by the Tuple itself |
| CollIndex ordinalPosition = bindWA->getCurrOrdinalPosition (); |
| |
| const NAColumnArray &formalParams = getNARoutine()->getParams(); |
| NAColumn &naColumn = *(formalParams[ordinalPosition-1]); |
| const NAType ¶mType = *(naColumn.getType()); |
| |
| // Don't really want to bind this, but how else can we |
| // get the ItemExpr's type |
| ItemExpr *boundExpr = expr->bindNode (bindWA); |
| if ( bindWA->errStatus ()) |
| { |
| return; |
| } |
| |
| |
| |
| //10-061031-0188-Begin |
| //Need to infer charset for string literals part of CALLSP |
| //parameters |
| ValueId inputTypeId = boundExpr->getValueId(); |
| |
| if(inputTypeId.getType().getTypeQualifier() == NA_CHARACTER_TYPE) |
| { |
| const CharType* stringLiteral = (CharType*)&(inputTypeId.getType()); |
| |
| if(CmpCommon::wantCharSetInference()) |
| { |
| const CharType* desiredType = |
| CharType::findPushDownCharType(((CharType&)paramType).getCharSet(), stringLiteral, 0); |
| |
| if ( desiredType ) |
| inputTypeId.coerceType((NAType&)*desiredType, NA_CHARACTER_TYPE); |
| } |
| } |
| |
| NABoolean throwInTranslateNode = FALSE; |
| CharInfo::CharSet paramCS = CharInfo::UnknownCharSet; |
| CharInfo::CharSet inputCS = CharInfo::UnknownCharSet; |
| |
| const NABoolean isJdbc = |
| (CmpCommon::getDefault(JDBC_PROCESS) == DF_ON ? TRUE : FALSE); |
| const NABoolean isOdbc = |
| (CmpCommon::getDefault(ODBC_PROCESS) == DF_ON ? TRUE : FALSE); |
| |
| const NAType &inputType = inputTypeId.getType(); |
| //10-061031-0188-End |
| |
| if ( COM_INPUT_COLUMN == paramMode || |
| COM_INOUT_COLUMN == paramMode ) |
| { |
| // If this input argument to the CALL is a single dynamic param |
| // then we want to record the formal parameter name. It will later |
| // be written into the query plan by the code generator and |
| // eventually if this CALL statement is DESCRIBEd, the formal |
| // param name gets returned in the SQLDESC_NAME descriptor entry. |
| if (expr->getOperatorType() == ITM_DYN_PARAM) |
| { |
| DynamicParam *dp = (DynamicParam *) expr; |
| dp->setUdrFormalParamName(naColumn.getColName()); |
| } |
| |
| // Check to see if we have a Subquery as an input |
| if ( !hasSubquery() ) |
| hasSubquery() = expr->containsSubquery (); |
| |
| |
| // Check to see if we have a UDF as an input |
| if ( !hasUDF() ) |
| hasUDF() = (expr->containsUDF () != NULL); |
| |
| // Do type checking, |
| // If it is not a compatible type report an error |
| if (!( NA_UNKNOWN_TYPE == inputType.getTypeQualifier () || |
| paramType.isCompatible(inputType) || |
| expr->getOperatorType () == ITM_DYN_PARAM |
| ) |
| ) |
| { |
| if ( inputType.getTypeQualifier() == NA_CHARACTER_TYPE ) |
| { |
| paramCS = ((CharType&)paramType).getCharSet(); |
| inputCS = ((CharType&)inputType).getCharSet(); |
| |
| NABoolean CS_unknown = (paramCS == CharInfo::UnknownCharSet) || |
| (inputCS == CharInfo::UnknownCharSet) ; |
| if ( paramType.NAType::isCompatible(inputType) && |
| paramCS != inputCS && |
| CS_unknown == FALSE && |
| CmpCommon::getDefault(ALLOW_IMPLICIT_CHAR_CASTING) == DF_ON |
| ) |
| throwInTranslateNode = TRUE; |
| } |
| if ( throwInTranslateNode == FALSE ) |
| { |
| // Error, data types dont match |
| #pragma nowarn(1506) // warning elimination |
| *CmpCommon::diags() << DgSqlCode(-UDR_BINDER_PARAM_TYPE_MISMATCH) |
| << DgInt0 (ordinalPosition) |
| << DgTableName(getRoutineName().getQualifiedNameAsString()) |
| << DgString0 (inputType.getTypeSQLname (TRUE)) |
| << DgString1 (paramType.getTypeSQLname (TRUE)); |
| #pragma warn(1506) // warning elimination |
| bindWA->setErrStatus (); |
| return; |
| } |
| } // if NOT isCompatible |
| |
| // Create a Cast node if the types are not identical |
| if (! (inputType == paramType)) |
| { |
| // First create a Translate node if the character sets are not identical |
| |
| if ( throwInTranslateNode ) |
| { |
| Int32 tran_type = find_translate_type( inputCS, paramCS ); |
| |
| ItemExpr * newTranslateChild = |
| new (bindWA->wHeap()) Translate(boundExpr, tran_type ); |
| boundExpr = newTranslateChild->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return; |
| // NOTE: Leave "expr" at it's old value as code below needs to check |
| // that original ItemExpr rather than the new Translate node. |
| } |
| Cast *retExpr = new (bindWA->wHeap()) |
| Cast(boundExpr, ¶mType, ITM_CAST, TRUE); |
| |
| boundExpr = retExpr->bindNode (bindWA); |
| if ( bindWA->errStatus ()) |
| { |
| return; |
| } |
| } |
| // Fill the ValueIdList for all the params |
| getProcAllParamsVids().insert( boundExpr->getValueId()); |
| |
| // Fill the ValueIdList for Input params |
| getProcInputParamsVids().insert( boundExpr->getValueId()); |
| |
| } // if INPUT or INOUT |
| |
| // For OUT (and INOUT) parameters, we create a NATypeToItem object, |
| // bind it and add it to the list of OUT parameters (procOutParams_) |
| if ( COM_OUTPUT_COLUMN == paramMode || |
| COM_INOUT_COLUMN == paramMode ) |
| { |
| if (!( ITM_HOSTVAR == expr->getOperatorType () || |
| ITM_DYN_PARAM == expr->getOperatorType ())) |
| { |
| #pragma nowarn(1506) // warning elimination |
| *CmpCommon::diags() << DgSqlCode(-UDR_BINDER_OUTVAR_NOT_HV_OR_DP) |
| << DgInt0(ordinalPosition) |
| << DgTableName(getRoutineName().getQualifiedNameAsString()); |
| #pragma warn(1506) // warning elimination |
| bindWA->setErrStatus (); |
| return; |
| } // if NOT HOSTVAR or DYNAMIC PARAM |
| |
| NATypeToItem *paramTypeItem = new (bindWA->wHeap()) |
| NATypeToItem (naColumn.mutateType()); |
| ItemExpr *outputExprToBind = NULL; |
| outputExprToBind = paramTypeItem->bindNode (bindWA); |
| if ( bindWA->errStatus ()) |
| { |
| return; |
| } |
| |
| |
| |
| // Fill the ValueIdList for all the params |
| getProcAllParamsVids().insert( outputExprToBind->getValueId()); |
| |
| // Fill the ValueIdList for the output params |
| addProcOutputParamsVid(outputExprToBind->getValueId ()); |
| |
| // |
| // Populate our RETDesc |
| // |
| // It has already been alocated |
| RETDesc *resultTable = getRETDesc(); |
| |
| const NAString &formalParamName = naColumn.getColName(); |
| const NAString *colParamName = &formalParamName; |
| |
| // Set the userParamName |
| const NAString &userParamName = |
| // cannot use the boundExpr here as it will be a cast() |
| // for the HostVar or DynamicParam. Use the original |
| // ItemExpr pointer instead. |
| (ITM_HOSTVAR == expr->getOperatorType()) ? |
| ((HostVar *)expr)->getName() : |
| ((DynamicParam *)expr)->getName(); |
| |
| |
| // Typically the name for this output column will be the formal |
| // parameter name. Exceptions: |
| // - No formal name was specified in the CREATE PROCEDURE. Use |
| // the (possibly empty) dynamic parameter or host variable name |
| // instead. |
| // - This is a JDBC or ODBC compile and the client is using a |
| // named host variable or dynamic parameter. JDBC and ODBC want |
| // us to use the client's name in this case. |
| |
| if (formalParamName.isNull() || |
| (!userParamName.isNull() && (isJdbc || isOdbc))) |
| { |
| colParamName = &userParamName; |
| } |
| |
| ColRefName *columnName = |
| new (bindWA->wHeap()) |
| ColRefName(*colParamName, bindWA->wHeap()); |
| |
| resultTable->addColumn(bindWA, *columnName, outputExprToBind->getValueId()); |
| |
| // |
| // We need the following line for static cursor declaration, |
| // according to a comment in bindRowValues() |
| // |
| cmpCurrentContext->saveRetrievedCols_ = resultTable->getDegree(); |
| |
| } // if OUTPUT or INOUT |
| |
| } // setInOrOutParam |
| |
| |
| |
| |
| |
| CollIndex RelRoutine::countSuppliedParams (ItemExpr *tree) const |
| { |
| CollIndex numParams=0; |
| |
| if ( !tree ) return 0; |
| |
| if (ITM_ITEM_LIST == tree->getOperatorType ()) |
| { |
| numParams += countSuppliedParams (tree->child (0)); |
| numParams += countSuppliedParams (tree->child (1)); |
| } |
| else |
| numParams++; |
| |
| return numParams; |
| |
| } // RelRoutine::countSuppliedParams |
| |
| void RelRoutine::gatherParamValueIds (const ItemExpr *tree, ValueIdList ¶msList) const |
| { |
| if ( !tree ) return; |
| |
| if (ITM_ITEM_LIST == tree->getOperatorType ()) |
| { |
| gatherParamValueIds (tree->child (0), paramsList); |
| gatherParamValueIds (tree->child (1), paramsList); |
| } |
| else |
| paramsList.insert(tree->getValueId()); |
| } // RelRoutine::gatherParamValueIds |
| |
| void ProxyFunc::createProxyFuncTableDesc(BindWA *bindWA, CorrName &corrName) |
| { |
| // Map column definitions into a TrafDesc |
| TrafDesc *tableDesc = createVirtualTableDesc(); |
| |
| // Map the TrafDesc into an NATable. This will also add an |
| // NATable entry into the bindWA's NATableDB. |
| NATable *naTable = |
| bindWA->getNATable(corrName, FALSE /*catmanUsages*/, tableDesc); |
| if (bindWA->errStatus()) |
| return; |
| |
| // Allocate a TableDesc and attach it to this RelExpr instance |
| setTableDesc(bindWA->createTableDesc(naTable, corrName)); |
| if (bindWA->errStatus()) |
| return; |
| |
| // Allocate a RETDesc and attach it to this and the BindScope |
| setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA, getTableDesc())); |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| } |
| |
| |
| RelExpr *ProxyFunc::bindNode(BindWA *bindWA) |
| { |
| // This method now serves as a common bind node for SPProxy and |
| // ExtractSource classes, where we before had SPProxyFunc::bindNode() |
| // and ExtractSource::bindNode(). |
| |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // Bind the child nodes |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Declare a correlation name that is unique within this query |
| switch (getOperatorType()) |
| { |
| case REL_EXTRACT_SOURCE: |
| virtualTableName_ = "EXTRACT_SOURCE_"; |
| break; |
| case REL_SP_PROXY: |
| virtualTableName_ = "SP_RESULT_SET_"; |
| break; |
| default: |
| CMPASSERT(0); |
| break; |
| } |
| |
| virtualTableName_ += bindWA->fabricateUniqueName(); |
| CorrName corrName(getVirtualTableName()); |
| corrName.setSpecialType(ExtendedQualName::VIRTUAL_TABLE); |
| |
| createProxyFuncTableDesc(bindWA, corrName); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Bind the base class |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| |
| // Assign the set of columns that belong to the virtual table |
| // as the output values that can be produced by this node. |
| getGroupAttr()->addCharacteristicOutputs(getTableDesc()->getColumnList()); |
| |
| return boundExpr; |
| |
| } // ProxyFunc::bindNode() |
| |
| RelExpr *TableMappingUDF::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| // Create NARoutine object (no caching for TMUDF) |
| NARoutine *tmudfRoutine =NULL; |
| CorrName& tmfuncName = getUserTableName(); |
| tmfuncName.setSpecialType(ExtendedQualName::VIRTUAL_TABLE); |
| |
| |
| QualifiedName name = getRoutineName(); |
| const SchemaName &defaultSchema = |
| bindWA->getSchemaDB ()->getDefaultSchema(); |
| name.applyDefaults(defaultSchema); |
| setRoutineName(name); |
| |
| // Return an error if an unsupported catalog is being used. |
| if ((NOT name.isSeabase()) && (NOT name.isHive())) |
| { |
| *CmpCommon::diags() |
| << DgSqlCode(-1002) |
| << DgCatalogName(name.getCatalogName()); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| Lng32 diagsMark = CmpCommon::diags()->mark(); |
| NABoolean errStatus = bindWA->errStatus(); |
| |
| tmudfRoutine = getRoutineMetadata(name, tmfuncName, bindWA); |
| if (tmudfRoutine == NULL) |
| { |
| // this could be a predefined TMUDF, which is not |
| // recorded in the metadata at this time |
| OperatorTypeEnum opType = |
| PredefinedTableMappingFunction::nameIsAPredefinedTMF(tmfuncName); |
| |
| if (opType != REL_ANY_TABLE_MAPPING_UDF) |
| { |
| // yes, this is a predefined TMUDF |
| PredefinedTableMappingFunction *result; |
| |
| // discard the errors from the failed name lookup |
| CmpCommon::diags()->rewind(diagsMark); |
| if (!errStatus) |
| bindWA->resetErrStatus(); |
| |
| // create a new RelExpr |
| result = new(bindWA->wHeap()) |
| PredefinedTableMappingFunction( |
| getArity(), |
| tmfuncName, |
| const_cast<ItemExpr *>(getProcAllParamsTree()), |
| opType); |
| |
| // copy data members of the base classes |
| TableMappingUDF::copyTopNode(result); |
| |
| // set children |
| result->setArity(getArity()); |
| for (int i=0; i<getArity(); i++) |
| result->child(i) = child(i); |
| |
| // Abandon the current node and return the bound new node. |
| // Next time it will reach this method it will call an |
| // overloaded getRoutineMetadata() that will succeed. |
| return result->bindNode(bindWA); |
| } |
| |
| // getRoutineMetadata has already set the diagnostics area |
| // and set the error status |
| CMPASSERT(bindWA->errStatus()); |
| return NULL; |
| } |
| |
| // Bind the child nodes. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Use information from child to populate childInfo_ |
| NAHeap *heap = CmpCommon::statementHeap(); |
| for(Int32 i = 0; i < getArity(); i++) |
| { |
| NAString childName(heap); |
| NAColumnArray childColumns(heap) ; |
| RETDesc *childRetDesc = child(i)->getRETDesc(); |
| |
| // Get Name |
| LIST(CorrName*) nameList(STMTHEAP); |
| childRetDesc->getXTNM().dumpKeys(nameList); |
| if (nameList.entries() == 1) |
| { |
| childName = (nameList[0])->getExposedNameAsString(); |
| } |
| else |
| { |
| childName = "_inputTable" + bindWA->fabricateUniqueName(); |
| } |
| |
| // ask for histograms of all child outputs, since we don't |
| // know what the UDF will need and what predicates exist |
| // on passthru columns of the UDF |
| bindWA->getCurrentScope()->context()->inWhereClause() = TRUE; |
| |
| // Get NAColumns |
| |
| CollIndex numChildCols = childRetDesc->getColumnList()->entries(); |
| for(CollIndex j=0; j < numChildCols; j++) |
| { |
| NAColumn * childCol = new (heap) NAColumn( |
| childRetDesc->getColRefNameObj(j).getColName().data(), |
| j, |
| childRetDesc->getType(j).newCopy(heap), |
| heap); |
| childColumns.insert(childCol); |
| |
| bindWA->markAsReferencedColumn(childRetDesc->getValueId(j)); |
| } |
| bindWA->getCurrentScope()->context()->inWhereClause() = FALSE; |
| |
| // get child root |
| CMPASSERT(child(i)->getOperator().match(REL_ROOT) || |
| child(i)->getOperator().match(REL_RENAME_TABLE)); |
| RelRoot * myChild; |
| if (child(i)->getOperator().match(REL_RENAME_TABLE)) |
| myChild = (RelRoot *) (child(i)->child(0).getPtr()); |
| else |
| myChild = (RelRoot *) child(i).getPtr(); |
| |
| // output vidList from child RetDesc, |
| // can also get from child Root compExpr |
| ValueIdList vidList; |
| childRetDesc->getValueIdList(vidList, USER_COLUMN); |
| ValueIdSet childPartition(myChild->partitionArrangement()); |
| ValueIdList childOrder(myChild->reqdOrder()); |
| |
| // request multi-column histograms for the PARTITION BY columns |
| bindWA->getCurrentScope()->context()->inGroupByClause() = TRUE; |
| |
| // replace 1-based ordinals in the child's partition by / order by with |
| // actual columns |
| for (ValueId cp=childPartition.init(); |
| childPartition.next(cp); |
| childPartition.advance(cp)) |
| { |
| NABoolean negate; |
| ConstValue *cv = cp.getItemExpr()->castToConstValue(negate); |
| |
| if (cv && |
| cv->canGetExactNumericValue()) |
| { |
| Lng32 scale = 0; |
| Int64 ordinal = cv->getExactNumericValue(scale); |
| |
| if (!negate && scale == 0 && ordinal >= 1 && ordinal <= vidList.entries()) |
| { |
| // remove this ValueId from the set and add the corresponding |
| // column value. Note that this won't cause problems with the |
| // iterator through the set, since we don't need to apply |
| // this conversion on the new element we are inserting |
| childPartition -= cp; |
| childPartition += vidList[ordinal-1]; |
| } |
| else |
| { |
| *CmpCommon::diags() |
| << DgSqlCode(-11154) |
| << DgInt0(ordinal) |
| << DgString0("PARTITION BY") |
| << DgInt1(vidList.entries()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| bindWA->markAsReferencedColumn(cp); |
| } |
| bindWA->getCurrentScope()->context()->inGroupByClause() = FALSE; |
| |
| for (CollIndex co=0; co<childOrder.entries(); co++) |
| { |
| NABoolean negate; |
| ItemExpr *ie = childOrder[co].getItemExpr(); |
| ConstValue *cv = NULL; |
| |
| if (ie->getOperatorType() == ITM_INVERSE) |
| ie = ie->child(0); |
| cv = ie->castToConstValue(negate); |
| |
| if (cv && |
| cv->canGetExactNumericValue()) |
| { |
| Lng32 scale = 0; |
| Int64 ordinal = cv->getExactNumericValue(scale); |
| |
| // replace the const value with the actual column |
| if (!negate && scale == 0 && ordinal >= 1 && ordinal <= vidList.entries()) |
| if (ie == childOrder[co].getItemExpr()) |
| { |
| // ascending order |
| childOrder[co] = vidList[ordinal-1]; |
| } |
| else |
| { |
| // desc order, need to add an InverseOrder on top |
| ItemExpr *inv = new(bindWA->wHeap()) InverseOrder( |
| vidList[ordinal-1].getItemExpr()); |
| inv->synthTypeAndValueId(); |
| childOrder[co] = inv->getValueId(); |
| } |
| else |
| { |
| *CmpCommon::diags() |
| << DgSqlCode(-11154) |
| << DgInt0(ordinal) |
| << DgString0("ORDER BY") |
| << DgInt1(vidList.entries()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| } |
| |
| TableMappingUDFChildInfo * cInfo = new (heap) TableMappingUDFChildInfo( |
| childName, |
| childColumns, |
| myChild->getPartReqType(), |
| childPartition, |
| childOrder, |
| vidList); |
| childInfo_.insert(cInfo); |
| } |
| |
| RoutineDesc *tmudfRoutineDesc = new (bindWA->wHeap()) RoutineDesc(bindWA, tmudfRoutine); |
| if (tmudfRoutineDesc == NULL || bindWA->errStatus ()) |
| { |
| // Error |
| bindWA->setBindingCall (FALSE); |
| bindWA->setErrStatus (); |
| return this; |
| } |
| setRoutineDesc(tmudfRoutineDesc); |
| // xcnm will be empty because the routineDesc does not contain any |
| // output columns yet |
| RETDesc *rDesc = new (bindWA->wHeap()) RETDesc(bindWA, tmudfRoutineDesc); |
| bindWA->getCurrentScope()->setRETDesc(rDesc); |
| setRETDesc(rDesc); |
| |
| dllInteraction_ = new (bindWA->wHeap()) TMUDFDllInteraction(); |
| |
| // ValueIDList of the actual input parameters |
| // (tmudfRoutine has formal parameters) |
| if (getProcAllParamsTree() && (getProcAllParamsVids().isEmpty() == TRUE)) |
| { |
| ((ItemExpr *)getProcAllParamsTree())->convertToValueIdList( |
| getProcAllParamsVids(), bindWA, ITM_ITEM_LIST); |
| if (bindWA->errStatus()) return NULL; |
| |
| // Clear the Tree since we now have gotten vids for all the parameters. |
| setProcAllParamsTree(NULL); |
| } |
| getProcInputParamsVids().insert(getProcAllParamsVids()); |
| |
| // invoke the optional UDF compiler interface or a default |
| // implementation to validate scalar inputs and produce a list of |
| // output columns |
| |
| NABoolean status = dllInteraction_->describeParamsAndMaxOutputs(this, bindWA); |
| if (!status) |
| { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| checkAndCoerceScalarInputParamTypes(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| createOutputVids(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| // create a ValueIdMap that allows us to translate |
| // output columns that are passed through back to |
| // input columns (outputs of the child), this can |
| // be used to push down predicates, translate |
| // required order and partitioning, etc. |
| status = dllInteraction_->createOutputInputColumnMap( |
| this, |
| udfOutputToChildInputMap_); |
| if (!status) |
| { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // if this is a maintenance-type operation that must run on |
| // all nodes of the cluster or must run in parallel, regardless |
| // of the ATTEMPT_ESP_PARALLELISM CQD, then set a flag in the |
| // root node |
| if (getOperatorType() == REL_TABLE_MAPPING_BUILTIN_LOG_READER) |
| bindWA->getTopRoot()->setMustUseESPs(TRUE); |
| |
| // add the routine to the UdrStoiList. The UdrStoi list is used |
| // to check valid privileges |
| LIST(OptUdrOpenInfo *) udrList = bindWA->getUdrStoiList (); |
| ULng32 numUdrs = udrList.entries(); |
| NABoolean udrReferenced = FALSE; |
| |
| // See if UDR already exists |
| for (ULng32 stoiIndex = 0; stoiIndex < numUdrs; stoiIndex++) |
| { |
| if ( 0 == |
| udrList[stoiIndex]->getUdrName().compareTo( |
| getRoutineName().getQualifiedNameAsAnsiString() |
| ) |
| ) |
| { |
| udrReferenced = TRUE; |
| break; |
| } |
| } |
| |
| // UDR has not been defined, go ahead an add one |
| if ( FALSE == udrReferenced ) |
| { |
| SqlTableOpenInfo *udrStoi = new (bindWA->wHeap ())SqlTableOpenInfo (); |
| udrStoi->setAnsiName ( convertNAString( |
| getRoutineName().getQualifiedNameAsAnsiString(), |
| bindWA->wHeap ()) |
| ); |
| |
| OptUdrOpenInfo *udrOpenInfo = new (bindWA->wHeap ()) |
| OptUdrOpenInfo( udrStoi |
| , getRoutineName().getQualifiedNameAsAnsiString() |
| , (NARoutine *)getNARoutine() |
| ); |
| bindWA->getUdrStoiList().insert(udrOpenInfo); |
| } |
| |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| return boundExpr; |
| } |
| |
| RelExpr * FastExtract::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| |
| // check validity of target location |
| if (getTargetType() == FILE) |
| { |
| char reasonMsg[256]; |
| NABoolean raiseError = FALSE; |
| if ((unsigned char)(getTargetName().data()[0]) != SLASH_C) |
| { |
| raiseError = TRUE; |
| sprintf(reasonMsg,"Relative path name was used"); |
| } |
| else if (getTargetName().length() > 512) |
| { |
| raiseError = TRUE; |
| sprintf(reasonMsg,"Length exceeds 512 characters"); |
| } |
| else |
| { |
| char * sqroot = getenv("TRAF_HOME"); |
| if (sqroot && (! CmpCommon::context()->getSqlmxRegress()) && |
| (strncmp(sqroot, getTargetName().data(),strlen(sqroot)) == 0)) |
| { |
| raiseError = TRUE; |
| sprintf(reasonMsg,"Database system directory was used"); |
| } |
| } |
| if (raiseError && strncmp(getTargetName().data(),"hdfs://",7) != 0 ) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4378) << DgString0(reasonMsg) ; |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| |
| |
| if (getDelimiter().length() == 0) |
| { |
| delimiter_ = ActiveSchemaDB()->getDefaults().getValue(TRAF_UNLOAD_DEF_DELIMITER); |
| } |
| |
| // if inserting into a hive table and an explicit null string was |
| // not specified in the unload command, and the target table has a user |
| // specified null format string, then use it. |
| if ((isHiveInsert()) && |
| (hiveTableDesc_ && hiveTableDesc_->getNATable() && |
| hiveTableDesc_->getNATable()->getClusteringIndex()) && |
| (NOT nullStringSpec_)) |
| { |
| const HHDFSTableStats* hTabStats = |
| hiveTableDesc_->getNATable()->getClusteringIndex()->getHHDFSTableStats(); |
| |
| if (hTabStats->getNullFormat()) |
| { |
| nullString_ = hTabStats->getNullFormat(); |
| nullStringSpec_ = TRUE; |
| } |
| } |
| |
| // if an explicit or user specified null format was not used, then |
| // use the default null string. |
| if (NOT nullStringSpec_) |
| { |
| nullString_ = HIVE_DEFAULT_NULL_STRING; |
| } |
| |
| if (getRecordSeparator().length() == 0) |
| { |
| recordSeparator_ = ActiveSchemaDB()->getDefaults().getValue(TRAF_UNLOAD_DEF_RECORD_SEPARATOR); |
| } |
| |
| |
| if (!isHiveInsert()) |
| { |
| bindWA->setIsFastExtract(); |
| } |
| // Bind the child nodes. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Use information from child to populate childInfo_ |
| NAHeap *heap = CmpCommon::statementHeap(); |
| |
| RETDesc *childRETDesc = child(0)->getRETDesc(); |
| // output vidList from child RetDesc, |
| // can also get from child Root compExpr |
| ValueIdList vidList; |
| childRETDesc->getValueIdList(vidList, USER_COLUMN); |
| |
| if (isHiveInsert()) |
| { |
| // validate number of columns and column types of the select list |
| ValueIdList tgtCols; |
| |
| hiveTableDesc_->getUserColumnList(tgtCols); |
| |
| if (vidList.entries() != tgtCols.entries()) |
| { |
| // 4023 degree of row value constructor must equal that of target table |
| *CmpCommon::diags() << DgSqlCode(-4023) |
| << DgInt0(vidList.entries()) |
| << DgInt1(tgtCols.entries()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // Check that the source and target types are compatible. |
| for (CollIndex j=0; j<vidList.entries(); j++) |
| { |
| Assign *tmpAssign = new(bindWA->wHeap()) |
| Assign(tgtCols[j].getItemExpr(), vidList[j].getItemExpr()); |
| |
| if ( CmpCommon::getDefault(ALLOW_IMPLICIT_CHAR_CASTING) == DF_ON ) |
| tmpAssign->tryToDoImplicitCasting(bindWA); |
| const NAType *targetType = tmpAssign->synthesizeType(); |
| if (!targetType) { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| } |
| |
| setSelectList(vidList); |
| |
| if (includeHeader()) |
| { |
| const ColumnDescList &columnsRET = *(childRETDesc->getColumnList()); |
| for (CollIndex i = 0; i < columnsRET.entries(); i++) |
| { |
| if (columnsRET[i]->getHeading()) |
| header_ += columnsRET[i]->getHeading(); |
| else if (!(columnsRET[i]->getColRefNameObj().isEmpty())) |
| header_ += columnsRET[i]->getColRefNameObj().getColName(); |
| else |
| header_ += "EXPR"; |
| |
| if (i < (columnsRET.entries() -1)) |
| { |
| header_ += " "; |
| header_ += delimiter_; |
| header_ += " "; |
| } |
| } |
| } |
| else |
| { |
| header_ = "NO_HEADER" ; |
| } |
| |
| // no rows are returned from this operator. |
| // Allocate an empty RETDesc and attach it to this and the BindScope. |
| setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA)); |
| |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| return boundExpr; |
| } |
| |
| |
| RelExpr * ControlRunningQuery::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| // |
| // Check to see if user is authorized to control this query. |
| // |
| if (!isUserAuthorized(bindWA)) |
| return NULL; |
| |
| // |
| // Bind the child nodes. |
| // |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // no rows are returned from this operator. |
| // Allocate an empty RETDesc and attach it to this and the BindScope. |
| // |
| setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA)); |
| |
| // |
| // Bind the base class. |
| // |
| RelExpr *boundExpr = bindSelf(bindWA); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| |
| |
| ValueIdSet ov; |
| getPotentialOutputValues(ov); |
| getGroupAttr()->addCharacteristicOutputs(ov); |
| |
| return boundExpr; |
| } // ControlRunningQuery::bindNode() |
| |
| |
| bool ControlRunningQuery::isUserAuthorized(BindWA *bindWA) |
| { |
| bool userHasPriv = false; |
| Int32 sessionID = ComUser::getSessionUser(); |
| |
| // Check to see if the current user owns the query id. |
| // This only has to be done for the Cancel query request. |
| // This option to check privilege is not available unless |
| // the query Id was supplied. |
| if ((action_ == Cancel) && |
| (qs_ == ControlQid)) |
| { |
| // The user ID associated with the query is stored in the QID. |
| // To be safe, copy the QID to a character string. |
| Int32 qidLen = queryId_.length(); |
| char *pQid = new (bindWA->wHeap()) char[qidLen+1]; |
| str_cpy_all(pQid, queryId_.data(), qidLen); |
| pQid[qidLen] = '\0'; |
| |
| // Set up the returned parameters |
| // Max username can be (128 * 2) + 2 (delimiters) + 1 (null indicator) |
| char username[2 * MAX_USERNAME_LEN + 2 + 1]; |
| Int64 usernameLen = sizeof(username) - 1; |
| |
| // Call function to extract the username from the QID |
| Int32 retcode = ComSqlId::getSqlQueryIdAttr(ComSqlId::SQLQUERYID_USERNAME, |
| pQid, |
| qidLen, |
| usernameLen, |
| &username[0]); |
| if (retcode == 0) |
| { |
| // The username stored in the QID is actually the userID preceeded with |
| // a "U". Check for a U and convert the succeeding characters |
| // to integer. This integer value is compared against the current userID. |
| username[usernameLen] = '\0'; |
| if (username[0] == 'U') |
| { |
| Int64 userID = str_atoi(&username[1],usernameLen - 1); |
| if (sessionID == userID || sessionID == ComUser::getRootUserID()) |
| userHasPriv = true; |
| } |
| // If userName does not begin with a 'U', ignore and continue |
| } |
| // If retcode != 0, continue, an invalid QID could be specified which |
| // is checked later in the code |
| } |
| |
| // The current user does not own the query, see if the current user has |
| // the correct QUERY privilege. Code above only supports cancel, but other |
| // checks could be added. Component checks for all query operations. |
| if (!userHasPriv) |
| { |
| SQLOperation operation; |
| switch (ControlRunningQuery::action_) |
| { |
| case ControlRunningQuery::Suspend: |
| operation = SQLOperation::QUERY_SUSPEND; |
| break; |
| case ControlRunningQuery::Activate: |
| operation = SQLOperation::QUERY_ACTIVATE; |
| break; |
| case ControlRunningQuery::Cancel: |
| operation = SQLOperation::QUERY_CANCEL; |
| break; |
| default: |
| operation = SQLOperation::UNKNOWN; |
| } |
| |
| NAString privMDLoc = CmpSeabaseDDL::getSystemCatalogStatic(); |
| privMDLoc += ".\""; |
| privMDLoc += SEABASE_PRIVMGR_SCHEMA; |
| privMDLoc += "\""; |
| |
| PrivMgrComponentPrivileges componentPriv( |
| privMDLoc.data(),CmpCommon::diags()); |
| |
| userHasPriv = componentPriv.hasSQLPriv(sessionID,operation,true); |
| |
| if (!userHasPriv) |
| { |
| // ANSI requests a special SqlState for cancel requests |
| if (ControlRunningQuery::action_ == ControlRunningQuery::Cancel) |
| *CmpCommon::diags() << DgSqlCode(-8029); |
| else |
| *CmpCommon::diags() << DgSqlCode(-1017); |
| bindWA->setErrStatus(); |
| } |
| |
| if (bindWA->errStatus()) |
| return false; |
| } |
| return true; |
| |
| }// ControlRunningQuery::isUserAuthorized() |
| |
| RelExpr * CommonSubExprRef::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| CSEInfo *info = CmpCommon::statement()->getCSEInfo(internalName_); |
| CommonSubExprRef *parentCSE = bindWA->inCSE(); |
| |
| DCMPASSERT(info); |
| |
| // eliminate any CommonSubExprRef nodes that are not truly common, |
| // i.e. those that are referenced only once |
| if (info->getNumConsumers() <= 1) |
| { |
| info->eliminate(); |
| return child(0).getPtr()->bindNode(bindWA); |
| } |
| |
| bindWA->setInCSE(this); |
| |
| // establish the parent/child relationship |
| addParentRef(parentCSE); |
| |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // we know that our child is a RenameTable (same name as this CSE, |
| // whose child is a RelRoot, defining the CTE. Copy the bound select |
| // list of the CTE. |
| CMPASSERT(child(0)->getOperatorType() == REL_RENAME_TABLE && |
| child(0)->child(0)->getOperatorType() == REL_ROOT); |
| columnList_ = static_cast<RelRoot *>(child(0)->child(0).getPtr())->compExpr(); |
| |
| bindWA->setInCSE(parentCSE); |
| |
| return bindSelf(bindWA); |
| } |
| |
| RelExpr * OSIMControl::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| //Create OptimizerSimulator if this is called first time. |
| if(!CURRCONTEXT_OPTSIMULATOR) |
| CURRCONTEXT_OPTSIMULATOR = new(CTXTHEAP) OptimizerSimulator(CTXTHEAP); |
| |
| //in respond to force option of osim load, |
| //e.g. osim load from '/xxx/xxx/osim-dir', force |
| //if true, when loading osim tables/views/indexes |
| //existing objects with same qualified name |
| //will be droped first |
| CURRCONTEXT_OPTSIMULATOR->setForceLoad(isForceLoad()); |
| //Set OSIM mode |
| if(!CURRCONTEXT_OPTSIMULATOR->setOsimModeAndLogDir(targetMode_, osimLocalDir_.data())) |
| { |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| return ControlAbstractClass::bindNode(bindWA); |
| } |
| |