blob: 716a9c67c21c0f55e9d9e110d1e873a032dd4503 [file] [log] [blame]
/**********************************************************************
// @@@ START COPYRIGHT @@@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
// @@@ END COPYRIGHT @@@
**********************************************************************/
/* -*-C++-*-
******************************************************************************
*
* File: 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;
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();
}
if (postEndl) cerr << endl;
#endif
} // GU_DEBUG_Display()
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()
// 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()
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()
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)
{
if (! rd)
return;
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(bindWA->wHeap(), MAX_HARDWARE_SUPPORTED_UNSIGNED_NUMERIC_PRECISION,
nTyp->getScale(),
FALSE,
FALSE,
naType->supportsSQLnull()
));
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(bindWA->wHeap(), 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(bindWA->wHeap(), NOT srcNum->isUnsigned(),
naType->supportsSQLnull());
else
newType = new (bindWA->wHeap())
SQLNumeric(bindWA->wHeap(), 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(bindWA->wHeap(), newPrec,
newScale,
((SQLBigNum &)col->getValueId().getType()).isARealBigNum(),
nTyp->isSigned(),
nTyp->supportsSQLnull());
}
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(bindWA->wHeap(), TRUE, // hardware only supports signed
nTyp->supportsSQLnull());
else
newTyp = new (bindWA->wHeap())
SQLNumeric(bindWA->wHeap(), 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);
}
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())))
{
// for Histogram, support to use VOLATILE SCHEMA
// or else, don't support
if (!corrName.getQualifiedNameObj().isHistogramTable() &&
!CmpCommon::context()->sqlSession()->validateVolatileQualifiedSchemaName
(corrName.getQualifiedNameObj()))
{
bindWA->setErrStatus();
return NULL;
}
}
// if specified name is an HBASE name, see if a mapped table exists
if ((corrName.getQualifiedNameObj().getCatalogName() == HBASE_SYSTEM_CATALOG) &&
((corrName.getQualifiedNameObj().getSchemaName() == HBASE_MAP_SCHEMA) ||
(corrName.getQualifiedNameObj().getSchemaName() == HBASE_SYSTEM_SCHEMA)))
{
corrName.getQualifiedNameObj().setCatalogName(TRAFODION_SYSCAT_LIT);
corrName.getQualifiedNameObj().setSchemaName(HBASE_EXT_MAP_SCHEMA);
}
//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)
{
if ((bindWA->inViewDefinition()) &&
(corrName.isHbaseMap()))
{
// this is an internal hbase mapped name.
// Use the original name to store in view definition.
CorrName origName(corrName);
origName.getQualifiedNameObj().setCatalogName(HBASE_SYSTEM_CATALOG);
origName.getQualifiedNameObj().setSchemaName(HBASE_MAP_SCHEMA);
BindUtil_CollectTableUsageInfo(bindWA, origName);
}
else
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->isTrafExternalTable() &&
(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->isTrafExternalTable() &&
(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 native and external table definitions.
// -- If this call is to drop external table, skip comparison.
// -- Otherwise compare that number of columns is the same.
// -- Compare type for corresponding columns. But if external table
// was created with explicit col attrs, then skip type check for cols.
//
// TBD - return what mismatches
NABoolean compError = FALSE;
if (nativeNATable &&
(NOT bindWA->externalTableDrop()))
{
if (table->getNAColumnArray().entries() !=
nativeNATable->getNAColumnArray().entries())
compError = TRUE;
if ((NOT compError) &&
(NOT table->hiveExtColAttrs()) &&
(NOT table->hiveExtKeyAttrs()))
{
if (NOT (table->getNAColumnArray() == nativeNATable->getNAColumnArray()))
compError = TRUE;
}
if (compError)
{
*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;
}
}
if (hint AND hint->indexCnt() > tdesc->getHintIndexes().entries() AND
CmpCommon::getDefault(INDEX_HINT_WARNINGS) != DF_OFF)
{
// emit a warning that we didn't process all the index hints,
// there is probably a spelling mistake
*CmpCommon::diags() << DgSqlCode(4371)
<< DgInt0(tdesc->getHintIndexes().entries())
<< DgInt1(hint->indexCnt())
<< DgTableName(naTable->getTableName().
getQualifiedNameAsAnsiString());
}
// 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
// If this is a native hive view, then temporarily change the default schema to
// that of viewName. This will make sure that all unqualified objects in view
// text are expanded in this schema.
NABoolean defSchWasChanged = FALSE;
SchemaName savedSch(bindWA->getDefaultSchema());
if (viewName.isHive())
{
SchemaName s(viewName.getQualifiedNameObj().getSchemaName(),
viewName.getQualifiedNameObj().getCatalogName());
bindWA->setDefaultSchema(s);
defSchWasChanged = TRUE;
}
// 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());
parser.hiveDDLInfo_->disableDDLcheck_ = TRUE;
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();
if (defSchWasChanged)
bindWA->setDefaultSchema(savedSch);
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() == TransMode::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())
{
if (defSchWasChanged)
bindWA->setDefaultSchema(savedSch);
return NULL;
}
bindWA->setNameLocListPtr(saveNameLocList);
bindWA->viewCount()--;
// 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
if (defSchWasChanged)
bindWA->setDefaultSchema(savedSch);
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) && (child(0)))
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
// -----------------------------------------------------------------------
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()
// -----------------------------------------------------------------------
// member functions for class Except
// -----------------------------------------------------------------------
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()
// -----------------------------------------------------------------------
// 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,
getUnionFlags());
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 &notAllowedWithSelIndexInHaving,
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(bindWA->wHeap(), 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))
{
CorrName cn(scan->getTableName());
NATable *naTable = bindWA->getNATable(cn);
if (bindWA->errStatus())
return this;
if (((naTable->getObjectType() == COM_BASE_TABLE_OBJECT) ||
(naTable->getObjectType() == COM_INDEX_OBJECT)) &&
(((naTable->isSeabaseTable()) &&
(NOT naTable->isHbaseMapTable())) ||
((naTable->isHiveTable()) &&
(NOT naTable->isView()) &&
(naTable->getClusteringIndex()) &&
(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(cn,
aggrSet);
else
eue =
new(CmpCommon::statementHeap())
ExeUtilOrcFastAggr(cn,
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_,
0, 0, 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) && 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 {
if (child(0)) {
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)) &&
(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)
<< DgInt0(outputVarCnt()) << DgInt1(getRETDesc()->getDegree());
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();
NABoolean groupByAggNodeAdded = FALSE;
if (inRowSubquery && (CmpCommon::getDefault(COMP_BOOL_137) == DF_OFF))
// force adding one row aggregates in the [last 0] case
groupByAggNodeAdded = addOneRowAggregates(bindWA,
getFirstNRows() == -2 /* [last 0] case */);
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 ((getFirstNRows() != -1) ||
(getFirstNRowsParam()))
{
// [first/any/last N] processing
RelExpr * nodeToInsertUnder = this;
if (inRowSubquery)
{
// [first/any/last N] in a row subquery special case
//
// In this case, if N > 1 it is first/any N, and we can simply
// ignore that as row subqueries already enforce an at-most-one
// row semantic. For [first 1], [last 1], [last 0], we need to
// add the node below any one-row aggregate group by node created
// earlier in this method. (If it put it above that group by node,
// that is too late; the one-row aggregate will raise an 8401 error
// before our FirstN node has a chance to narrow the result down to
// zero or one rows.) There is an interesting nuance with [last 0]:
// We forced the addition of a one-row aggregate group by node
// in that case, because [last 0] returns no rows. We might have
// a scalar aggregate subquery which ordinarily would not require
// a one-row aggregate group, but when [last 0] is present we want
// to force the aggregates to become NULL. Adding a one-row
// aggregate group on top of the scalar aggregate, with the FirstN
// node in between them does the trick.
if (groupByAggNodeAdded &&
( (getFirstNRows() == 1) || // [first 1] or [any 1]
(getFirstNRows() == -2) || // [last 0]
(getFirstNRows() == -3) ) ) // [last 1]
{
nodeToInsertUnder = child(0);
CMPASSERT(nodeToInsertUnder->getOperatorType() == REL_GROUPBY);
}
else if (!groupByAggNodeAdded && (getFirstNRows() == -2)) // [last 0]
{
CMPASSERT(groupByAggNodeAdded); // a GroupByAgg should have been forced
}
else // a case where we can throw the [first/any/last N] away
{
nodeToInsertUnder = NULL;
}
}
if (nodeToInsertUnder)
{
// create a firstN node to retrieve firstN rows.
FirstN * firstn = new(bindWA->wHeap())
FirstN(nodeToInsertUnder->child(0), getFirstNRows(), needFirstSortedRows(), getFirstNRowsParam());
firstn->bindNode(bindWA);
if (bindWA->errStatus())
return NULL;
// Note: For ORDER BY + [first n], we want to sort the rows before
// picking just n of them. (We don't do this for [any n].) We might
// be tempted to copy the orderByTree into the FirstN node at this
// point, but this doesn't work. Instead, we copy the bound ValueIds
// at normalize time. We have to do this in case there are expressions
// involved in the ORDER BY and there is a DESC. The presence of the
// Inverse node at the top of the expression tree seems to cause the
// expressions underneath to be bound to different ValueIds, which
// causes coverage tests in FirstN::createContextForAChild requirements
// generation to fail. An example of where this occurs is:
//
// prepare s1 from
// select [first 2] y, x from
// (select a,b + 26 from t1) as t(x,y)
// order by y desc;
//
// If we copy the ORDER BY ItemExpr tree and rebind, we get a different
// ValueId for the expression b + 26 in the child characteristic outputs
// than what we get for the child of Inverse in Inverse(B + 26). The
// trick of copying the already-bound ORDER BY clause later avoids this.
nodeToInsertUnder->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() != TransMode::ACCESS_TYPE_NOT_SPECIFIED_)
{
if (accessOptions().accessType() == TransMode::REPEATABLE_READ_ACCESS_)
{
*CmpCommon::diags() << DgSqlCode(-4381);
bindWA->setErrStatus();
return NULL;
}
}
else
{
TransMode::IsolationLevel il=CmpCommon::transMode()->getIsolationLevel();
if (CmpCommon::transMode()->ILtoAT(il) == TransMode::REPEATABLE_READ_ACCESS_)
{
*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
ItemExpr * RelRoot::removeAssignmentStTree()
{
ItemExpr* tempTree = assignmentStTree_;
assignmentStTree_ = NULL;
return tempTree;
}
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
//
// Keys are added as follows:
// if a privilege has been granted via a role, add a RoleUserKey
// if this role is revoked from the user, then invalidation is forced
// if a privilege has been granted to public, add a UserObjectPublicKey
// if a privilege is revoked from public, then invalidation is forced
// if a privilege has been granted directly to an object, add UserObjectKey
// if the privilege is revoked from the user, then invalidation is forced
// If a privilege has not been granted to an object, but is has been granted
// to a role, add a RoleObjectKey
//
// So if the same privilege has been granted directly to the user and via
// a role granted to the user, we only add a UserObjectKey
// ****************************************************************************
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;
ComSecurityKey * RoleUserKey = 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;
}
// Found a security key for a role associated with the user
else
{
if ( ! RoleObjectKey )
RoleObjectKey = thisKey;
}
}
// See if the security key is role related
else if (thisKey->getSecurityKeyType() == COM_QI_USER_GRANT_ROLE)
{
if ( thisKey->getSubjectHashValue() == userHashValue )
{
if (! RoleUserKey )
RoleUserKey = 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 RoleUserKey if priv comes from role - handles revoke role from user
if (BestKey == RoleObjectKey)
if ( RoleUserKey )
securityKeySet_.insert(*RoleUserKey );
// Add public if it exists - handles revoke public from user
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();
}
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))
{
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()
static void bindHint(Hint *hint, BindWA *bindWA)
{
// bind the index names, make them fully qualified
for (CollIndex x=0; hint && x<hint->indexCnt(); x++)
{
QualifiedName qualIxName((*hint)[x], 1, bindWA->wHeap(), bindWA);
hint->replaceIndexHint(x, qualIxName.getQualifiedNameAsAnsiString());
}
}
//----------------------------------------------------------------------------
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);
}
if (getHint())
bindHint(getHint(), bindWA);
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() == TransMode::REPEATABLE_READ_ACCESS_ ||
accessOptions().accessType() == TransMode::READ_COMMITTED_ACCESS_ ||
accessOptions().accessType() == TransMode::READ_UNCOMMITTED_ACCESS_
) {
naTable->setRefsIncompatibleDP2Halloween();
}
}
else {
TransMode::IsolationLevel il = CmpCommon::transMode()->getIsolationLevel();
if((CmpCommon::transMode()->ILtoAT(il) == TransMode::REPEATABLE_READ_ACCESS_ ) ||
(CmpCommon::transMode()->ILtoAT(il) == TransMode::READ_COMMITTED_ACCESS_ ) ||
(CmpCommon::transMode()->ILtoAT(il) == TransMode::READ_UNCOMMITTED_ACCESS_ )) {
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(heap));
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->setIsCastTo(TRUE);
}
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)
<< DgInt0(derivedColList.entries()) << DgInt1(sourceTable.getDegree());
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, &currentCol, 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, &currentCol, 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
// -----------------------------------------------------------------------
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(heap, 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(heap, 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(heap, 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
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;
}
// 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.
if (child(0)->getOperatorType() == REL_TUPLE_LIST) {
ValueIdList tgtColList;
getTableDesc()->getUserColumnList(tgtColList);
TupleList *tl = (TupleList *)child(0)->castToRelExpr();
tl->castToList() = tgtColList;
}
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);
}
// 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) {
cerr << "columnLkp " << flush;
columnLkp->display();
}
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)
<< DgInt0(sourceTable.getDegree()) << DgInt1(tgtColList.entries());
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 || nacol->getDefaultClass() == COM_CURRENT_UT_DEFAULT
|| nacol->getDefaultClass() == COM_UUID_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_CURRENT_UT_DEFAULT) &&
(nacol->getDefaultClass() != COM_FUNCTION_DEFINED_DEFAULT) &&
(nacol->getDefaultClass() != COM_UUID_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();
// 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 ((NOT ie->wasDefaultClause()) &&
(CmpCommon::getDefault(OVERRIDE_GENERATED_IDENTITY_VALUES) == DF_OFF))
{
*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) {
const LIST(IndexDesc *) indexes = getTableDesc()->getIndexes();
for(i = 0; i < indexes.entries(); i++) {
indexes[i]->getPartitioningFunction()->setAssignPartition(TRUE);
}
}
if ((getOperatorType() == REL_UNARY_INSERT) &&
(getTableDesc()->getNATable()->hasLobColumn()) &&
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;
}
else
{
Assign * assign = (Assign*)newRecExprArray()[n].getItemExpr();
ItemExpr *assign_child = NULL;
if (assign)
{
assign_child = assign->child(1);
}
if ( assign_child && assign_child->getOperatorType() == ITM_CAST )
{
const NAType& type = assign_child->getValueId().getType();
if ( type.getTypeQualifier() == NA_LOB_TYPE )
{
// cannot have this function in a values list with multiple
// tuples. Use a single tuple.
*CmpCommon::diags() << DgSqlCode(-4483);
bindWA->setErrStatus();
return boundExpr;
}
}
}
}
}
}
}
// 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") ;
bindWA->setErrStatus();
return boundExpr;
}
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);
bindWA->setErrStatus();
return boundExpr;
}
}
const NATable* naTable = getTableDesc()->getNATable();
if (naTable->hasLobColumn())
{
NAColumn *nac = NULL;
for (CollIndex c = 0; c < naTable->getColumnCount(); c++)
{
nac = naTable->getNAColumnArray()[c];
if (nac->getType()->isLob())
break;
}
*CmpCommon::diags() << DgSqlCode(-4494)
<< DgTableName(naTable->getTableName().
getQualifiedNameAsAnsiString())
<< DgColumnName(nac->getColName());
bindWA->setErrStatus();
return boundExpr;
} // has Lob column
} // isLoadPrep
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 = 0;
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)
<< DgInt0(++pos)
<< DgInt1(colList.entries());
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(), FALSE /* there's no ORDER BY on an UPDATE */, 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;
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(bindWA->wHeap(),
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(), FALSE /* There's no ORDER BY on a DELETE */, 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 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(), FALSE /* There's no ORDER BY on a DELETE */ );
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;
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();
ValueIdArray holeyArray(totalColCount);
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);
}
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()) {
// On attempt to update an individual VP, say: 4082 table not accessible
*CmpCommon::diags() << DgSqlCode(-4082) <<
DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString());
bindWA->setErrStatus();
return this;
}
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 (!bindWA->isBindingMvRefresh())
{
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.
// Rather than compare naTable pointers we now compare the extended
// qualified name contained in them. This name is the key to an natable
// object in NATableDB and will enable us to tell if scan's table and
// GU's table are the same.
isScanOnDifferentTable = (naTable->getExtendedQualName() !=
naTableTop->getExtendedQualName());
}
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) == TransMode::REPEATABLE_READ_ACCESS_ ) ||
(CmpCommon::transMode()->ILtoAT(il) == TransMode::READ_COMMITTED_ACCESS_ ) ||
(CmpCommon::transMode()->ILtoAT(il) == TransMode::READ_UNCOMMITTED_ACCESS_ ))
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() == TransMode::SKIP_CONFLICT_ACCESS_) ||
(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
// -----------------------------------------------------------------------
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()
// -----------------------------------------------------------------------
// 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(NULL, 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)
{
if (affectYourself)
attrEnum_ = defs.validateAndInsert(token_, value_, reset_);
else
attrEnum_ = 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());
//
*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
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 (getIsControl())
describedTableName_.applyDefaults(bindWA, bindWA->getDefaultSchema());
if (NOT getIsControl())
{
// do not override schema for showddl
bindWA->setToOverrideSchema(FALSE);
// check if we need to consider public schema before
// describedTableName_ is qualified by getNATable
if (describedTableName_.getQualifiedNameObj().getSchemaName().isNull())
setToTryPublicSchema(TRUE);
if ((getFormat() == Describe::INVOKE_) ||
(getFormat() == Describe::SHOWDDL_) &&
(getLabelAnsiNameSpace() == COM_TABLE_NAME) &&
(NOT getIsSchema()))
{
bindWA->getNATableInternal(describedTableName_);
if (bindWA->errStatus())
{
return this;
}
}
else
describedTableName_.applyDefaults(bindWA, bindWA->getDefaultSchema());
}
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))
{
if (NOT (mode_->isolationLevel() == TransMode::IL_NOT_SPECIFIED_ &&
mode_->accessMode() == TransMode::AM_NOT_SPECIFIED_))
{
*CmpCommon::diags() << DgSqlCode(-3242)
<< DgString0("Isolation level or Access mode options cannot be specified along with autocommit or autobegin options.");
bindWA->setErrStatus();
return this;
}
}
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
//
constExpr = new(bindWA->wHeap()) SystemLiteral(keyVal);
// 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.
ItemExpr* pfie = new (bindWA->wHeap()) SystemLiteral(packingFactorLong());
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();
Int32 length = packedColName.length();
char * colName = new (bindWA->wHeap()) char[length + 1];
colName[length] = 0;
str_cpy_all(colName, packedColName, packedColName.length());
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(bindWA->wHeap(), 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(bindWA->wHeap(), 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();
Lng32 base = (colType->supportsSQLnullPhysical() ? (pf-1)/CHAR_BIT +1 : 0)
+ sizeof(Int32);
// $$$ 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(bindWA->wHeap(), 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(bindWA->wHeap(), 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(bindWA->wHeap(), 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(bindWA->wHeap(), 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(bindWA->wHeap(), 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()
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;
}
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)
<< DgInt0(numOutputHostvars) << DgInt1(childTableVidList.entries());
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 &params = 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
void
IsolatedNonTableUDR::setInOrOutParam (ItemExpr *expr,
ComColumnDirection paramMode,
BindWA *bindWA)
{
// Should not get here..
CMPASSERT(FALSE);
}
// 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 &paramType = *(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
*CmpCommon::diags() << DgSqlCode(-UDR_BINDER_PARAM_TYPE_MISMATCH)
<< DgInt0 (ordinalPosition)
<< DgTableName(getRoutineName().getQualifiedNameAsString())
<< DgString0 (inputType.getTypeSQLname (TRUE))
<< DgString1 (paramType.getTypeSQLname (TRUE));
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, &paramType, 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 ()))
{
*CmpCommon::diags() << DgSqlCode(-UDR_BINDER_OUTVAR_NOT_HV_OR_DP)
<< DgInt0(ordinalPosition)
<< DgTableName(getRoutineName().getQualifiedNameAsString());
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 &paramsList) 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);
if (opType == REL_TABLE_MAPPING_BUILTIN_LOG_READER ||
opType == REL_TABLE_MAPPING_BUILTIN_JDBC)
{
// The event log reader and JDBC TMUDFs are being migrated
// to real UDFs, use of the predefined UDFs is deprecated.
// Issue a warning. Eventually these predefined functions
// will be removed.
(*CmpCommon::diags())
<< DgSqlCode(4323)
<< DgString0(tmfuncName.getExposedNameAsAnsiString());
}
// 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++)
{
const NAType &childColType = childRetDesc->getType(j);
const NAType *adjustedChildColType = &childColType;
NAColumn * childCol = NULL;
if (childColType.getSimpleTypeName() == "NUMERIC" &&
getBinaryStorageSize(childColType.getPrecision()) !=
childColType.getNominalSize())
{
// In the optimizer code, we may choose different storage
// sizes for the same type, for example a NUMERIC(1,0)
// sometimes has one byte, sometimes 2 bytes of storage. For
// examples, see NumericType::synthesizeType() and CQD
// TRAF_CREATE_TINYINT_LITERAL.
// In the TMUDF code, on the other hand, we compute the
// storage size from the precision. So, make sure we follow
// the TMUDF rules here, when we describe its input table
adjustedChildColType = new(heap) SQLNumeric(heap,
getBinaryStorageSize(childColType.getPrecision()),
childColType.getPrecision(),
childColType.getScale(),
((const NumericType &) childColType).isSigned(),
childColType.supportsSQLnull());
}
childCol = new (heap) NAColumn(
childRetDesc->getColRefNameObj(j).getColName().data(),
j,
adjustedChildColType->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;
}
// 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;
}
}
}
else
{
NABoolean hasLob = FALSE;
CollIndex i = 0;
for (i = 0; (i < vidList.entries() && !hasLob); i++)
hasLob = vidList[i].getType().isLob();
if (hasLob) {
*CmpCommon::diags() << DgSqlCode(-4495)
<< DgColumnName(childRETDesc->getColRefNameObj(i).
getColRefAsAnsiString());
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);
}