| /********************************************************************** |
| // @@@ 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: BindItemExpr.C |
| * Description: Item expressions (binder-related methods) |
| * Created: 09/31/94 |
| * Language: C++ |
| * |
| * |
| * Canst thou bind the sweet influences of Pleiades, |
| * or loose the bands of Orion? |
| * -- Job 38:31 |
| * |
| ****************************************************************************** |
| */ |
| |
| #define SQLPARSERGLOBALS_FLAGS // must precede all #include's |
| #define SQLPARSERGLOBALS_NADEFAULTS |
| |
| #include "Platform.h" |
| #include "NAWinNT.h" |
| #include "nawstring.h" |
| #include "AllItemExpr.h" |
| #include "BindWA.h" |
| #include "ComOperators.h" |
| #include "GroupAttr.h" // QSTUFF |
| #include "parser.h" |
| #include "ParNameLocList.h" |
| #include "Rel3GL.h" |
| #include "RelMisc.h" |
| #include "RelScan.h" |
| #include "RelUpdate.h" |
| #include "Sqlcomp.h" |
| #include "StmtDDLAddConstraintCheck.h" |
| #include "StmtDDLCreateTrigger.h" |
| #include "StmtDDLCreateView.h" |
| #include "StmtDDLCreateMV.h" |
| #include "ex_error.h" |
| #include "exp_like.h" |
| #include "ItemColRef.h" |
| #include "TriggerDB.h" |
| #include "UdrErrors.h" |
| #include "nchar_mp.h" |
| #include "CmpStatement.h" |
| #include "CmpCommon.h" |
| #include "Analyzer.h" |
| #include "ControlDB.h" |
| #include "OptimizerSimulator.h" |
| #include "RelSequence.h" |
| #include "ItemSample.h" |
| #include "SqlParserGlobals.h" |
| #include "RelSequence.h" |
| #include "ComSqlId.h" |
| |
| #include "ItemFuncUDF.h" |
| #include "CmpSeabaseDDL.h" |
| #include "QCache.h" |
| |
| #include "TrafDDLdesc.h" |
| #include "exp_datetime.h" |
| |
| #include <stack> |
| |
| |
| // defined in SynthType.cpp |
| extern |
| void emitDyadicTypeSQLnameMsg(Lng32 sqlCode, |
| const NAType &op1, |
| const NAType &op2, |
| const char *str1 = NULL, |
| const char *str2 = NULL, |
| ComDiagsArea * diagsArea = NULL, |
| const Lng32 int1 = -999999); |
| |
| // defined in parser/SqlParserAux.h |
| extern |
| ItemExpr *literalOfDate(NAString *strptr, NABoolean noDealloc = FALSE); |
| |
| #define BINDITEMEXPR_STMTCHARSET CharInfo::UTF8 |
| |
| // ----------------------------------------------------------------------- |
| // utility functions |
| // ----------------------------------------------------------------------- |
| |
| inline static ValueId createValueDesc(BindWA *bindWA, |
| ItemExpr *expr, |
| const NAType *type) |
| { |
| // See CMPASSERT in RelRoot::bindNode -- |
| // put there, instead of here, for performance (assert only once per query). |
| return ValueDesc::create(expr, type, bindWA->wHeap()); |
| } |
| |
| // Work for Genesis 10-971028-7413 (Ansi 7.1 SR 1, plus Ansi 8 "predicates"). |
| // |
| // According to Ansi, the keyword NULL in a value expression (in DML) |
| // may appear *only* in: |
| // - the VALUES row-val-ctor(s) of an INSERT |
| // - a result of a CASE |
| // - operand 1 of a CAST |
| // As extensions, since they're harmless and possibly useful, |
| // we also allow the NULL kwd to appear: |
| // - as a select-list item, e.g. SELECT col1,NULL,col2 FROM tbl; |
| // - in a IS [NOT] NULL predicate, e.g. WHERE NULL IS NULL; |
| // - in any non-Ansi function, e.g. EXPLAIN |
| // - in various (many) internal functions |
| // |
| // Here (throughout BindItemExpr), we explicitly disallow the NULL kwd in: |
| // - comparison predicates (BiRelat), e.g. col>=NULL, col IN (x,NULL,y) |
| // - quantified comparison predicates e.g. col>=ANY(subqry), NULL IN (subqry) |
| // - IN predicates (e.g. see above two lines!) |
| // - BETWEEN and LIKE predicates (see ItemFunc.h ctors, and Function::bindNode) |
| // - all Ansi functions, including aggregates, e.g. UPPER(NULL), SUM(NULL) |
| // - arithmetic operations, e.g. NULL*col, -NULL |
| // |
| // **Note** that when we support the MATCH and OVERLAPS predicates, |
| // they'll need to disallow the kwd too (emulate BETWEEN and LIKE, |
| // their ::allowsSQLnullArg, ::isAPredicate, and ::bindNode methods). |
| // |
| // |
| // The keyword DEFAULT is even more restricted in Ansi syntax, so the mechanism |
| // for disallowing it is different and simpler: |
| // it's by default disallowed everywhere except where explicitly enabled |
| // via binder context and Insert node context -- in an INSERT VALUES list only. |
| // |
| // According to Ansi, |
| // INSERT INTO t(a,b,c) VALUES(DEFAULT,DEFAULT+1,SUBSTRING(DEFAULT FROM 2)) |
| // is not legal (DEFAULT for column A is, but not in the other 2 expressions), |
| // but as a Tandem extension we allow arithmetic and functions. |
| // (Note that RelRoot will catch ..VALUES(..SUM(DEFAULT).. with err 4015.) |
| // |
| enum errSQLnullChild |
| { FUNCTION_ = -4097, ARITH_ = -4098, PREDICATE_ = -4099, |
| DEFAULT_SPEC_ = -4096, // see DefaultSpecification::bindNode |
| DEFAULT_BOUND_AS_NULL_ = -4095, // see ItemExpr::bindNode |
| CHILDREN_OK_ = 0 |
| }; |
| |
| static errSQLnullChild checkForSQLnullChild(const ItemExpr *ie, |
| NABoolean SQLnullIsLegal, |
| errSQLnullChild err) |
| { |
| // Shallow (nonrecursive) testing of children, |
| // except for ItemLists whose arity 2 really implements an n-ary list. |
| Int32 arity = ie->getArity(); |
| for (Int32 i=0; i<arity; i++) { |
| ItemExpr *ieChild = ie->child(i); |
| if (!ieChild) break; // could be optional children eg. STDDEV |
| |
| if (ieChild->getOperatorType() == ITM_CONSTANT) { |
| if (!SQLnullIsLegal && ((ConstValue *)ieChild)->isNull()) { |
| |
| if (err == DEFAULT_BOUND_AS_NULL_ && |
| ((ConstValue *)ieChild)->isNullWasDefaultSpec()) |
| ((ConstValue *)ieChild)->setText("DEFAULT"); |
| return err; |
| } |
| } |
| else if (ieChild->getOperatorType() == ITM_DEFAULT_SPECIFICATION) { |
| if (err == PREDICATE_) // Tandem extension (see comments) |
| return DEFAULT_SPEC_; |
| } |
| else if (ieChild->getOperatorType() == ITM_ITEM_LIST) { |
| errSQLnullChild ret = checkForSQLnullChild(ieChild, SQLnullIsLegal, err); |
| if (ret) return ret; |
| } |
| } |
| return CHILDREN_OK_; // no naughty children |
| } |
| static NABoolean checkForSQLnullChild(BindWA *bindWA, |
| ItemExpr *ie, |
| NABoolean SQLnullIsLegal = FALSE, |
| errSQLnullChild err = PREDICATE_, |
| NABoolean isUnaryNegate = FALSE) |
| { |
| // Some Function classes implement SQL predicates (BETWEEN, LIKE, MATCH, OVERLAPS). |
| if (ie->isAPredicate()) err = PREDICATE_; |
| |
| err = checkForSQLnullChild(ie, SQLnullIsLegal, err); |
| |
| if (err) { |
| NAString unparsed(bindWA->wHeap()); |
| |
| if (isUnaryNegate) // instead of "0 - NULL" |
| unparsed = (err == DEFAULT_SPEC_) ? "(-DEFAULT)" : "(-NULL)"; |
| else if (ie->getOperatorType() == ITM_POSITION) unparsed = "POSITION or LOCATE"; |
| else if (ie->getOperatorType() == ITM_SUBSTR) unparsed = "SUBSTRING"; |
| else if (ie->getOperatorType() == ITM_TRIM) unparsed = "TRIM, LTRIM or RTRIM"; |
| else if (ie->getOperatorType() == ITM_LOWER) unparsed = "LOWER or LCASE"; |
| else if (ie->getOperatorType() == ITM_UPPER) unparsed = "UPPER or UCASE"; |
| else if (ie->getOperatorType() == ITM_NVL) unparsed = "NVL"; |
| else if (ie->getOperatorType() == ITM_QUERYID_EXTRACT) unparsed = "QUERYID_EXTRACT"; |
| else { |
| ie->unparse(unparsed, DEFAULT_PHASE, USER_FORMAT_DELUXE); |
| if (unparsed[(size_t)0] != '(') unparsed = "(" + unparsed + ")"; |
| } |
| |
| // 4095 A DEFAULT which resolves to NULL is not allowed in $0~String0. |
| // 4096 A DEFAULT spec is allowed only in an INSERT's VALUES list. |
| // 4097-99 A NULL operand cannot appear in func/oper/pred $0~String0. |
| *CmpCommon::diags() << DgSqlCode(err) << DgString0(unparsed); |
| bindWA->setErrStatus(); |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| // Flatten certain types to simplify (shorten) various if-tests. |
| static inline OperatorTypeEnum getOperatorTypeFlat(const ItemExpr *ie) |
| { |
| const OperatorType &e = ie->getOperator(); |
| return e.match(ITM_ANY_CAST) ? ITM_CAST : OperatorTypeEnum(e); |
| } |
| |
| // If target requires upshifting, and source is not already upshifted, |
| // then interpose a new upshift-function node and bind it. |
| // It is caller's duty to determine if the upshifting is required. |
| // |
| // See also Upper::bindNode(), which removes unneeded Upper's. |
| // ## Future enhancement is to NOT interpose an upshift in these cases: |
| // ## - source is a CAST from a TIME/TIMESTAMP *in format with no AM/PM marker*, |
| // ## as the source then ends up being all digits and punctuation |
| // ## so upshift is unnecessary. |
| // ## SynthType.cpp should probably set the isUpshifted flag of the CharType.. |
| // |
| static ItemExpr *applyUpperToSource(BindWA *bindWA, ItemExpr *ie, Int32 srcIndex) |
| { |
| OperatorTypeEnum opSelf = getOperatorTypeFlat(ie); |
| CMPASSERT(opSelf == ITM_ASSIGN || ie->getOperator().match(ITM_ANY_CAST)); |
| ItemExpr *src = ie->child(srcIndex); |
| ItemExpr *upSrc; |
| OperatorTypeEnum opSrc = getOperatorTypeFlat(src); |
| |
| const CharType &ct = (CharType &)src->getValueId().getType(); |
| if (ct.getTypeQualifier() == NA_CHARACTER_TYPE && !ct.isUpshifted()) { |
| |
| if (opSrc == ITM_CONSTANT && (upSrc = ((ConstValue *)src)->toUpper())) |
| ie->setChild(srcIndex, upSrc->bindNode(bindWA)); |
| else { |
| CMPASSERT(opSrc != ITM_UPPER); // otherwise, bug in SynthType |
| const NAColumn *nacolSrc = |
| src->getValueId().getNAColumn(TRUE/*okIfNotColumn*/); |
| |
| if (NOT nacolSrc || NOT nacolSrc->isUpshiftReqd()) { |
| if (opSelf == ITM_ASSIGN) { |
| // Genesis 10-980402-1556: uppercase (or NULL) constant is ok |
| Upper *upSrc = new (bindWA->wHeap()) Upper(src); |
| upSrc->allowsSQLnullArg() = TRUE; // internal UPPER, it's ok |
| ie->setChild(srcIndex, upSrc->bindNode(bindWA)); |
| } |
| else { |
| // Genesis 10-980611-7115: |
| // CAST(expr AS CHAR(n) UPSHIFT) => UPPER(CAST(expr AS CHAR(n))) |
| CharType &ctSelf = (CharType &)ie->getValueId().getType(); |
| ctSelf.setUpshifted(FALSE); |
| Upper *upSrc = new (bindWA->wHeap()) Upper(ie); |
| ie = upSrc->bindNode(bindWA); |
| } |
| } // !isUpshiftReqd in src |
| } |
| } |
| |
| return ie; |
| } |
| |
| |
| // There are two metadata tables containing view-column-usage information, |
| // VW_COL_USAGE and VW_COL_TBL_COLS. |
| // |
| // VW_COL_USAGE is defined in page 472 of the ANSI SQL-92 standard |
| // as the Information Schema view VIEW_COLUMN_USAGE. |
| // VW_COL_USAGE contains information about all columns referenced in |
| // the view definition (excluding the columns in the view column list) |
| // so supporting it is pretty simple. |
| // |
| // VW_COL_TBL_COLS is for internal use by the catalog manager to support |
| // view column security; it is only used when the view is updatable. |
| // Supporting VW_COL_TBL_COLS is harder because we need to collect only |
| // certain (not all) referenced columns relating to each view column. |
| // Since we don't know whether the view is updatable or not until the |
| // normalization phase completes, we must blindly collect information for it.. |
| // Once we find out that the view is not updatable, we can throw this |
| // extra information away. It's still not clear what information |
| // needs to be collected and what not. So the code might still have |
| // problems, but hopefully it'll be more stable soon. In certain |
| // cases the binder can find out if the view is not updatable; for |
| // example, a view is not updatable if its query expression a natural |
| // join operation; the binder can stop collecting information for |
| // the VW_COL_TBL_COLS right away. |
| // |
| void |
| BindUtil_CollectColUsageForCommonCol(BindWA *const bindWA, |
| const NAString &commonColName, |
| const RelExpr *const tableRelExpr, |
| const ItemExpr *const columnItemExpr) |
| { |
| if (bindWA->getNameLocListPtr() == NULL) return; |
| ExprNode *pUsageParseNode = bindWA->getUsageParseNodePtr(); |
| if (pUsageParseNode == NULL) return; |
| |
| QualifiedName tblQualName; |
| |
| if (tableRelExpr->getOperatorType() == REL_RENAME_TABLE AND |
| ((RenameTable *)tableRelExpr)->isView()) // view |
| { |
| RenameTable *pRenameTab = (RenameTable *)tableRelExpr; |
| tblQualName = pRenameTab->getTableName().getQualifiedNameObj(); |
| } |
| else if (tableRelExpr->getOperatorType() == REL_SCAN) // table |
| { |
| Scan *pScan = (Scan*)tableRelExpr; |
| tblQualName = pScan->getTableName().getQualifiedNameObj(); |
| if (NOT tblQualName.fullyExpanded()) // is a renamed table |
| return; |
| } |
| else return; // does nothing |
| |
| ColRefName colRefName(commonColName, tblQualName); |
| |
| if (pUsageParseNode->getOperatorType() |
| == DDL_ALTER_TABLE_ADD_CONSTRAINT_CHECK) |
| { |
| StmtDDLAddConstraintCheck &pn = *pUsageParseNode->castToElemDDLNode()->castToStmtDDLAddConstraintCheck(); |
| ParCheckConstraintColUsageList &cul = pn.getColumnUsageList(); |
| cul.insert(colRefName, |
| bindWA->getCurrentScope()->context()->inSelectList()); |
| } |
| else if (pUsageParseNode->getOperatorType() == DDL_CREATE_VIEW) |
| { |
| StmtDDLCreateView &cvpn = *pUsageParseNode->castToElemDDLNode()->castToStmtDDLCreateView(); |
| ParViewTableColsUsageList &vcul = cvpn.getViewUsages(). |
| getViewTableColsUsageList(); |
| vcul.insert(colRefName); |
| |
| // The view is not updatable because it has common columns. |
| // This is one of a few cases that the binder knows whether the |
| // view is not updatable. See comments for method |
| // ParViewUsages::isViewSurelyNotUpdatable() for more information. |
| cvpn.getViewUsages().setViewIsSurelyNotUpdatableFlag(); |
| } |
| return; |
| |
| } // BindUtil_CollectColUsageForCommonCol() |
| |
| // |
| // Returns FALSE if colRefName does not contain adequate information. |
| // Returns TRUE if colRefName contains enough information to be added |
| // to a column-usage list. |
| // |
| static NABoolean |
| BindUtil_IsColUsgInfoOk(const ColRefName &colRefName) |
| { |
| if (colRefName.isFabricated()) |
| { |
| // |
| // column-usage information for common columns (e.g., in |
| // a natural join operation) has already been collected by |
| // routine BindUtil_CollectColUsageForCommonCol(). |
| // |
| return FALSE; |
| } |
| |
| const QualifiedName &qualName = colRefName.getCorrNameObj(). |
| getQualifiedNameObj(); |
| |
| if (colRefName.getColName().isNull() OR |
| qualName.getObjectName().isNull()) |
| { |
| // |
| // col name in GroupBy node created by parser |
| // for Select Distinct; for example: |
| // |
| // create view v as select distinct * from t; |
| // |
| return FALSE; |
| } |
| |
| if (qualName.getCatalogName().isNull() OR |
| qualName.getSchemaName().isNull()) |
| { |
| // |
| // ( query_expr ) corr_name |
| // |
| // For example: |
| // |
| // create view v as select * from (select c from t) x(d); |
| // |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static NABoolean BindUtil_CollectColumnUsageInfo(BindWA *bindWA, |
| const ColRefName &colRefName, |
| const ValueId vid, |
| RelExpr * parent) |
| { |
| ExprNode *pUsageParseNode = bindWA->getUsageParseNodePtr(); |
| if (pUsageParseNode == NULL) |
| { |
| return FALSE; // no, do not continue to collect usage information |
| } |
| |
| if (pUsageParseNode->getOperatorType() |
| == DDL_ALTER_TABLE_ADD_CONSTRAINT_CHECK) |
| { |
| StmtDDLAddConstraintCheck &pn = *pUsageParseNode |
| ->castToElemDDLNode()->castToStmtDDLAddConstraintCheck(); |
| ParCheckConstraintColUsageList &cul = pn.getColumnUsageList(); |
| if (BindUtil_IsColUsgInfoOk(colRefName)) |
| { |
| cul.insert(colRefName, |
| bindWA->getCurrentScope()->context()->inSelectList()); |
| } |
| return TRUE; |
| } |
| else if (pUsageParseNode->getOperatorType() == DDL_CREATE_VIEW) |
| { |
| StmtDDLCreateView &cvpn = *bindWA->getCreateViewParseNode(); |
| ParViewTableColsUsageList &vcul = cvpn.getViewUsages(). |
| getViewTableColsUsageList(); |
| |
| if (BindUtil_IsColUsgInfoOk(colRefName)) |
| { |
| vcul.insert(colRefName); |
| } |
| |
| if (cvpn.isProcessingViewColList()) |
| { |
| ParViewColTableColsUsageList &vctcul = cvpn.getViewUsages(). |
| getViewColTableColsUsageList(); |
| |
| if (NOT colRefName.isFabricated() AND // com col info already collected |
| NOT cvpn.getViewUsages().isViewSurelyNotUpdatable()) |
| { |
| const QualifiedName &qualName = colRefName.getCorrNameObj(). |
| getQualifiedNameObj(); |
| |
| if (BindUtil_IsColUsgInfoOk(colRefName)) |
| { |
| vctcul.insert(cvpn.getCurViewColNum(), colRefName); |
| } |
| else if (qualName.getObjectName().isNull() AND |
| NOT colRefName.getCorrNameObj().getCorrNameAsString().isNull() |
| AND NOT colRefName.getColName().isNull() AND |
| cvpn.isItmColRefInColInRowVals() AND |
| parent == cvpn.getQueryExpression()) |
| { |
| // renamed_table.column case |
| // for example: x.d in the following statement |
| // create view v as select * from (select c from t) x(d); |
| // needs to map to t.c (a persistent object) |
| |
| // traverses down the parse tree, skipping the RenameTable |
| // parse nodes, until reaching a RenameTable parse node for |
| // a referenced view or (hopefully) a base table. |
| |
| // QSTUFF |
| // at this time we know that the view is updatable |
| // previously an updatable view ended up to be a RelRoot |
| // followed by a sequence of (Rename,RelRoot) pairs. |
| // At the bottom of the view we either find a Scan |
| // or a "RenameTable" inserted by view expansion. |
| // When allowing emdedded generic updates we may see |
| // the following combination at the bottom of a |
| // view (RenameTable,RelRoot,GenericUpdate). If we |
| // encounter this combination we need to skip the |
| // GenericUpdate to get to the base table scan or |
| // RenameTable inserted by view expansion below |
| // the GenericUpdate. |
| // RelExpr *result = parent->child(0); // table in FROM clause |
| |
| NABoolean isTopLevelUpdateInView = |
| (parent->getGroupAttr()->isEmbeddedUpdate() && (NOT bindWA->inViewExpansion())); |
| |
| // in case of a top level update within a view definition we really |
| // want the RETDesc of the generic update as it contains both, the |
| // valueids of the scan table and the valueids of the update base table. |
| RelExpr *result = parent->getViewScanNode(isTopLevelUpdateInView); |
| |
| if (result) |
| { |
| const ValueIdList & updateValueIds = |
| bindWA->getUpdateToScanValueIds().getTopValues(); |
| const ValueIdList & scanValueIds = |
| bindWA->getUpdateToScanValueIds().getBottomValues(); |
| ValueId mappedVid = vid; |
| |
| for (CollIndex i = 0; i < updateValueIds.entries(); i++){ |
| if (vid == updateValueIds[i]){ |
| mappedVid = scanValueIds[i]; |
| break; |
| } |
| } |
| |
| // gets the fully-qualified column name (of the referenced |
| // view or base table) if it exists. |
| ColumnNameMap *cnm = result->getRETDesc()->findColumn(mappedVid); |
| // QSTUFF |
| |
| if (cnm AND |
| NOT cnm->getColumnDesc()->getColRefNameObj().isFabricated()) |
| { |
| vctcul.insert(cvpn.getCurViewColNum(), |
| cnm->getColumnDesc()->getColRefNameObj()); |
| } |
| } // if (result) |
| } // else if (rename-table.column-name AND ...) |
| } // if (column-name is not fabricated AND ...) |
| |
| if (cvpn.isItmColRefInColInRowVals() AND |
| parent == cvpn.getQueryExpression()) |
| { |
| // only increment the counter if the column in the bindRowValues() |
| // is a column reference or a star column references; for example, |
| // create view v as select a, t1.*, b+2 from t1,t2; |
| // in the above example, when we process a (or t1.*) here, we |
| // need to increment the counter here. When we process b (in the |
| // expression b+2), we do not increment the counter (the counter will |
| // be incremented later in routine ItemExpr::convertToValueIdList(). |
| cvpn.incrCurViewColNum(); |
| } |
| } |
| return TRUE; |
| } |
| return FALSE; |
| } // BindUtil_CollectColumnUsageInfo() |
| |
| static void BindUtil_UpdateNameLocForColRef(BindWA *bindWA, |
| ColRefName &colRefName, |
| ColumnNameMap *colNameMap, |
| RelExpr *parent) |
| { |
| ParNameLocList *pNameLocList = bindWA->getNameLocListPtr(); |
| if (pNameLocList == NULL) return; |
| CMPASSERT(NOT colRefName.isStar()); |
| CMPASSERT(colNameMap); |
| |
| // note that the name of a common column (e.g., in a natural |
| // join operation) may not be qualified so qualName and |
| // qualNameExpanded may be empty. |
| const ColRefName &colRefNameExpanded = |
| colNameMap->getColumnDesc()->getColRefNameObj(); |
| const QualifiedName &qualNameExpanded = |
| colRefNameExpanded.getCorrNameObj().getQualifiedNameObj(); |
| |
| CorrName &corrName = colRefName.getCorrNameObj(); |
| QualifiedName &qualName = corrName.getQualifiedNameObj(); |
| |
| ParNameLoc *pNameLoc = pNameLocList->getNameLocPtr( |
| colRefName.getNamePosition()); |
| // -- Triggers |
| //CMPASSERT(pNameLoc); |
| if (pNameLoc == NULL) |
| return; |
| |
| // Note that qualName can be empty when column is a common |
| // column (e.g. in a natural join operation). Common columns |
| // is handled in routine joinCommonColumns(). |
| // |
| if (qualName.fullyExpanded()) |
| { |
| pNameLoc->setExpandedName(colRefName.getColRefAsAnsiString()); |
| } |
| else if (qualNameExpanded.fullyExpanded()) |
| { |
| // Expand colRefName's qualified name, without modifying any overlying |
| // correlation name; and if no overlying corr name, also expand the |
| // name loc to four-part col ref ("c.s.t.col"). |
| // |
| qualName.setObjectName(qualNameExpanded.getObjectName()); |
| if (NOT qualName.getObjectName().isNull()) |
| qualName.applyDefaults(qualNameExpanded); |
| if (corrName.getCorrNameAsString().isNull()) |
| { |
| corrName.setCorrName(colRefNameExpanded.getCorrNameObj().getCorrNameAsString()); |
| pNameLoc->setExpandedName(colRefNameExpanded.getColRefAsAnsiString()); |
| } |
| } |
| else if (!colRefNameExpanded.getCorrNameObj().getCorrNameAsString().isNull()) |
| { |
| pNameLoc->setExpandedName(colRefNameExpanded.getColRefAsAnsiString()); |
| } |
| |
| // Note that colRefNameExpanded might not be fully qualified in |
| // certain cases (for example, the column is a derived column, |
| // not a catalog object). These should not be put into the usage |
| // list. The routine BindUtil_CollectColumnUsageInfo will take |
| // care of these cases. |
| // |
| // Example: The following query generates a derived column: |
| // create view v as select a from (select x + y from t1) as t(a) |
| |
| BindUtil_CollectColumnUsageInfo(bindWA, colRefNameExpanded, |
| colNameMap->getValueId(), parent); |
| |
| } // BindUtil_UpdateNameLocForColRef() |
| |
| static void BindUtil_UpdateNameLocForStarExpansion |
| (BindWA *bindWA, |
| const ColumnDescList &colDescList, |
| const StringPos starPos, |
| RelExpr *parent) |
| { |
| ParNameLocList *pNameLocList = bindWA->getNameLocListPtr(); |
| if (pNameLocList == NULL) return; |
| if (starPos) |
| { |
| ParNameLoc *pNameLoc = pNameLocList->getNameLocPtr(starPos); |
| CMPASSERT(pNameLoc); |
| // tries to handle the following case |
| // create view v as select * from (select * from (values (1), (2)) x |
| NABoolean isStarWithoutColNames = FALSE; |
| for (CollIndex i = 0; i < colDescList.entries(); i++) |
| { |
| if (colDescList[i]->getColRefNameObj().getColName().isNull()) |
| { |
| isStarWithoutColNames = TRUE; |
| break; |
| } |
| } |
| if (isStarWithoutColNames) |
| pNameLoc->setExpandedName("*"); |
| else |
| pNameLoc->setExpandedName(colDescList.getColumnDescListAsString()); |
| } |
| |
| for (CollIndex i = 0; i < colDescList.entries(); i++) |
| if (NOT BindUtil_CollectColumnUsageInfo(bindWA, |
| colDescList[i]->getColRefNameObj(), |
| colDescList[i]->getValueId(), |
| parent)) |
| break; |
| |
| } // BindUtil_UpdateNameLocForStarExpansion() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ItemExpr |
| // ----------------------------------------------------------------------- |
| void ItemExpr::bindChildren(BindWA *bindWA) |
| { |
| Int32 savedCurrChildNo = currChildNo(); |
| for (Int32 i = 0; i < getArity(); i++, currChildNo()++) { |
| |
| ItemExpr *boundExpr = child(i)->bindNode(bindWA); |
| if (bindWA->errStatus()) return; |
| child(i) = boundExpr; |
| |
| |
| } |
| currChildNo() = savedCurrChildNo; |
| } |
| |
| // Within the loop here, currChildNo() is an index or position. |
| // After the loop (it does not get reset), it is an index one past the max, |
| // i.e. it is the degree of this ItemExpr -- which will equal arity for all(?) |
| // items except ItemList (arity 2, arbitrary degree). |
| // If this ItemExpr returns a node different than 'this' in its bindNode, |
| // its bindNode must explicitly save this currChildNo degree if needed. |
| // |
| void ItemExpr::bindSelf(BindWA *bindWA) |
| { |
| if (nodeIsBound()) return; |
| |
| // defaultSpecBoundAsSQLnull is needed only for our Tandem extension |
| // of allowing DEFAULT to appear in item expressions |
| // *except* when the DEFAULT value is NULL: |
| // INSERT INTO t(a,b) VALUES(DEFAULT,DEFAULT+1) |
| // -- DEFAULT for A is ok always, even if A is DEFAULT NULL, |
| // -- DEFAULT+1 for B is ok, *unless* B is DEFAULT NULL. |
| // Couldn't catch the disallowed NULL+1 earlier in checkForSQLnullChild |
| // because didn't know DEFAULT was NULL then, but no big deal, since |
| // DEFAULT+1 and its ilk are allowed only as a Tandem extension anyway. |
| // |
| NABoolean defaultSpecBoundAsSQLnull = FALSE; |
| |
| for (Int32 i = 0; i < getArity(); i++, currChildNo()++) { |
| |
| ItemExpr *boundExpr = child(i)->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return; |
| if (! boundExpr) |
| { |
| bindWA->setErrStatus(); |
| return; |
| } |
| |
| if (boundExpr->getOperatorType() == ITM_CONSTANT && |
| ((ConstValue *)boundExpr)->isNullWasDefaultSpec()) |
| defaultSpecBoundAsSQLnull = TRUE; |
| |
| child(i) = boundExpr; |
| |
| } |
| |
| //##? Should getOperatorType() != ITM_CAST ?## |
| //##? be !getOperator().match(ITM_ANY_CAST) ?## |
| if (defaultSpecBoundAsSQLnull) |
| if (getOperatorType() != ITM_ASSIGN && |
| getOperatorType() != ITM_CAST && |
| getOperatorType() != ITM_EXPLODE_VARCHAR && // Genesis 10-980402-1556 |
| getOperatorType() != ITM_ITEM_LIST) |
| checkForSQLnullChild(bindWA, this, FALSE, DEFAULT_BOUND_AS_NULL_); |
| |
| markAsBound(); |
| } // ItemExpr::bindSelf() |
| |
| |
| // Relaxation on an item expression is necessary if some of its |
| // character-typed children are associated with character set values such |
| // that the ANSI character type matching rule is violated for the expression. |
| // |
| // Relaxation can be done for an item expression, if it is either |
| // 1) a character hostvar with UCS2 charset, or |
| // 2) a SQL string function that returns an UCS2 result, and all |
| // character-typed operands of the function are relaxable. |
| // |
| // To perform the relaxation for a non-Translate relexable expression, add |
| // a translate node (UCS2->ISO88591) on top of it. For a translate node, |
| // simply remove the expression. |
| // |
| // Assume hostvar a, b and c are defined as |
| // char CHARACTER SET IS UCS2 a[10] |
| // char CHARACTER SET IS UCS2 b[1] |
| // char CHARACTER SET IS ISO88591 c[1] |
| // |
| // Examples of relexable item expressions: |
| // :a // :a is an UCS2 hostvar |
| // :b // :b is an UCS2 hostvar |
| // trim(:a) // trim(:a) produces an UCS2 resulta, :a is relaxable. |
| // upper(:a) // upper(:a) produces an UCS2 result, :a is relaxable. |
| // repeat(:b, 10) // repeat(:b, 10) produces an UCS2 result, |
| // // :b (the only character operand of the function) |
| // // is relaxable. |
| // |
| // Examples of non-relexable item expressions: |
| // :c // :c is not an UCS2 host variable. |
| // concat(:a, 'b') // 'b' is not a relaxable child of concat(). |
| // replace(:a, _ucs2'b', _ucs2'z') // both _ucs2'b' and _ucs2'z'are not |
| // // relaxable. |
| // |
| |
| // A generic routine to relax ANSIcharacter-type matching rule for Static Inputs |
| // |
| // Always return this. |
| ItemExpr* ItemExpr::tryToRelaxCharTypeMatchRules(BindWA *bindWA) |
| { |
| |
| Int32 ucs2s = 0; |
| CharInfo::CharSet cs = CharInfo::UnknownCharSet; |
| |
| // determine whether the ANSI character type matching rule is violated |
| for (Int32 i = 0; i < getArity(); i++) { |
| |
| const NAType& type = child(i)->getValueId().getType(); |
| |
| if ( type.getTypeQualifier() == NA_CHARACTER_TYPE ) |
| { |
| switch (((const CharType&)type).getCharSet()) |
| { |
| case CharInfo::UNICODE : |
| ucs2s++; |
| break; |
| |
| case CharInfo::KANJI_MP: |
| case CharInfo::KSC5601_MP: |
| // can not perform relaxation on non-ISO88591 charsets. |
| return this; |
| |
| case CharInfo::ISO88591: |
| default: |
| if (cs == CharInfo::UnknownCharSet) |
| cs = ((CharType&)type).getCharSet(); |
| break; |
| } |
| } |
| } |
| |
| if ( cs != CharInfo::UnknownCharSet && ucs2s > 0 ) |
| // ANSI rule is violated, try to perform relaxation |
| return performRelaxation(cs, bindWA); |
| else |
| // relaxation not needed |
| return this; |
| } |
| |
| // Always return this. |
| ItemExpr* ItemExpr::performRelaxation(CharInfo::CharSet cs, BindWA *bindWA) |
| { |
| // perofrm relaxation on any relaxable child |
| for (Int32 i = 0; i < (Int32) currChildNo(); i++) { |
| if ( ((ItemExpr*)child(i)) -> isCharTypeMatchRulesRelaxable() ) |
| { |
| // for R2 FCS, the target is fixed at 88591. With Phase II work, it is |
| // possible to set the target charset in Translator cstr. |
| |
| ItemExpr * newTranslateChild = |
| new (bindWA->wHeap()) Translate(child(i), Translate::UNICODE_TO_ISO88591); |
| |
| newTranslateChild = newTranslateChild->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| setChild(i, newTranslateChild); |
| } |
| } |
| return this; |
| } |
| |
| |
| Int32 find_translate_type( CharInfo::CharSet src_cs, // Source charset |
| CharInfo::CharSet dest_cs ) // Destination charset |
| { |
| Int32 tran_type = Translate::UNKNOWN_TRANSLATION; |
| |
| switch( dest_cs ) |
| { |
| case CharInfo::ISO88591: |
| switch( src_cs ) |
| { |
| case CharInfo::UCS2: |
| tran_type = Translate::UNICODE_TO_ISO88591; |
| break; |
| case CharInfo::UTF8: |
| tran_type = Translate::UTF8_TO_ISO88591; |
| break; |
| case CharInfo::SJIS: |
| // tran_type = Translate::SJIS_TO_ISO88591; |
| break; |
| } |
| break; |
| case CharInfo::UCS2: |
| switch( src_cs ) |
| { |
| case CharInfo::UTF8: |
| tran_type = Translate::UTF8_TO_UCS2; |
| break; |
| case CharInfo::ISO88591: |
| tran_type = Translate::ISO88591_TO_UNICODE; |
| break; |
| case CharInfo::SJIS: |
| tran_type = Translate::SJIS_TO_UCS2; |
| break; |
| } |
| break; |
| case CharInfo::UTF8: |
| switch( src_cs ) |
| { |
| case CharInfo::UCS2: |
| tran_type = Translate::UCS2_TO_UTF8; |
| break; |
| case CharInfo::ISO88591: |
| tran_type = Translate::ISO88591_TO_UTF8; |
| break; |
| case CharInfo::SJIS: |
| tran_type = Translate::SJIS_TO_UTF8; |
| break; |
| } |
| break; |
| case CharInfo::SJIS: |
| switch( src_cs ) |
| { |
| case CharInfo::UCS2: |
| tran_type = Translate::UCS2_TO_SJIS; |
| break; |
| case CharInfo::ISO88591: |
| // tran_type = Translate::ISO88591_TO_SJIS; |
| break; |
| case CharInfo::UTF8: |
| tran_type = Translate::UTF8_TO_SJIS; |
| break; |
| } |
| break; |
| } |
| return tran_type; |
| } |
| |
| // |
| // For each character child node (of node pointed to by 'this') that has |
| // a different character set than the 'cs' argument, we must do something |
| // to convert the child to the 'cs' character set. |
| // |
| // For children that are character string constants, we can do the actual |
| // translation here in the Compiler (UTF8->UCS2 or UCS2->UTF8). If we |
| // later want to do something similar for the SJIS or ISO88591 configurations, |
| // we might have to restrict this and do the translation here in the compiler |
| // ONLY IF the string is all ASCII characters or something like that. |
| // For Seaquest platform (using the True ISO88591 configuration), the |
| // translation is between ISO88591 and UCS2, and we issue error messages |
| // when the translation fails. |
| // |
| // For children that are Translate nodes, we may be able to *delete* the |
| // Translate node in order to get the correct character set. |
| // |
| // For some child nodes that are functions, we may be able to push the |
| // translation operation down to the children of those functions. We |
| // expecially want to do this if the children of those functions are |
| // string constant nodes. |
| // |
| // For all other cases, we insert a Translate node between the 'this' |
| // node and the child node. |
| // |
| // Always return this. |
| // |
| ItemExpr* ItemExpr::performImplicitCasting(CharInfo::CharSet cs, BindWA *bindWA) |
| { |
| if ( getOperatorType() == ITM_INSTANTIATE_NULL || |
| getOperatorType() == ITM_BITMUX ) // Don't want translate node |
| return this; // below one of these. |
| |
| |
| ItemExpr *result = this; |
| |
| // If we are dealing with an expression that already has been assigned a ValueId, |
| // it may be shared with other ItemExpr trees, therefore we need to make a copy |
| // and can't modify it or its children. |
| // Note that when we call this method from tryToDoImplicitCasting(), "this" |
| // is an expression that does not yet have a value id. In this case, the |
| // method will return "this", but maybe with modified children. |
| if (getValueId() != NULL_VALUE_ID) |
| result = copyTopNode(); |
| |
| for (Int32 i = 0; i < getArity(); i++) |
| { |
| // initialize the result's child, assuming for now that it won't change |
| if (result != this) |
| result->setChild(i, getChild(i)); |
| |
| const NAType& type = child(i)->getValueId().getType(); |
| if ( type.getTypeQualifier() != NA_CHARACTER_TYPE ) |
| continue; // Skip non-char types |
| |
| CharInfo::CharSet child_cs = ((const CharType&)type).getCharSet() ; |
| if ( child_cs != CharInfo::ISO88591 && child_cs != CharInfo::UNICODE && |
| child_cs != CharInfo::UTF8 && child_cs != CharInfo::SJIS ) |
| continue; |
| |
| // If child is already in desired charset, then skip it. |
| if ( child_cs == cs ) |
| continue; |
| |
| CharInfo::CharSet desired_cs = cs; |
| |
| enum cnv_charset eCnvCS = convertCharsetEnum(desired_cs); |
| enum cnv_charset eCnvChild_cs = convertCharsetEnum(child_cs); |
| |
| Int32 tran_type = find_translate_type( child_cs, desired_cs ); |
| |
| if ( tran_type == Translate::UNKNOWN_TRANSLATION ) |
| continue; |
| |
| // Remove a Translate node when appropriate (insteading of adding |
| // another one to undo the existing one.) |
| // |
| OperatorTypeEnum chld_opertyp = child(i)->getOperatorType(); |
| if ( chld_opertyp == ITM_TRANSLATE ) |
| { |
| Translate * Trans_node = (Translate *) ( child(i)->castToItemExpr() ); |
| ItemExpr * grandchild = Trans_node->child(0); |
| |
| const NAType& gr_type = grandchild->getValueId().getType(); |
| CharInfo::CharSet gr_child_cs = ((const CharType&)gr_type).getCharSet() ; |
| |
| if ( gr_child_cs == cs ) // If true, delete Translate node! |
| { |
| // Make the copy become this Translate Node's child |
| result->setChild(i, grandchild); |
| continue; //Go on to deal with next child |
| } |
| |
| } // end: if child(i) is an ITM_TRANSLATE |
| |
| else if ( chld_opertyp == ITM_CONSTANT ) |
| { |
| NABoolean cv_is_NULL ; |
| Lng32 cv_StorageSize ; |
| const char * cv_ConstValue; |
| ValueId cv_ValueId ; |
| |
| ConstValue * new_cv ; |
| |
| ConstValue * cv ; |
| cv = (ConstValue*)(child(i)->castToItemExpr()); |
| cv_is_NULL = cv->isNull(); |
| cv_StorageSize = cv->getStorageSize() - cv->getType()->getVarLenHdrSize(); |
| cv_ConstValue = (const char *)(cv->getConstValue()); |
| cv_ValueId = cv->getValueId(); |
| |
| if ( cv_is_NULL ) |
| { |
| new_cv = new (bindWA->wHeap()) ConstValue() ; |
| } |
| else |
| { |
| // First find length of constant string in characters. |
| // Also find length of result string in bytes. |
| // |
| Int32 byte_offset = 0; |
| Int32 cv_len_in_chars = 0; |
| Int32 rslt_len_in_bytes = 0; |
| while ( byte_offset < cv_StorageSize ) |
| { |
| Int32 firstCharLenInBuf; |
| UInt32 UCS4value; |
| firstCharLenInBuf = LocaleCharToUCS4(&cv_ConstValue[byte_offset], |
| cv_StorageSize - byte_offset, |
| &UCS4value, |
| eCnvChild_cs); |
| if(firstCharLenInBuf < 0) |
| { |
| *CmpCommon::diags() << DgSqlCode(-2109) |
| << DgString0(CharInfo::getCharSetName(child_cs)) |
| << DgString1("UCS4") |
| << DgInt0(cv_len_in_chars) |
| << DgInt1((Int32)(byte_offset)); |
| bindWA->setErrStatus(); |
| break; |
| } |
| byte_offset += firstCharLenInBuf; |
| cv_len_in_chars++; |
| |
| Int16 tmpBuf[4]; |
| Int32 len_in_bytes = UCS4ToLocaleChar(&UCS4value, |
| (char *)tmpBuf, 8, |
| eCnvCS); |
| if (len_in_bytes < 0) |
| { |
| *CmpCommon::diags() << DgSqlCode(-2109) |
| << DgString0(CharInfo::getCharSetName(child_cs)) |
| << DgString1(CharInfo::getCharSetName(desired_cs)) |
| << DgInt0(cv_len_in_chars) |
| << DgInt1((Int32)(byte_offset)); |
| bindWA->setErrStatus(); |
| break; |
| } |
| rslt_len_in_bytes += len_in_bytes; |
| } |
| // |
| // Now we know the exact length of the output buffer and |
| // the length of the string (in chars!) |
| // |
| Int32 out_length = rslt_len_in_bytes + |
| ( (desired_cs == CharInfo::UNICODE) ? 2 : 1 ); |
| char * tmpbufr = new(bindWA->wHeap()) char[out_length]; |
| |
| Int32 rslt_len = 0; |
| |
| byte_offset = 0; |
| while ( byte_offset < cv_StorageSize ) |
| { |
| Int32 firstCharLenInBuf; |
| UInt32 UCS4value; |
| firstCharLenInBuf = LocaleCharToUCS4(&cv_ConstValue[byte_offset], |
| cv_StorageSize - byte_offset, |
| &UCS4value, |
| eCnvChild_cs); |
| if(firstCharLenInBuf < 0) |
| break; // Error would have already been reported above |
| |
| byte_offset += firstCharLenInBuf; |
| Int32 len_in_bytes = UCS4ToLocaleChar(&UCS4value, |
| tmpbufr + rslt_len, |
| 8, eCnvCS); |
| if (len_in_bytes < 0) |
| break; // Error would have already been reported above |
| |
| rslt_len += len_in_bytes; |
| } |
| |
| if ( desired_cs == CharInfo::UCS2 ) |
| { |
| NAWString tmpStr; |
| // Instead of using |
| // NAWString tmpStr = (NAWchar *)tmpbufr; |
| // use NAWString::append() method in case tmpbufr contains |
| // many consecultive embedded binary zero bytes that |
| // look like the NAWchar NULL terminator(s). |
| tmpStr.append((NAWchar *)tmpbufr, (size_t)cv_len_in_chars); |
| NADELETEBASIC(tmpbufr, bindWA->wHeap()); tmpbufr = NULL; |
| new_cv = new (bindWA->wHeap()) ConstValue( tmpStr, |
| CharInfo::UNICODE, CharInfo::DefaultCollation, CharInfo::COERCIBLE ); |
| } |
| else |
| { |
| *(tmpbufr + rslt_len) = 0; // Add a trailing Null |
| NAString tmpStr = tmpbufr; |
| NADELETEBASIC(tmpbufr, bindWA->wHeap()); tmpbufr = NULL; |
| new_cv = new (bindWA->wHeap()) ConstValue( tmpStr, |
| desired_cs, CharInfo::DefaultCollation, CharInfo::COERCIBLE); |
| } |
| } |
| |
| ItemExpr * new_chld_ie = new_cv->bindNode(bindWA); |
| if ( cv_is_NULL ) |
| { |
| CharType myCharType = (const CharType&)type; |
| Int32 bytesPerCh = myCharType.getBytesPerChar(); |
| NAType * newType = new (HEAP) SQLChar(HEAP, |
| ( cv_ValueId == NULL_VALUE_ID ) |
| ? 0 : type.getNominalSize()/bytesPerCh, |
| TRUE, FALSE, FALSE, FALSE, |
| cs, // The target charset |
| CharInfo::DefaultCollation, |
| CharInfo::COERCIBLE ); |
| ValueId theId = new_cv->getValueId(); |
| theId.coerceType(*newType); |
| new_cv->changeType(newType); |
| } |
| |
| ConstValue* new_chld_cv = dynamic_cast<ConstValue*>(new_chld_ie); |
| |
| CURRENTQCACHE->getHQC() |
| ->collectBinderRetConstVal4HQC |
| ((ConstValue*)(child(i)->castToItemExpr()), new_chld_cv); |
| |
| result->setChild(i, new_chld_ie); |
| |
| continue; //Go on to deal with next child |
| |
| } // end: ITM_CONSTANT |
| |
| else if ( child(i)->getArity() > 0 ) // If it has child nodes |
| { |
| if ( child(i)->shouldPushTranslateDown(cs) >= 0 ) |
| { |
| result->setChild(i, child(i)->performImplicitCasting(cs, bindWA)); |
| continue; //Go on to deal with next child |
| |
| } // end: if ( child(i)->shouldPushTranslateDown(cs) >= 0 ) |
| } // end: if ( child(i)->getArity() > 0 ) |
| |
| ItemExpr * newTranslateChild = |
| new (bindWA->wHeap()) Translate(child(i), tran_type ); |
| |
| newTranslateChild = newTranslateChild->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| result->setChild(i, newTranslateChild); |
| |
| } //end of for loop (for each child node) |
| |
| if ( result->getOperatorType() == ITM_CAST ) |
| { |
| // Must fix up the Cast's target type field |
| const NAType *targetType = ((Cast *)result)->getType(); |
| NAType * newCastType = targetType->newCopy(bindWA->wHeap()); |
| CharType * newChCastType = (CharType *) newCastType; |
| newChCastType->setCharSet(cs) ; |
| newChCastType->setBytesPerChar(CharInfo::maxBytesPerChar(cs)) ; |
| ((Cast *)result)->changeType(newCastType); |
| } |
| |
| if (result != this && nodeIsBound() && getValueId() != NULL_VALUE_ID) |
| { |
| // if this node is already bound (not just marked as bound, but completely |
| // bound with a value id assigned), then bind the new result as well |
| result->bindNode(bindWA); |
| } |
| |
| return result; |
| } |
| |
| // |
| // This method tries to determine whether or not it is a good idea |
| // to push the Translate operation down to the children of the 'this' |
| // node. Returns: |
| // + num if this is a good idea |
| // 0 if this is not a good idea |
| // - num if this is a bad idea (or not even possible) |
| // |
| Int32 ItemExpr::shouldPushTranslateDown(CharInfo::CharSet chrset) const |
| { |
| Int32 Goodness = 1; // Good if number of Translate Nodes would not increase |
| NABoolean sawChildWithDiffCS = FALSE; |
| |
| // Exclude all ItemExprs with arity > 1 that possibly produce character |
| // output, have children with possibly a character type, and have at least |
| // one of the following: |
| // |
| // a) A fixed output charset, so pushing the translate down would |
| // have no effect. Examples: CAST, ENCODE. |
| // b) The semantics of the operator may depend on the character set. |
| // Examples: MIN, MAX, LIKE. |
| // c) the operator isn't well-enough understood to push the translate |
| // down below it. Example: ITM_USER_DEF_FUNCTION. |
| // d) Other reasons. |
| |
| // For any ItemExpr do not push a CAST node down the ItemExpr tree |
| // if the ItemExpr itself is the output of a groupby. This is needed |
| // because the components of a grouped expression are likely not available |
| // above the groupby. Pushing the CAST down a group expression forces |
| // the generator to look for parts of the group exptresion in nodes where |
| // they are typically not available. |
| if (isGroupByExpr()) |
| return -1 ; |
| |
| switch (getOperatorType()) |
| { |
| case ITM_BITMUX: // d) mixes separate item expressions together |
| case ITM_MAX: // b) ordering, also may eliminate data |
| case ITM_MIN: // b) ordering, also may eliminate data |
| case ITM_MAX_ORDERED: // b) ordering, also may eliminate data |
| case ITM_MIN_ORDERED: // b) ordering, also may eliminate data |
| case ITM_USER_DEF_FUNCTION: // c) may or may not depend on charset |
| case ITM_COMP_ENCODE: // a), b) output is binary disguised as ISO |
| case ITM_COMP_DECODE: // a), b) output is binary disguised as ISO |
| case ITM_MOVING_MAX: // b) possibly different orderings |
| case ITM_MOVING_MIN: // b) possibly different orderings |
| case ITM_SCALAR_MIN: // b) ordering, also may eliminate data |
| case ITM_SCALAR_MAX: // b) ordering, also may eliminate data |
| case ITM_OLAP_MAX: // b) ordering, also may eliminate data |
| case ITM_OLAP_MIN: // b) ordering, also may eliminate data |
| case ITM_CONVERTTOHEX: // b) operator depends on encoding |
| case ITM_CONVERTFROMHEX: // b) produces a specific encoding |
| case ITM_TOKENSTR: // b) implementation assumes all same |
| case ITM_SUBSTR_DOUBLEBYTE: // b) operator specific to UCS2 |
| case ITM_LIKE_DOUBLEBYTE: // b) operator specific to UCS2 |
| case ITM_UPPER_UNICODE: // b) operator specific to UCS2 |
| case ITM_LOWER_UNICODE: // b) operator specific to UCS2 |
| case ITM_REPLACE_UNICODE: // b) operator specific to UCS2 |
| case ITM_INSTANTIATE_NULL: // d) caused test failures |
| case ITM_QUERYID_EXTRACT: // a) output is always ISO88591 |
| case ITM_NARROW: // b) NARROW may want to catch conversion errors |
| case ITM_CONVERT: // a) internal node, too late to do ICAT |
| case ITM_CAST: // a) output is of a specific charset |
| case ITM_CAST_CONVERT: // a) internal node, too late to do ICAT |
| case ITM_CAST_TYPE: |
| case ITM_DATEFORMAT: |
| case ITM_REVERSE: |
| return -1; |
| |
| case ITM_LEFT: // b) counts characters |
| case ITM_RIGHT: // b) counts characters |
| case ITM_LIKE: // b) counts characters |
| case ITM_REGEXP: // b) counts characters |
| case ITM_SUBSTR: // b) counts characters |
| case ITM_REPLACE: // b) counts characters |
| case ITM_INSERT_STR: // b) counts characters |
| { |
| // Some operators produce subtle differences when used on UTF-16 surrogate |
| // pairs instead of 4-byte UTF-8 characters. Don't open that can of worms. |
| const NAType& myType = getValueId().getType(); |
| CharInfo::CharSet myCharset = CharInfo::UnknownCharSet; |
| |
| if (myType.getTypeQualifier() == NA_CHARACTER_TYPE) |
| myCharset = ((const CharType &) myType).getCharSet(); |
| |
| if ((chrset == CharInfo::UCS2 && |
| myCharset == CharInfo::UTF8) |
| || |
| (myCharset == CharInfo::UCS2 && |
| chrset == CharInfo::UTF8)) |
| return -1; |
| } |
| |
| default: |
| ; // go on, it's ok to push the translate operator down |
| } |
| |
| for (Int32 ii = 0 ; ii < getArity(); ii++ ) |
| { |
| const NAType& type = child(ii)->getValueId().getType(); |
| if ( type.getTypeQualifier() != NA_CHARACTER_TYPE ) |
| continue; // Skip non-char types |
| |
| if ( ((const CharType&)type).getCharSet() == chrset ) |
| continue; // Skip children with desired character set |
| |
| sawChildWithDiffCS = TRUE; |
| |
| switch ( child(ii)->getOperatorType() ) |
| { |
| case ITM_CONSTANT: // Node should be changed to new character set. |
| Goodness++ ; // and may allow optimizer to eval at Compile time |
| continue; |
| case ITM_TRANSLATE: |
| Goodness++ ; // Good since node would be removed if we did push down. |
| continue; |
| |
| case ITM_BASECOLUMN: |
| case ITM_VALUEIDUNION: |
| case ITM_ROW_SUBQUERY: |
| case ITM_IN_SUBQUERY: |
| Goodness-- ; // Bad since we would add a Translate node for this. |
| continue; |
| |
| default: |
| Int32 chld_goodness = child(ii)->shouldPushTranslateDown(chrset) ; |
| if ( chld_goodness < 0 ) |
| Goodness = 0; // Cannot push down any lower (at least for now) |
| else if ( chld_goodness > 1 ) |
| Goodness++ ; |
| continue; |
| } |
| } |
| if (sawChildWithDiffCS == FALSE ) |
| Goodness = -1; //Pushing Translate down is not possible |
| return ( Goodness ); |
| } |
| |
| // A special routine to relax ANSI character-type matching rule for binary comparison |
| // operators for Static Inputs, due to special internal representation for such |
| // operators. |
| // |
| // Aleays return this. |
| ItemExpr* BiRelat::tryToRelaxCharTypeMatchRules(BindWA *bindWA) |
| { |
| ItemExpr *x = (ItemExpr*)child(0); |
| ItemExpr *xy= (ItemExpr*)child(1); |
| |
| // if both children are not list type, just relax both |
| if (x-> getOperatorType() != ITM_ITEM_LIST && |
| xy-> getOperatorType() != ITM_ITEM_LIST |
| ) |
| return ItemExpr::tryToRelaxCharTypeMatchRules(bindWA); |
| |
| // handle the comparison bwt two lists |
| if ( x-> getOperatorType() == ITM_ITEM_LIST && |
| xy-> getOperatorType() == ITM_ITEM_LIST ) |
| { |
| NAMemory *heap = CmpCommon::statementHeap(); |
| |
| // collect the leaves from the two trees into two lists |
| ExprValueIdList* leafList1 = |
| ((ItemList*)(x)) -> collectLeaves(heap); |
| ExprValueIdList* leafList2 = |
| ((ItemList*)(xy)) -> collectLeaves(heap); |
| |
| // can not relax if both lists are not of same length |
| if ( leafList1 -> entries() != leafList2 -> entries() ) |
| return this; |
| |
| // relax by pairs |
| for ( Int32 i=0; i<leafList1 -> entries(); i++ ) { |
| if ( performRelaxation((*leafList1)[i], (*leafList2)[i], bindWA) == FALSE ) |
| return this; // If one pair can not be made to be type-compatible, return |
| // right away. The type synthesise code will flag the |
| // type mismatch error. |
| } |
| } |
| return this; |
| } |
| |
| // This routine is a helper function to BiRelat::tryToRelaxCharTypeMatchRules(). |
| // It linearizes the item expression tree by collecting the address of leave |
| // nodes into a list. The list is returned. |
| ExprValueIdList* |
| ItemList::collectLeaves(CollHeap* heap, ExprValueIdList* list) |
| { |
| if ( list == NULL ) |
| list = new (heap) ExprValueIdList(heap); |
| |
| for ( Int32 i=0; i<getArity(); i++ ) { |
| |
| ItemExpr* c = this->child(i); |
| if ( c ) { |
| |
| if ( ((ItemExpr*)child(i)) -> getOperatorType() != ITM_ITEM_LIST ) |
| list->insert(&child(i)); |
| else |
| return ((ItemList*)((ItemExpr*)child(i))) -> collectLeaves(heap, list); |
| |
| } |
| } |
| |
| return list; |
| } |
| |
| void ItemList::setResolveIncompleteTypeStatus(NABoolean x) |
| { |
| for ( Int32 i=0; i<getArity(); i++ ) { |
| ItemExpr* child = this->child(i); |
| if ( child ) |
| child -> setResolveIncompleteTypeStatus(x); |
| } |
| } |
| |
| // This routine is a helper function to BiRelat::tryToRelaxCharTypeMatchRules(). |
| // It performs the relaxation. |
| NABoolean |
| ItemExpr::performRelaxation(ExprValueId* ie1, ExprValueId* ie2, BindWA *bindWA) |
| { |
| const NAType *operand1 = &(ie1->getValueId()).getType(); |
| const NAType *operand2 = &(ie2->getValueId()).getType(); |
| |
| if ( operand1 -> getTypeQualifier() != NA_CHARACTER_TYPE || |
| operand2 -> getTypeQualifier() != NA_CHARACTER_TYPE |
| ) |
| return TRUE; |
| |
| const CharType *charOp1 = (CharType*)operand1; |
| const CharType *charOp2 = (CharType*)operand2; |
| |
| // pointer to the node to be translated (relaxed) |
| ExprValueId* nodeToTranslatePtr = NULL; |
| |
| if ( (ie1->getPtr()) -> isCharTypeMatchRulesRelaxable() && |
| charOp2->getCharSet() == CharInfo::ISO88591 |
| ) |
| { |
| nodeToTranslatePtr = ie1; // the left node should be translated |
| } else |
| if ( (ie2->getPtr()) -> isCharTypeMatchRulesRelaxable() && |
| charOp1->getCharSet() == CharInfo::ISO88591 |
| ) |
| { |
| nodeToTranslatePtr = ie2; // the right node should be translated |
| } |
| |
| if ( nodeToTranslatePtr != NULL ) |
| { |
| ItemExpr * newTranslateChild = |
| new (CmpCommon::statementHeap()) Translate( |
| nodeToTranslatePtr->getPtr(), |
| Translate::UNICODE_TO_ISO88591 |
| ); |
| |
| newTranslateChild = newTranslateChild->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return FALSE; |
| |
| *nodeToTranslatePtr = newTranslateChild; |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| // special relax code because we want to handle Code_Value(:ucs_hv) |
| ItemExpr* CodeVal::tryToRelaxCharTypeMatchRules(BindWA *bindWA) |
| { |
| if ( (getOperatorType() == ITM_ASCII || getOperatorType() == ITM_CODE_VALUE) |
| && ((ItemExpr*)child(0)) -> isCharTypeMatchRulesRelaxable() |
| ) |
| { |
| return performRelaxation(CharInfo::ISO88591, bindWA); |
| } |
| return this; |
| } |
| |
| // special relax code because we want to handle |
| // Translate(:ucs_hv using ISO88591TOUCS2) |
| ItemExpr* Translate::tryToRelaxCharTypeMatchRules(BindWA *bindWA) |
| { |
| if ( getTranslateMapTableId() == Translate::ISO88591_TO_UNICODE && |
| ((ItemExpr*)child(0)) -> isCharTypeMatchRulesRelaxable() |
| ) |
| { |
| // just return the child(0) because this translate is not |
| // necessary and we want to avoid double 1-to-1 translation |
| return (ItemExpr*)child(0); |
| } |
| return this; |
| } |
| |
| // special "relaxable" test for assign. Return true if the target is |
| // an UCS2 hostvar and the source is ISO88591. |
| NABoolean Assign::isRelaxCharTypeMatchRulesPossible() |
| { |
| if ( getTarget().getType().getTypeQualifier() == NA_CHARACTER_TYPE && |
| getSource().getType().getTypeQualifier() == NA_CHARACTER_TYPE && |
| getSource().getItemExpr() -> isCharTypeMatchRulesRelaxable() |
| ) |
| { |
| const CharType& sourceCT = (const CharType&)getSource().getType(); |
| const CharType& targetCT = (const CharType&)getTarget().getType(); |
| |
| if ( sourceCT.getCharSet() == CharInfo::UNICODE && |
| targetCT.getCharSet() == CharInfo::ISO88591 |
| ) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| ItemExpr* Assign::tryToRelaxCharTypeMatchRules(BindWA *bindWA) |
| { |
| return performRelaxation(CharInfo::ISO88591, bindWA); |
| } |
| |
| // A generic routine to attempt Implicit Casting/Translation of |
| // child nodes to the character set required by the context. |
| // |
| // Always return this. |
| // |
| ItemExpr* ItemExpr::tryToDoImplicitCasting(BindWA *bindWA) |
| { |
| ItemExpr *result = this; |
| enum {iUCS2 = 0, iISO = 1, iUTF8 = 2, iSJIS = 3, iGBK = 4, iUNK = 5}; |
| Int32 Literals_involved[6] = { 0, 0, 0, 0, 0, 0}; |
| Int32 nonLiterals_involved[6] = { 0, 0, 0, 0, 0, 0 }; |
| Int32 charsets_involved[6] = { 0, 0, 0, 0, 0, 0 }; |
| Int32 charsetsCount = 0; |
| CharInfo::CharSet cs = CharInfo::UnknownCharSet; |
| CharInfo::CharSet curr_chld_cs= CharInfo::UnknownCharSet; |
| CharInfo::CharSet chld0_cs = CharInfo::UnknownCharSet; |
| OperatorTypeEnum chld0_opType = ITM_FIRST_ITEM_OP; |
| OperatorTypeEnum curr_chld_opType = ITM_FIRST_ITEM_OP; |
| |
| Int32 arity = getArity(); |
| if (arity <= 0) // This method works only if there are children |
| return this; |
| // |
| // First we must determine the best target character set to use |
| // given the context. |
| // |
| // Step 1: Determine if we have children with different character set attributes |
| // |
| for (Int32 i = 0; i < arity; i++) { |
| |
| const NAType& type = child(i)->getValueId().getType(); |
| if ( type.getTypeQualifier() == NA_CHARACTER_TYPE ) |
| { |
| curr_chld_cs = ((const CharType&)type).getCharSet(); |
| if ( i==0 ) chld0_cs = curr_chld_cs; // Remember this one |
| |
| Int16 cur_chld_cs_ndx = iUNK; |
| |
| switch ( curr_chld_cs ) |
| { |
| case CharInfo::UNICODE : |
| cur_chld_cs_ndx = iUCS2; |
| break; |
| |
| case CharInfo::ISO88591: |
| cur_chld_cs_ndx = iISO; |
| break; |
| |
| case CharInfo::UTF8: |
| cur_chld_cs_ndx = iUTF8; |
| break; |
| |
| case CharInfo::SJIS: |
| cur_chld_cs_ndx = iSJIS; |
| break; |
| |
| case CharInfo::GBK: |
| cur_chld_cs_ndx = iGBK; |
| break; |
| |
| //case CharInfo::KANJI_MP: |
| //case CharInfo::KSC5601_MP: |
| default: |
| break; // Can not translate these currently. |
| } |
| charsets_involved[cur_chld_cs_ndx]++; |
| |
| OperatorTypeEnum curr_chld_opType = child(i)->getOperatorType(); |
| if (i == 0 ) chld0_opType = curr_chld_opType; // Remember this one |
| |
| if ( curr_chld_opType == ITM_CONSTANT ) |
| Literals_involved[cur_chld_cs_ndx] += 1 ; |
| else |
| nonLiterals_involved[cur_chld_cs_ndx] += 1 ; |
| } |
| |
| } |
| |
| for (Int32 j = 0; j < iUNK; j++) |
| { |
| if (charsets_involved[j] > 0) |
| charsetsCount++; |
| } |
| |
| if (charsetsCount > 1) |
| { |
| // Now choose the best character set for the translations |
| |
| cs = CharInfo::ISO88591; |
| |
| if ( ! CanChild0BeImplicitlyCast() ) |
| { |
| // Looks like an Assign operation, so use child 0's cs |
| cs = chld0_cs; |
| } |
| else |
| { |
| if ( nonLiterals_involved[iUCS2] > 0 ) |
| cs = CharInfo::UCS2; |
| else if ( nonLiterals_involved[iUTF8] > 0 ) |
| cs = CharInfo::UTF8; |
| else if ( nonLiterals_involved[iSJIS] > 0 ) |
| cs = CharInfo::SJIS; |
| else if ( Literals_involved[iUCS2] > 0 ) |
| cs = CharInfo::UCS2; |
| else if ( Literals_involved[iUTF8] > 0 ) |
| cs = CharInfo::UTF8; |
| else if ( Literals_involved[iSJIS] > 0 ) |
| cs = CharInfo::SJIS; |
| else if ( Literals_involved[iGBK] > 0 ) |
| cs = CharInfo::GBK; |
| |
| // |
| // Now, we may be able to optimize by translating the 1st child |
| // rather than the 2nd. For now, we consider only the case |
| // when there are exactly 2 children (e.g. WHERE predicate) |
| // |
| if ( ( cs == chld0_cs ) && ( arity == 2 ) && |
| ( curr_chld_opType != ITM_TRANSLATE ) && |
| ( charsetsCount == (charsets_involved[iUCS2] + charsets_involved[iUTF8] + charsets_involved[iGBK]) ) ) |
| { |
| if ( chld0_opType == ITM_TRANSLATE ) |
| cs = curr_chld_cs; //...because we will eliminate a translate op |
| else |
| if ( CanChild0BeImplicitlyCast() && |
| ( chld0_opType != ITM_BASECOLUMN ) ) |
| { |
| if ( child(0)->shouldPushTranslateDown( curr_chld_cs ) > |
| child(1)->shouldPushTranslateDown( cs ) ) |
| { // If translating to curr_chld_cs is more beneficial |
| cs = curr_chld_cs; |
| } |
| } |
| } |
| } |
| |
| result = performImplicitCasting(cs, bindWA); |
| } |
| else if ( getOperatorType() == ITM_CAST ) |
| { |
| const NAType& chldType = child(0)->getValueId().getType(); |
| if ( chldType.getTypeQualifier() == NA_CHARACTER_TYPE ) |
| { |
| CharInfo::CharSet chld_cs = ((const CharType&)chldType).getCharSet(); |
| |
| const NAType *desiredType = ((Cast *)this)->getType(); |
| if ( desiredType->getTypeQualifier() == NA_CHARACTER_TYPE ) |
| { |
| CharInfo::CharSet Desired_cs = ((const CharType*)desiredType)->getCharSet(); |
| /* |
| * this is a special handling for jira 1720, only used in a bulkload scenario |
| * that is, when user set the HIVE_FILE_CHARSET to 'gbk', it means the data saved in hive |
| * table is encoded as GBK. Trafodion default all Hive data charset as 'UTF8', so |
| * this will allow the auto charset converting to happen during bulk load |
| * the reason is: |
| * hive scan will mark the source column as GBK when HIVE_FILE_CHARSET is set to GBK |
| * which is the only value it can be |
| * So the bind will invoke this implicit casting method to check if an auto charset |
| * converting is needed. |
| * In the hive scan, it does not set the tgtCharSetSpecified field, so in order to |
| * force it to perform a translate, add a checking here |
| */ |
| if( (chld_cs != Desired_cs) && CmpCommon::getDefaultString(HIVE_FILE_CHARSET) == "GBK" ) |
| result = performImplicitCasting( Desired_cs, bindWA ); |
| else if ( (chld_cs != Desired_cs) && ( ! ((Cast *)this)->tgtCharSetSpecified() ) ) |
| { |
| // |
| // Looks like user said CAST( ... as [var]char(NNN) ) |
| // without specifying charset. |
| // Leave the child's charset alone and change desired type to match it. |
| NAType * newType = desiredType->newCopy(bindWA->wHeap()); |
| CharType * newCType = (CharType *) newType; |
| Int32 child_bpc = CharInfo::maxBytesPerChar(chld_cs); |
| Int32 child_charLimit = ((const CharType&)chldType).getStrCharLimit(); |
| Int32 Desired_charLimit = ((const CharType*)desiredType)->getStrCharLimit(); |
| |
| newCType->setCharSet(chld_cs) ; |
| newCType->setBytesPerChar(child_bpc) ; |
| newCType->setEncodingCharSet( ((const CharType&)chldType).getEncodingCharSet() ); |
| if ( chld_cs == CharInfo::UNICODE ) |
| newCType->setDataStorageSize( Desired_charLimit * child_bpc ); |
| if ( (Desired_cs == CharInfo::ISO88591) && |
| (chld_cs == CharInfo::UTF8) |
| ) |
| { |
| // Assume user meant [var]char(nnn CHARS) |
| newCType->setDataStorageSize( Desired_charLimit * child_bpc ); |
| } |
| ((Cast *)this)->changeType(newType); // Change the Cast's target type! |
| |
| if (getValueId() != NULL_VALUE_ID) |
| getValueId().changeType(newType); |
| return this; |
| } |
| else if ( chld_cs != Desired_cs ) // New CharSet specified |
| { |
| result = performImplicitCasting( Desired_cs, bindWA ); |
| } |
| } |
| } |
| } |
| else if ( getOperatorType() == ITM_TRANSLATE ) |
| { |
| CharInfo::CharSet Required_cs = CharInfo::UnknownCharSet; |
| Translate *parent_tran = (Translate *) this; |
| switch( parent_tran->getTranslateMapTableId() ) |
| { |
| case Translate::ISO88591_TO_UNICODE: |
| case Translate::ISO88591_TO_UTF8: |
| // case Translate::ISO88591_TO_SJIS: |
| Required_cs = CharInfo::ISO88591; |
| break; |
| case Translate::SJIS_TO_UNICODE: |
| case Translate::SJIS_TO_UCS2: |
| case Translate::SJIS_TO_UTF8: |
| // case Translate::SJIS_TO_ISO88591: |
| Required_cs = CharInfo::SJIS; |
| break; |
| case Translate::UTF8_TO_UCS2: |
| case Translate::UTF8_TO_SJIS: |
| case Translate::UTF8_TO_ISO88591: |
| Required_cs = CharInfo::UTF8; |
| break; |
| case Translate::UNICODE_TO_ISO88591: |
| case Translate::UNICODE_TO_SJIS: |
| case Translate::UCS2_TO_SJIS: |
| case Translate::UCS2_TO_UTF8: |
| Required_cs = CharInfo::UNICODE; |
| break; |
| case Translate::GBK_TO_UTF8: |
| Required_cs = CharInfo::GBK; |
| break; |
| default: |
| break; |
| } |
| if ( Required_cs == CharInfo::UnknownCharSet ) |
| return this; // Cannot do anything for this situation |
| |
| if ( child(0)->getOperatorType() == ITM_TRANSLATE ) // 1st child ? |
| { |
| const NAType& type = child(0)->child(0)->getValueId().getType(); |
| CharInfo::CharSet gr_chld_cs = ((const CharType&)type).getCharSet(); |
| if ( gr_chld_cs == Required_cs ) |
| { |
| // We have an undesired TRANSLATE node, so we will remove it. |
| |
| ItemExpr *grnd_child = child(0)->child(0)->castToItemExpr(); |
| |
| result->setChild(0, grnd_child); |
| } |
| } |
| else if ( child(0)->getOperatorType() != ITM_BASECOLUMN ) |
| { |
| const NAType& type = child(0)->getValueId().getType(); |
| if ( type.getTypeQualifier() == NA_CHARACTER_TYPE ) |
| { |
| CharInfo::CharSet chld_cs = ((const CharType&)type).getCharSet(); |
| // |
| // If the child is NOT of the required character set we need to |
| // do Implicit Casting UNLESS the child is ISO88591 and the |
| // Required_cs is a superset of ISO88591 |
| // |
| if ( ( chld_cs != Required_cs ) && |
| ! ( ( chld_cs == CharInfo::ISO88591 ) && ( Required_cs == CharInfo::UTF8 ) ) |
| ) |
| { |
| result = performImplicitCasting(Required_cs, bindWA); |
| } |
| } |
| } |
| // else it is an ITM_BASECOLUMN. We choose to give user an error, |
| // so here we just return. |
| |
| } // end of: if ( getOperatorType() == ITM_TRANSLATE ) |
| |
| // assumption is that we call this method before assigning a value id, and that |
| // performImplicitCasting returns the object it has been called on |
| CMPASSERT(result == this && getValueId() == NULL_VALUE_ID); |
| |
| return result; // No automatic translations to do. |
| } |
| |
| ItemExpr *ItemExpr::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) return this; |
| bindSelf(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| ItemExpr* exp = this; |
| |
| // A quick way to determine whether we should worry about relaxation. |
| // Only comparison and assign operators, SQL string functions are the |
| // candidates. |
| if ( isRelaxCharTypeMatchRulesPossible() ) |
| { |
| // may return this or an modified expression. In either case, need to |
| // perform the type synthesization. |
| exp=tryToRelaxCharTypeMatchRules(bindWA); |
| CMPASSERT(exp); |
| } |
| |
| if ( getArity() > 0 || getOperatorType() == ITM_VALUEIDUNION) |
| { |
| // Might be possible to do some automatic translation of character sets |
| // |
| if (getOperatorType() != ITM_ITEM_LIST) // Ignore LISTs - no context yet! |
| { |
| if ( CmpCommon::getDefault(ALLOW_IMPLICIT_CHAR_CASTING) == DF_ON ) |
| { |
| //User wants this (despite being rather non-ANSI standard) |
| //so we will try to do so. |
| |
| exp = tryToDoImplicitCasting(bindWA); |
| CMPASSERT(exp); |
| } |
| } |
| } |
| |
| const NAType *type = exp->synthTypeWithCollateClause(bindWA); |
| if (!type) return exp; |
| setValueId(createValueDesc(bindWA, exp, type)); |
| return exp; |
| } // ItemExpr::bindNode() |
| |
| ////////////////////////////////////////////////// |
| // This function binds an item expression tree. |
| // After bindNode() returns, the completeness of |
| // the type of the expression is checked. Pushing |
| // down desired complete type may take place. |
| ////////////////////////////////////////////////// |
| |
| ItemExpr *ItemExpr::bindNodeRoot(BindWA *bindWA) |
| { |
| ItemExpr* exp = bindNode(bindWA); |
| |
| if ( getResolveIncompleteTypeStatus() == FALSE ) |
| return exp; |
| |
| // if the valudId is invalid or we are not bind the true root, |
| // forget the pushdown type business |
| if ( exp ==0 || exp->getValueId() == NULL_VALUE_ID || |
| bindWA->isBindTrueRoot() == FALSE |
| ) |
| return exp; |
| |
| const NAType* type = &(exp->getValueId()).getType(); |
| |
| if ( type->getTypeQualifier() == NA_CHARACTER_TYPE && |
| ((CharType*)type)->getCharSet() == CharInfo::UnknownCharSet |
| ) |
| { |
| |
| // deal with cases where the item expression tree can not determine |
| // the charset attribute by itself. |
| // Example: select upper('abcd') from t; |
| // Solution: we force ISO88591 throughout the tree here. |
| const NAType* desired = CharType::desiredCharType(CharInfo::ISO88591); |
| |
| (exp->getValueId()).coerceType(*desired, NA_CHARACTER_TYPE); |
| type = &(exp->getValueId()).getType(); |
| |
| // We only give one shot here. |
| if ( type->getTypeQualifier() == NA_CHARACTER_TYPE && |
| ((CharType*)type)->getCharSet() == CharInfo::UnknownCharSet |
| ) |
| { |
| return 0; |
| } |
| } |
| |
| return exp; |
| |
| } // ItemExpr::bindNodeRoot() |
| |
| ItemExpr* ItemExpr::_bindNodeRoot(BindWA *bindWA) |
| { |
| return 0; |
| } |
| |
| ItemExpr * ItemExpr::foldConstants(BindWA *bindWA) |
| { |
| // a shortcut to constant folding for use in the binder |
| ItemExpr *result = foldConstants(CmpCommon::diags(), TRUE); |
| if (CmpCommon::diags()->mainSQLCODE() < 0) |
| bindWA->setErrStatus(); |
| return result; |
| } |
| |
| ItemExpr * ItemExpr::bindUDFsOrSubqueries(BindWA *bindWA) |
| { |
| |
| // Method to bind only a UDF or Subquery node in an expr Tree. This |
| // is used for assign expression in update to make sure we know the |
| // the degree of the UDF/Subquery before we create assignment lists. |
| |
| switch (getOperatorType()) |
| { |
| case ITM_ROW_SUBQUERY: |
| { |
| DefaultToken allowMultiDegreeTok = |
| CmpCommon::getDefault(ALLOW_MULTIDEGREE_SUBQ_IN_SELECTLIST); |
| |
| if (allowMultiDegreeTok == DF_ON || |
| allowMultiDegreeTok == DF_SYSTEM) |
| return bindNode(bindWA); |
| else |
| return this; // don't do anything. |
| break; |
| } |
| |
| case ITM_USER_DEF_FUNCTION: |
| return bindNode(bindWA); |
| break; |
| |
| default: |
| // Walk the rest of the tree. |
| for (Int32 chld=0; chld < getArity(); chld++) |
| { |
| child(chld) = child(chld)->bindUDFsOrSubqueries(bindWA); |
| } |
| } |
| return this; |
| } |
| |
| ItemExpr *AnsiUSERFunction::bindNode(BindWA *bindWA) |
| { |
| if (bindWA->inDDL() && (bindWA->inCheckConstraintDefinition())) |
| { |
| StmtDDLAddConstraintCheck *pCkC = bindWA->getUsageParseNodePtr() |
| ->castToElemDDLNode() |
| ->castToStmtDDLAddConstraintCheck(); |
| *CmpCommon::diags() << DgSqlCode(-4132); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| const NAType *type = synthTypeWithCollateClause(bindWA); |
| if (!type) return this; |
| |
| // Multiple references to the USER function should return the same |
| // user ID in one query, no matter which process it is being evaluated |
| // (sqlci/esp/dp2). |
| // So all occurrences of CURRENT USER functions are treated as input |
| // values and are given the same value id. Similarly, all occurrences |
| // of SESSION USER function are given the same value id (but different |
| // from that of the CURRENT USER). |
| // |
| ItemExpr * ie = ItemExpr::bindUserInput(bindWA,type,getText()); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // add this value id to BindWA's input function list. |
| bindWA->inputFunction().insert(getValueId()); |
| |
| return ie; |
| } |
| |
| ItemExpr *MonadicUSERFunction::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // For now user(x) is allowed only in the top most select list |
| // check that first, or else give an error |
| |
| BindScope * currScope = bindWA->getCurrentScope(); |
| BindContext *context = currScope->context(); |
| |
| if (!(context->inSelectList())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4310) |
| << DgString0("USER(x)"); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // Check for case like select (select user(1) ...). |
| // or select * from t1, (select user(x) from t1) t3 etc. |
| // Here the user function is in the select list of a |
| // sub-query and in join, hence is not allowed. |
| // Also it is not allowed at any other place example orderBy |
| BindScope *prevScope = NULL; |
| |
| while (currScope) |
| { |
| BindContext *currContext = currScope->context(); |
| if (currContext->inSubquery() || |
| currContext->inOrderBy() || |
| currContext->inExistsPredicate() || |
| currContext->inGroupByClause() || |
| currContext->inWhereClause() || |
| currContext->inHavingClause() || |
| currContext->inUnion() || |
| currContext->inJoin() ) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4310) |
| << DgString0("USER(x)"); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| prevScope = currScope; |
| currScope = bindWA->getPreviousScope(prevScope); |
| } |
| |
| bindSelf(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| unBind(); |
| |
| return ItemExpr::bindNode(bindWA); |
| } // MonadicUSERFunction::bindNode |
| |
| |
| // ----------------------------------------------------------------------- |
| // BiRelat & Function classes set context flags, then call ItemExpr::bindNode |
| // (they are directly derived subclasses of ItemExpr; safe to invoke this) |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *BiRelat::bindNode(BindWA *bindWA) |
| { |
| if (checkForSQLnullChild(bindWA, this, getSpecialNulls())) return this; |
| ItemExpr *save = bindWA->getCurrentScope()->context()->inMultaryPred(); |
| bindWA->getCurrentScope()->context()->inMultaryPred() = this; |
| |
| //changes for HistIntRed |
| |
| //save the current state of inRangePred_ in the binder |
| //context so that we can set it back after binding this |
| //node in case we reset it the if statement below |
| NABoolean inRangePredSave = bindWA->getCurrentScope()->context()->inRangePred(); |
| |
| //get the operator |
| OperatorTypeEnum oper = getOperatorType(); |
| |
| //check if the operator implies a range predicate |
| //this would be if the operator is <, >, <=, >= |
| if((oper == ITM_LESS)|| |
| (oper == ITM_LESS_EQ)|| |
| (oper == ITM_GREATER)|| |
| (oper == ITM_GREATER_EQ)) |
| { |
| bindWA->getCurrentScope()->context()->inRangePred() = TRUE; |
| }; |
| bindWA->getCurrentScope()->context()->inPredicate() = TRUE; |
| |
| // this will bind/type-propagate all children. |
| //bindChildren(bindWA); |
| ItemExpr *boundExpr = ItemExpr::bindNode(bindWA); |
| |
| //set inRangePred_ in the context to original value |
| bindWA->getCurrentScope()->context()->inRangePred() = inRangePredSave; |
| bindWA->getCurrentScope()->context()->inMultaryPred() = save; |
| |
| bindWA->getCurrentScope()->context()->inPredicate() = FALSE; |
| |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (!handleIncompatibleComparison(bindWA)) |
| return NULL; |
| |
| // ------ |
| // for dynamic histogram compression: |
| // ---------------------------------- |
| // if the node relates two basecolumns mark the columns |
| // as having a join predicate |
| // The hasJoinPred flag is used by dynamic histogram compression |
| // to pick the correct version of compressed histograms |
| if((child(0)->getOperatorType()==ITM_BASECOLUMN) && |
| (child(1)->getOperatorType()==ITM_BASECOLUMN)) |
| { |
| // get the table descriptor for each column |
| TableDesc * tabdesc1 = ((BaseColumn *) getChild(0))->getTableDesc(); |
| TableDesc * tabdesc2 = ((BaseColumn *) getChild(1))->getTableDesc(); |
| |
| // if table descriptors don't match these columns are from different |
| // tables i.e. join predicate |
| // This avoids a scenario like select * from t1 where t1.a = t1.b |
| if(tabdesc1 != tabdesc2) |
| { |
| ((BaseColumn *) getChild(0))->getNAColumn()->setHasJoinPred(); |
| ((BaseColumn *) getChild(1))->getNAColumn()->setHasJoinPred(); |
| } |
| } |
| |
| if ((child(0)->getOperatorType() == ITM_BASECOLUMN) || |
| (child(1)->getOperatorType() == ITM_BASECOLUMN)) |
| { |
| const NAType &type1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| const NAType &type2 = |
| child(1)->castToItemExpr()->getValueId().getType(); |
| |
| if ((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (type2.getTypeQualifier() == NA_CHARACTER_TYPE)) |
| { |
| const CharType &cType1 = (CharType&)type1; |
| const CharType &cType2 = (CharType&)type2; |
| |
| if (cType1.isCaseinsensitive() != cType2.isCaseinsensitive()) |
| { |
| if ((child(0)->getOperatorType() == ITM_BASECOLUMN) && |
| (cType1.isCaseinsensitive())) |
| { |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) Convert(child(0)); |
| newChild->bindNode(bindWA); |
| setChild(0, newChild); |
| } |
| |
| if ((child(1)->getOperatorType() == ITM_BASECOLUMN) && |
| (cType2.isCaseinsensitive())) |
| { |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) Convert(child(1)); |
| newChild->bindNode(bindWA); |
| setChild(1, newChild); |
| } |
| } |
| } |
| |
| if ( child(0)->getOperatorType() == ITM_CONSTANT || |
| child(1)->getOperatorType() == ITM_CONSTANT ) |
| { |
| NABoolean checkRebind = FALSE; |
| Int32 cvExprIndex; |
| if ( child(1)->getOperatorType() == ITM_CONSTANT AND |
| oper == ITM_EQUAL ) |
| { |
| checkRebind = TRUE; |
| cvExprIndex = 1; |
| } else { |
| if ( child(0)->getOperatorType() == ITM_CONSTANT AND |
| oper == ITM_EQUAL ) |
| { |
| checkRebind = TRUE; |
| cvExprIndex = 0; |
| } |
| } |
| |
| if ( checkRebind == TRUE ) { |
| ConstValue* cvExpr = (ConstValue*)(getChild(cvExprIndex)); |
| if ( cvExpr->getValueId().getType().getTypeQualifier() == NA_CHARACTER_TYPE |
| AND |
| cvExpr-> isRebindNeeded() == TRUE ) { |
| cvExpr -> unBind(); |
| cvExpr->bindNode(bindWA); // rebind to populate the cache or |
| // share the valueId of an identical |
| // string literal |
| setChild(cvExprIndex, cvExpr); |
| cvExpr->setRebindNeeded(FALSE); |
| } |
| } |
| } |
| } |
| |
| // |
| return boundExpr; |
| } // BiRelat::bindNode() |
| |
| void BiRelat::synthTypeAndValueId(NABoolean redriveTypeSynthesisFlag, |
| NABoolean redriveChildTypeSynthesis) |
| { |
| ItemExpr::synthTypeAndValueId(redriveTypeSynthesisFlag, |
| redriveChildTypeSynthesis); |
| |
| if ((child(0)->getOperatorType() == ITM_BASECOLUMN) || |
| (child(1)->getOperatorType() == ITM_BASECOLUMN)) |
| { |
| const NAType &type1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| const NAType &type2 = |
| child(1)->castToItemExpr()->getValueId().getType(); |
| |
| if ((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (type2.getTypeQualifier() == NA_CHARACTER_TYPE)) |
| { |
| const CharType &cType1 = (CharType&)type1; |
| const CharType &cType2 = (CharType&)type2; |
| |
| if (cType1.isCaseinsensitive() != cType2.isCaseinsensitive()) |
| { |
| if ((child(0)->getOperatorType() == ITM_BASECOLUMN) && |
| (cType1.isCaseinsensitive())) |
| { |
| ItemExpr * newChild = new HEAP Convert(child(0)); |
| newChild->synthTypeAndValueId(redriveTypeSynthesisFlag, |
| redriveChildTypeSynthesis); |
| setChild(0, newChild); |
| } |
| |
| if ((child(1)->getOperatorType() == ITM_BASECOLUMN) && |
| (cType2.isCaseinsensitive())) |
| { |
| ItemExpr * newChild = new HEAP Convert(child(1)); |
| newChild->synthTypeAndValueId(redriveTypeSynthesisFlag, |
| redriveChildTypeSynthesis); |
| setChild(1, newChild); |
| } |
| } |
| } |
| } |
| |
| } |
| |
| static ItemExpr * ItemExpr_handleIncompatibleComparison( |
| BindWA *bindWA, |
| ItemExpr * thisPtr, |
| ItemExpr * op1, ItemExpr * op2, |
| ItemExpr * &newOp1, ItemExpr * &newOp2) |
| { |
| const NAType &type1 = op1->castToItemExpr()->getValueId().getType(); |
| const NAType &type2 = op2->castToItemExpr()->getValueId().getType(); |
| |
| // lob cannot be in a predicate |
| if (type1.isLob() || type2.isLob()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4322); |
| bindWA->setErrStatus(); |
| return NULL; // error |
| } |
| |
| // Check if we are to allow certain incompatible comparisons |
| if (CmpCommon::getDefault(ALLOW_INCOMPATIBLE_OPERATIONS) == DF_ON) |
| { |
| // if left and right operands are incompatible, convert |
| // one of them to the other type. |
| // The check for the conditions under which this is allowed |
| // has already been done in BiRelat::synthesizeType. |
| |
| Int32 srcOpIndex = -1; //index of src child |
| Int32 tgtOpIndex = -1; //index of tgt child |
| Int32 conversion = 0; //0 = no conversion |
| //1 = cast char to numeric |
| //2 = cast char to date |
| //3 = cast date to numeric |
| //4 = cast numeric to interval |
| //5 = cast numeric to date |
| |
| //check if: |
| //1. Comparing numeric to a character type |
| //2. Comparing date column to a character string literal |
| //3. Comparing date to numeric |
| //4. Comparing interval to numeric |
| //6. Comparing date to char of form: DD-MON-YYYY |
| |
| //check for numeric to character comparison |
| if ((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (type2.getTypeQualifier() == NA_NUMERIC_TYPE)) |
| { |
| // convert op1(char) to numeric type |
| srcOpIndex = 0; |
| tgtOpIndex = 1; |
| conversion = 1; |
| } |
| else |
| if ((type1.getTypeQualifier() == NA_NUMERIC_TYPE) && |
| (type2.getTypeQualifier() == NA_CHARACTER_TYPE)) |
| { |
| // convert op2(character) to numeric type |
| srcOpIndex = 1; |
| tgtOpIndex = 0; |
| conversion = 1; |
| } |
| |
| //check for date to character literal comparison |
| if (((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (op1->getOperatorType() == ITM_CONSTANT) && |
| (type2.getTypeQualifier() == NA_DATETIME_TYPE)) || |
| ((type1.getTypeQualifier() == NA_DATETIME_TYPE) && |
| (type2.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (op2->getOperatorType() == ITM_CONSTANT))) |
| { |
| NABoolean op1IsChar = |
| (type1.getTypeQualifier() == NA_CHARACTER_TYPE); |
| |
| // only a specific char const pattern is being supported right now. |
| // Pattern is: DD-MON-YYYY. |
| // Check for that. |
| ConstValue * cv = |
| (op1IsChar ? (ConstValue*)op1->castToItemExpr() |
| : (ConstValue*)op2->castToItemExpr()); |
| |
| if (cv->getStorageSize() == strlen("DD-MON-YYYY")) |
| { |
| char * str = (char*)cv->getRawText()->data(); |
| if ((str[2] == '-') && (str[6] == '-')) |
| { |
| if (op1IsChar) |
| { |
| //convert op1(char literal) to date |
| srcOpIndex = 0; |
| tgtOpIndex = 1; |
| } |
| else |
| { |
| //convert op2(char literal) to date |
| srcOpIndex = 1; |
| tgtOpIndex = 0; |
| } |
| |
| if (op1IsChar) |
| { |
| if (type2.getPrecision() == SQLDTCODE_DATE) |
| conversion = 6; |
| else if (type2.getPrecision() == SQLDTCODE_TIMESTAMP) |
| conversion = 7; |
| } |
| else |
| { |
| if (type1.getPrecision() == SQLDTCODE_DATE) |
| conversion = 6; |
| else if (type1.getPrecision() == SQLDTCODE_TIMESTAMP) |
| conversion = 7; |
| } |
| } |
| } |
| } |
| |
| // check for date to character comparison that was not covered by |
| // the previous condition. |
| if (conversion == 0) |
| { |
| if ((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (type2.getTypeQualifier() == NA_DATETIME_TYPE)) |
| { |
| //convert op1(char literal) to date |
| srcOpIndex = 0; |
| tgtOpIndex = 1; |
| conversion = 2; |
| } |
| else |
| if ((type1.getTypeQualifier() == NA_DATETIME_TYPE) && |
| (type2.getTypeQualifier() == NA_CHARACTER_TYPE)) |
| { |
| //convert op2(char literal) to date |
| srcOpIndex = 1; |
| tgtOpIndex = 0; |
| conversion = 2; |
| } |
| } |
| |
| // check for date to numeric comparison. |
| // if one child is a column and the other child is not, then |
| // convert the non-column child to the type of the column child. |
| // This would result in a CAST node not being added on top of the |
| // column which could then be used as a key predicate. |
| // If both children are columns or both are non-columns, then convert |
| // datetime to numeric. |
| if ((type1.getTypeQualifier() == NA_DATETIME_TYPE) && |
| (type2.getTypeQualifier() == NA_NUMERIC_TYPE)) |
| { |
| if (((op1->getOperatorType() == ITM_REFERENCE) || |
| (op1->getOperatorType() == ITM_BASECOLUMN)) && |
| (NOT ((op2->getOperatorType() == ITM_REFERENCE) || |
| (op2->getOperatorType() == ITM_BASECOLUMN)))) |
| { |
| // op1 is column, op2 is not. |
| // convert op2(numeric) to the type of op1 (datetime) |
| srcOpIndex = 1; |
| tgtOpIndex = 0; |
| conversion = 5; |
| } |
| else |
| { |
| // op2 is column and op1 is not, |
| // or both children are columns, or both children are non-columns. |
| // Convert op1(datetime) to numeric type |
| srcOpIndex = 0; |
| tgtOpIndex = 1; |
| conversion = 3; |
| } |
| } |
| else if ((type1.getTypeQualifier() == NA_NUMERIC_TYPE) && |
| (type2.getTypeQualifier() == NA_DATETIME_TYPE)) |
| { |
| if (((op2->getOperatorType() == ITM_REFERENCE) || |
| (op2->getOperatorType() == ITM_BASECOLUMN)) && |
| (NOT ((op1->getOperatorType() == ITM_REFERENCE) || |
| (op1->getOperatorType() == ITM_BASECOLUMN)))) |
| { |
| // op2 is column, op1 is not. |
| // convert op1(numeric) to the type of op2 (datetime) |
| srcOpIndex = 0; |
| tgtOpIndex = 1; |
| conversion = 5; |
| } |
| else |
| { |
| // op1 is column and op2 is not, |
| // or both children are columns, or both children are non-columns. |
| // Convert op2(datetime) to numeric type(op1) |
| srcOpIndex = 1; |
| tgtOpIndex = 0; |
| conversion = 3; |
| } |
| } |
| |
| //check for interval to numeric comparison |
| if ((type1.getTypeQualifier() == NA_INTERVAL_TYPE) && |
| (type2.getTypeQualifier() == NA_NUMERIC_TYPE)) |
| { |
| // convert op2 to interval type |
| srcOpIndex = 1; |
| tgtOpIndex = 0; |
| conversion = 4; |
| } |
| else |
| if ((type1.getTypeQualifier() == NA_NUMERIC_TYPE) && |
| (type2.getTypeQualifier() == NA_INTERVAL_TYPE)) |
| { |
| // convert op1 to interval type |
| srcOpIndex = 0; |
| tgtOpIndex = 1; |
| conversion = 4; |
| } |
| else |
| if ((type1.getTypeQualifier() == NA_DATETIME_TYPE) && |
| (type2.getTypeQualifier() == NA_DATETIME_TYPE) && |
| (type1.getPrecision() != type2.getPrecision())) |
| { |
| conversion = 8; |
| if (type1.getPrecision() == SQLDTCODE_TIMESTAMP) |
| { |
| // convert op2 to timestamp |
| srcOpIndex = 1; |
| tgtOpIndex = 0; |
| } |
| else |
| { |
| // convert op1 to timestamp |
| srcOpIndex = 0; |
| tgtOpIndex = 1; |
| } |
| } |
| |
| ItemExpr * newOp = NULL; |
| |
| switch (conversion) |
| { |
| case 1: |
| //doing a char to numeric conversion |
| // convert to double precision. This will handle all precision, |
| // scale and type specified in the char value. |
| newOp = |
| new (bindWA->wHeap()) |
| Cast((srcOpIndex == 0 ? op1 : op2), |
| new (bindWA->wHeap()) |
| SQLDoublePrecision(bindWA->wHeap(), (srcOpIndex == 0 ? op1 : op2)->castToItemExpr()->getValueId().getType().supportsSQLnull())); |
| newOp = newOp->bindNode(bindWA); |
| break; |
| |
| case 2: |
| //doing a char to date conversion |
| newOp = |
| new (bindWA->wHeap()) |
| Cast((srcOpIndex == 0 ? op1 : op2), |
| (tgtOpIndex == 0 ? op1 : op2)->castToItemExpr()->getValueId().getType().newCopy(bindWA->wHeap())); |
| newOp = newOp->bindNode(bindWA); |
| break; |
| |
| case 3: |
| { |
| //doing a date to numeric conversion |
| newOp = |
| new (bindWA->wHeap()) |
| Cast((srcOpIndex == 0 ? op1 : op2), |
| new (bindWA->wHeap()) |
| SQLLargeInt(bindWA->wHeap(), TRUE, |
| (srcOpIndex == 0 ? op1 : op2)->castToItemExpr()-> |
| getValueId().getType().supportsSQLnull())); |
| newOp = newOp->bindNode(bindWA); |
| } |
| break; |
| |
| case 4: |
| { |
| //doing a numeric to interval conversion |
| const NumericType& numeric = |
| (NumericType&)(srcOpIndex == 0 ? op1 : op2)->castToItemExpr()->getValueId().getType(); |
| const IntervalType& interval = |
| (IntervalType&)(tgtOpIndex == 0 ? op1 : op2)->castToItemExpr()->getValueId().getType(); |
| Lng32 maxDigits = (numeric.getMagnitude() + 9) / 10; |
| maxDigits = MINOF(maxDigits, |
| SQLInterval::MAX_LEADING_PRECISION); |
| |
| SQLInterval * newInterval = |
| new(bindWA->wHeap()) SQLInterval( |
| bindWA->wHeap(), |
| numeric.supportsSQLnull(), |
| interval.getEndField(), |
| maxDigits, |
| interval.getEndField(), |
| 0); |
| |
| newOp = |
| new (bindWA->wHeap()) |
| Cast((srcOpIndex == 0 ? op1 : op2), newInterval); |
| newOp = newOp->bindNode(bindWA); |
| } |
| break; |
| |
| case 5: |
| //doing a numeric to date conversion |
| newOp = |
| new (bindWA->wHeap()) |
| Cast((srcOpIndex == 0 ? op1 : op2), |
| (tgtOpIndex == 0 ? op1 : op2)->castToItemExpr()->getValueId().getType().newCopy(bindWA->wHeap())); |
| newOp = newOp->bindNode(bindWA); |
| break; |
| |
| case 6: |
| case 7: |
| // doing char to date formatting. |
| newOp = |
| new (bindWA->wHeap()) |
| Format((srcOpIndex == 0 ? op1 : op2), "DD-MON-YYYY", TRUE); |
| newOp = newOp->bindNode(bindWA); |
| |
| if (conversion == 7) |
| { |
| newOp = |
| new (bindWA->wHeap()) |
| Cast(newOp, |
| (tgtOpIndex == 0 ? op1 : op2)->castToItemExpr()->getValueId().getType().newCopy(bindWA->wHeap())); |
| newOp = newOp->bindNode(bindWA); |
| } |
| break; |
| |
| case 8: |
| newOp = |
| new (bindWA->wHeap()) |
| Cast((srcOpIndex == 0 ? op1 : op2), |
| (tgtOpIndex == 0 ? op1 : op2)->castToItemExpr()->getValueId().getType().newCopy(bindWA->wHeap())); |
| newOp = newOp->bindNode(bindWA); |
| break; |
| |
| default: |
| break; |
| } |
| |
| if(bindWA->errStatus()) |
| return NULL; |
| |
| newOp1 = NULL; |
| newOp2 = NULL; |
| if (newOp) |
| { |
| if (srcOpIndex == 0) |
| newOp1 = newOp; |
| else |
| newOp2 = newOp; |
| } |
| } |
| |
| return thisPtr; |
| } |
| |
| |
| // if we are allowing certain incompatible comparisons handle them. |
| // currently the following incompatible comparisons are supported: |
| // 1. Numeric and Character |
| // 2. Date and Character literal |
| BiRelat * BiRelat::handleIncompatibleComparison(BindWA *bindWA) |
| { |
| ItemExpr * newChild0 = NULL; |
| ItemExpr * newChild1 = NULL; |
| ItemExpr * result = |
| ItemExpr_handleIncompatibleComparison( |
| bindWA, |
| this, |
| child(0)->castToItemExpr(), child(1)->castToItemExpr(), |
| newChild0, newChild1); |
| if (! result) |
| return NULL; |
| |
| if(bindWA->errStatus()) |
| return NULL; |
| |
| if (newChild0) |
| setChild(0, newChild0); |
| if (newChild1) |
| setChild(1, newChild1); |
| |
| return this; |
| } |
| |
| ItemExpr *KeyRangeCompare::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) return this; |
| |
| CollIndex i = 0; |
| |
| |
| // In the case of CLUSTERING KEY option (child(0) == NULL), |
| // see if the objectName exists in the RetDesc. This is done to support |
| // queries of the form - "select * from t_4a1 as X where |
| // Key_Range_Compare (CLUSTERING KEY >= (1,2) on table X);". |
| // The X within the KeyRangeCompare resolves to t_4a1. |
| // |
| // In the case of PARTITIONING KEY option use the object name that |
| // the user provides. |
| // This is to support the requirements from Online Populate Index, where |
| // KEY_RANGE_COMAPRE is done to see which partition a row belongs |
| // to in the index. The key values are obtained from the Audit Image of the |
| // table. Example: |
| // select case |
| // when KEY_RANGE_COMPARE(PARTITIONING KEY(X.C2, X.C3) < ('l',1000) |
| // ON INDEX_TABLE t_indx) |
| // THEN 0 |
| // ELSE -1 |
| // END |
| // from (SELECT c2,c3 from table(INTERPRET_AS_ROW(:tableAuditImage,....)) |
| // AS X(C2,C3)). |
| // |
| |
| if (child(0) == NULL) |
| { |
| TableNameMap *exposedTableName = NULL; |
| LIST(TableNameMap*) xtnmList(bindWA->wHeap()); |
| bindWA->getTablesInScope(xtnmList, NULL); |
| |
| if ((getObjectName().isEmpty()) && (xtnmList.entries() == 1)) |
| { // if no table was specified and there is only one table in scope |
| // then use that table. |
| exposedTableName = xtnmList[0]; |
| } |
| else |
| { |
| RETDesc * retDesc = bindWA->getCurrentScope()->getRETDesc(); |
| exposedTableName = retDesc->getXTNM().get(&objectName_); |
| |
| if (!exposedTableName) |
| { |
| // Table specified for KeyRangeCompare is not in scope. |
| retDesc->getTableList(xtnmList, NULL); |
| NAString fmtdList(bindWA->wHeap()); |
| RETDesc::formatTableList(xtnmList, &fmtdList, TRUE); // include partition names, if present |
| |
| NAString objectNameAsString; |
| if (objectName_.hasPartnClause()) |
| objectNameAsString = objectName_.getExposedNameAsStringWithPartitionNames(); |
| else |
| objectNameAsString = objectName_.getExposedNameAsString(); |
| |
| *CmpCommon::diags() << DgSqlCode(-4332) |
| << DgTableName(objectNameAsString) |
| << DgString0(fmtdList); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } // table name was specified or we have more than one table in scope. |
| |
| setObjectName(exposedTableName->getTableName()); |
| } |
| |
| // Obtain the NATable for the objectName. |
| NATable *naTable = bindWA->getNATable(objectName_); |
| if (bindWA->errStatus()) |
| return this; |
| naTable->decrReferenceCount(); // refcount need not be incremented for this use of natable |
| |
| // Key_Range_Compare disallowed on views. |
| if (naTable->getViewText() != NULL) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4334); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // The constructor sets the specialNulls_ flag to TRUE |
| // Initialize the direction vector here. Set it up |
| // later in the procedure. |
| IntegerList *directionVector = new(bindWA->wHeap()) IntegerList(); |
| |
| // Setup the LHS of the comparision predicate |
| // Currently, we require the users to specify the LHS for |
| // partition keys, but not for clustering keys. |
| // child0 will be NULL only for the clustering key comparision. |
| if (child(0) == NULL) |
| { |
| // Get the Clustering Key Columns. |
| const NAColumnArray & clustKeyColumns = |
| naTable->getClusteringIndex()->getIndexKeyColumns(); |
| |
| CorrName qualifiedName(objectName_, bindWA->wHeap()); |
| // since these CKs colReferences are created in the Binder |
| // don't fix the name in LocList. |
| qualifiedName.setNamePosition(0); |
| |
| // Create an item list and set it as the LHS. |
| ItemExpr *clustKeyColList = new (bindWA->wHeap()) |
| ColReference(new(bindWA->wHeap()) ColRefName |
| (clustKeyColumns[0]->getColName(), |
| qualifiedName, |
| bindWA->wHeap())); |
| |
| for (i = 1; i < clustKeyColumns.entries(); i++) |
| clustKeyColList = new (bindWA->wHeap()) |
| ItemList(clustKeyColList, (ItemExpr *) new (bindWA->wHeap()) |
| ColReference(new(bindWA->wHeap()) ColRefName |
| (clustKeyColumns[i]->getColName(), |
| qualifiedName, |
| bindWA->wHeap()))); |
| |
| // Set that as child(0) |
| child(0) = clustKeyColList; |
| |
| // Setup the direction vector. |
| for (i=0; i < clustKeyColumns.entries(); i++) |
| { |
| if (clustKeyColumns.isAscending(i)) |
| directionVector->insert(1); |
| else |
| directionVector->insert(-1); |
| } |
| } |
| else |
| { |
| const PartitioningFunction *partFunc = |
| naTable->getClusteringIndex()->getPartitioningFunction(); |
| |
| if (partFunc->isATableHashPartitioningFunction()) |
| { |
| // Table is Hash or Hash2 partitioned. Partitioning Keys |
| // comparison is not allowed on hash partitioned tables. |
| *CmpCommon::diags() << DgSqlCode(-4331) |
| << DgTableName(objectName_.getExposedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Partition Key is being specified. |
| const NAColumnArray &partKeyCols = |
| naTable->getClusteringIndex()->getPartitioningKeyColumns(); |
| |
| if(!verifyPartitioningKeys(bindWA, |
| child(0), |
| partKeyCols, |
| bindWA->wHeap())) |
| { |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| for (CollIndex i2=0; i2 < partKeyCols.entries(); i2++) |
| { |
| if (partKeyCols.isAscending(i2)) |
| directionVector->insert(1); |
| else |
| directionVector->insert(-1); |
| } |
| } |
| |
| setDirectionVector(directionVector); |
| |
| // KeyRangeCompare inherits from BiRelat |
| return BiRelat::bindNode(bindWA); |
| } |
| |
| NABoolean KeyRangeCompare::verifyPartitioningKeys(BindWA *bindWA, |
| ItemExpr *tree, |
| const NAColumnArray &partKeyCols, |
| CollHeap *heap) |
| { |
| ExprValueIdList *list = new (heap) ExprValueIdList(heap); |
| |
| if (tree->getOperatorType() != ITM_ITEM_LIST) |
| { |
| ExprValueId exprTree(tree); |
| // There is only one element. |
| list->insert(&exprTree); |
| } |
| else |
| { |
| list = ((ItemList *)tree)->collectLeaves(heap, list); |
| } |
| |
| const ULng32 num = list->entries(); |
| |
| if (partKeyCols.entries() != num) { |
| // 4042 The operands of a comparison predicate must be of equal degree. |
| *CmpCommon::diags() << DgSqlCode(-4335) |
| << DgInt0((Lng32) num) |
| << DgInt1((Lng32) partKeyCols.entries()) |
| << DgTableName(objectName_.getExposedNameAsAnsiString()); |
| return FALSE; |
| } |
| |
| |
| // Make sure that the types of items in tree match the partKeyCols types. |
| // We do this by temporarily creating a cast node and binding it. |
| for (ULng32 i = 0; i < num; i++) |
| { |
| ItemExpr *cast = new (bindWA->wHeap()) Cast((*list)[i]->getPtr(), |
| partKeyCols[i]->getType(), |
| ITM_CAST); |
| cast = cast->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return FALSE; |
| |
| delete cast; |
| |
| } |
| return TRUE; |
| } |
| |
| ItemExpr *Function::bindNode(BindWA *bindWA) |
| { |
| if (checkForSQLnullChild(bindWA, this, allowsSQLnullArg(), FUNCTION_)) |
| return this; |
| |
| ItemExpr *save = bindWA->getCurrentScope()->context()->inMultaryPred(); |
| bindWA->getCurrentScope()->context()->inMultaryPred() = this; |
| ItemExpr *boundExpr = ItemExpr::bindNode(bindWA); |
| bindWA->getCurrentScope()->context()->inMultaryPred() = save; |
| return boundExpr; |
| } // Function::bindNode() |
| |
| ItemExpr *Between::bindNode(BindWA *bindWA) |
| { |
| //changes for HistIntRed |
| |
| //save the current state of inRangePred_ in the binder |
| //context so that we can set it back after binding this |
| //node in case we reset it the if statement below |
| NABoolean inRangePredSave = bindWA->getCurrentScope()->context()->inRangePred(); |
| bindWA->getCurrentScope()->context()->inRangePred() = TRUE; |
| |
| ItemExpr * boundExpr = BuiltinFunction::bindNode(bindWA); |
| |
| if((boundExpr) && |
| (!bindWA->errStatus()) && |
| (!handleIncompatibleComparison(bindWA))) |
| return NULL; |
| |
| //set inRangePred_ in the context to original value |
| bindWA->getCurrentScope()->context()->inRangePred() = inRangePredSave; |
| |
| // Apply the substr transformation, if possible |
| if (child(0)->getOperatorType() == ITM_SUBSTR AND |
| child(1)->getOperatorType() == ITM_CONSTANT AND |
| child(2)->getOperatorType() == ITM_CONSTANT AND |
| CmpCommon::getDefault(SUBSTRING_TRANSFORMATION) == DF_ON) |
| { |
| ItemExpr * bt = checkAndApplySubstrTransformation(); |
| if ( bt ) { |
| boundExpr = bt->bindNode(bindWA); |
| } |
| } |
| |
| return boundExpr; |
| } |
| |
| // Check out the possibilty of SUBSTR optimization, which |
| // convert the predicate |
| // |
| // substr(PK_column_fix_char, 1, n) between literal1 and literal2 |
| // |
| // into the following |
| // |
| // PK_column_fix_char between c1 and c2 |
| // |
| // where c1 = literal1 paded with character 0x00 |
| // and |
| // c2 = literal2 paded with character 0xff |
| // |
| // The transformation is applied when the following is true |
| // |
| // 1. PK_column_fix_char is leading key fixed ISO88591 char column; |
| // 2. strlen(literal1) = strlen(literal2) = length of substr(); |
| // 3. 1 <= n <= column length of PK_column_fix_char |
| // |
| // The method returns the transformed between expr if the transformation |
| // can be done. Otherwise, a NULL is returned. |
| // |
| ItemExpr* Between::checkAndApplySubstrTransformation() |
| { |
| ItemExpr* substr = child(0)->castToItemExpr(); |
| ItemExpr* literal1 = child(1)->castToItemExpr(); |
| ItemExpr* literal2 = child(2)->castToItemExpr(); |
| |
| NAMemory *heap = CmpCommon::statementHeap(); |
| |
| Int32 column_len = 0; |
| NABoolean col_support_null = FALSE; |
| Lng32 substr_len = 0; |
| |
| // check substr() first |
| ItemExpr* c = substr->child(0)->castToItemExpr(); |
| BaseColumn* baseCol = NULL; |
| |
| // skip the CAST operator, if any |
| if ( c->getOperatorType()==ITM_CAST ) |
| baseCol = (BaseColumn*)(c->child(0)->castToItemExpr()); |
| else |
| baseCol = (BaseColumn*)c; |
| |
| // c in SUBSTR(c, a, b) must be a basecolumn |
| if ( baseCol->getOperatorType()==ITM_BASECOLUMN ) |
| { |
| const NAType &coltype = (baseCol->getValueId()).getType(); |
| |
| // c should be a leading key base column with fixed ISO88591 char data type |
| if ( coltype.getTypeQualifier() != NA_CHARACTER_TYPE || |
| ((const CharType&)(coltype)).getCharSet() != CharInfo::ISO88591 || |
| !DFS2REC::isSQLFixedChar(((const CharType&)(coltype)).getFSDatatype()) |
| ) |
| return NULL; |
| |
| // c must be a key column |
| const NAColumn* naCol = baseCol->getNAColumn(); |
| if (naCol==NULL || !(naCol->isPrimaryKey())) return NULL; |
| |
| |
| const NATable* naTable = naCol->getNATable(); |
| const NAColumnArray & clustKeyColumns = |
| naTable->getClusteringIndex()->getIndexKeyColumns(); |
| |
| // c must be a leading key column |
| if ( clustKeyColumns.entries() < 1 || |
| clustKeyColumns[0]->getPosition() != baseCol->getColNumber() ) |
| return NULL; |
| |
| /* |
| const ValueIdList keyCols = |
| baseCol->getTableDesc()->getClusteringIndex()->getClusteringKeyCols(); |
| |
| if ( keyCols.entries() == 0 || |
| keyCols[0].getItemExpr()->getOperatorType() != ITM_INDEXCOLUMN ) |
| return NULL; |
| |
| IndexColumn* indexCol = (IndexColumn*)(keyCols[0].getItemExpr()); |
| if ( indexCol->getIndexColNumber() != baseCol->getColNumber() ) |
| return NULL; |
| */ |
| |
| column_len = ((const CharType&)(coltype)).getStrCharLimit(); |
| |
| // Get the number of characters returned by the SUBSTR |
| substr_len = |
| ((const CharType&)((substr->getValueId()).getType())).getStrCharLimit(); |
| |
| // Column length must be at least substr_len long |
| if ( column_len < substr_len ) |
| return NULL; |
| |
| col_support_null = coltype.supportsSQLnullLogical(); |
| } else |
| return NULL; |
| |
| // The 2nd argument of substr should be 1 |
| if ( substr->child(1)->getOperatorType()==ITM_CONSTANT ) { |
| |
| const ConstValue* cv1 = |
| (const ConstValue*)(substr->child(1)->castToItemExpr()); |
| |
| if ( cv1->canGetExactNumericValue() AND |
| cv1->getExactNumericValue() != 1 ) |
| return NULL; |
| } else |
| return NULL; |
| |
| // The 3rd argument of substr should be >= 1. |
| if ( substr->child(1)->getOperatorType()==ITM_CONSTANT ) { |
| |
| const ConstValue* cv2 = |
| (const ConstValue*)(substr->child(2)->castToItemExpr()); |
| |
| if ( cv2->canGetExactNumericValue() AND |
| cv2->getExactNumericValue() < 1 ) |
| return NULL; |
| } else |
| return NULL; |
| |
| // literal1 must be of character type and its length must be |
| // equal to that of the synthesized SUBSTR(). |
| const NAType &literal1_type = |
| literal1->castToItemExpr()->getValueId().getType(); |
| |
| if ( literal1_type.getTypeQualifier() != NA_CHARACTER_TYPE || |
| substr_len != ((const CharType&)literal1_type).getStrCharLimit() |
| ) |
| return NULL; |
| |
| // literal2 must be of character type and its length must be |
| // equal to that of the synthesized SUBSTR(). |
| const NAType &literal2_type = |
| literal2->castToItemExpr()->getValueId().getType(); |
| |
| if ( literal2_type.getTypeQualifier() != NA_CHARACTER_TYPE || |
| substr_len != ((const CharType&)literal2_type).getStrCharLimit() |
| ) |
| return NULL; |
| |
| |
| // Now modify the between tree |
| // ---------------------------- |
| |
| // Figure out the number of character 0x00 or 0xff to pad |
| Int32 chars_to_pad = column_len - substr_len; |
| |
| // modify the Construct the new constant |
| ConstValue* cv = (ConstValue*)literal1; |
| NAString cstr1(*(cv->getRawText())); |
| cstr1.append((const char)0x00, chars_to_pad); |
| ConstValue *c1 = new (heap) ConstValue(cstr1, heap); |
| |
| cv = (ConstValue*)literal2; |
| NAString cstr2(*(cv->getRawText())); |
| cstr2.append((const char)0xff, chars_to_pad); |
| |
| ConstValue *c2 = new (heap) ConstValue(cstr2, heap); |
| |
| child(0)= baseCol; |
| child(1)= c1; |
| child(2)= c2; |
| |
| // make sure this gets bound again |
| unBind(); |
| |
| return this; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class BitOperFunc |
| // ----------------------------------------------------------------------- |
| ItemExpr *BitOperFunc::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| return BuiltinFunction::bindNode(bindWA); |
| } // BitOperFunc::bindNode() |
| |
| Between * Between::handleIncompatibleComparison(BindWA * bindWA) |
| { |
| ItemExpr * newChild0 = NULL; |
| ItemExpr * newChild1 = NULL; |
| ItemExpr * newChild2 = NULL; |
| ItemExpr * result = |
| ItemExpr_handleIncompatibleComparison( |
| bindWA, |
| this, |
| child(0)->castToItemExpr(), child(1)->castToItemExpr(), |
| newChild0, newChild1); |
| if (! result) |
| return NULL; |
| |
| if(bindWA->errStatus()) |
| return NULL; |
| |
| result = |
| ItemExpr_handleIncompatibleComparison( |
| bindWA, |
| this, |
| child(0)->castToItemExpr(), child(2)->castToItemExpr(), |
| newChild0, newChild2); |
| if (! result) |
| return NULL; |
| |
| if(bindWA->errStatus()) |
| return NULL; |
| |
| if (newChild0) |
| setChild(0, newChild0); |
| if (newChild1) |
| setChild(1, newChild1); |
| if (newChild2) |
| setChild(2, newChild2); |
| |
| return this; |
| } |
| |
| ItemExpr *BuiltinFunction::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| // 10-100719-1893 |
| // Look for the replacementExpr if the builtinFunction is already |
| // bound. Solves the issue where we forget that we have transformed |
| // the function. For example NVL() -> CASE(). |
| |
| ItemExpr * retExpr = getValueId().getItemExpr()->getReplacementExpr(); |
| if (retExpr != NULL) |
| return retExpr; |
| else |
| return getValueId().getItemExpr(); |
| } |
| |
| ////////////////////////////////////////////////////////////////////// |
| // Due to the many problems with rand() implementations we decided to |
| // disable this method for R2.0. The last known problem with rand() |
| // caused data corruption. |
| // We will re-enable rand() after it get fixed. |
| // Also we provide special CQD to allow R1.8 users who may already used |
| // rand() and have scripts depending on it to use it in R2.0. |
| // We need also to make sure we are only disabling user rands (binder |
| // phase rands) and not system generated rands. |
| /////////////////////////////////////////////////////////////////////// |
| if ( getOperatorType() == ITM_RANDOMNUM ) |
| { |
| // if this builtinfunction is a RandomNum(), then it is safe to cast |
| // the this pointer to (Random *). |
| if (CmpCommon::getDefault(ALLOW_RAND_FUNCTION) != DF_ON AND |
| QueryAnalysis::Instance() AND |
| QueryAnalysis::Instance()->getCompilerPhase() == QueryAnalysis::BINDER) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4313); |
| bindWA->setErrStatus(); |
| return NULL; // error |
| } |
| } |
| |
| // this will bind/type-propagate all children. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if ( getOperatorType() != ITM_BETWEEN ) |
| { |
| // Reverify inputs |
| // This is to deal with cases where a builtin function requires one |
| // input, but the input given is a subquery with degree greater than 1 or |
| // a MultiValuedFunction(MVF). In this case the parser would not flag it, |
| // since we don't know the degree of those until after bind time. |
| // Here we check again.. |
| |
| // We don't need to do this for between because comparison expandsions |
| // will detect it. |
| |
| // XXX If there are builtin functions that requires two inputs and we |
| // want to allow specifying just an mvf or subquery of degree 2, we need |
| // to change the parser... |
| |
| Int32 childDegree = 0; |
| Subquery * subq = NULL; |
| UDFunction * udf = NULL; |
| Int32 origArity = getArity(); |
| |
| for (Int32 chld=0; chld < origArity; chld++) |
| { |
| |
| switch (child(chld)->getOperatorType()) |
| { |
| case ITM_ROW_SUBQUERY: |
| subq = (Subquery *) child(chld)->castToItemExpr(); |
| childDegree += subq->getSubquery()->getDegree(); |
| break; |
| |
| case ITM_USER_DEF_FUNCTION: |
| udf = (UDFunction *) child(chld)->castToItemExpr(); |
| childDegree += udf->getRoutineDesc()->getOutputColumnList().entries(); |
| break; |
| |
| default: |
| childDegree += 1; |
| break; |
| } |
| |
| } |
| |
| if (childDegree > origArity) |
| { |
| NAString upperFunc(getText(), bindWA->wHeap()); |
| |
| upperFunc.toUpper(); |
| *CmpCommon::diags() << DgSqlCode(-4479) << DgString0(upperFunc) |
| << DgInt1(origArity) << DgInt2(childDegree); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } // if ITM_BETWEEN |
| |
| ItemExpr * retExpr = NULL; |
| NABoolean useCase = FALSE; |
| ItemExpr * ie = NULL; |
| switch (getOperatorType()) |
| { |
| |
| case ITM_ISIPV4: |
| case ITM_ISIPV6: |
| case ITM_MD5: |
| case ITM_CRC32: |
| case ITM_SHA1: |
| case ITM_SOUNDEX: |
| case ITM_SHA2_224: |
| case ITM_SHA2_256: |
| case ITM_SHA2_384: |
| case ITM_SHA2_512: |
| { |
| break; |
| } |
| case ITM_NULLIFZERO: |
| { |
| // binder has already verified that child is numeric |
| const NumericType &type_op1 = (NumericType&) |
| (child(0)->castToItemExpr()->getValueId().getType()); |
| |
| if ((type_op1.isComplexType()) || |
| (NOT (type_op1.isExact())) || |
| (type_op1.isDecimal())) |
| { |
| Parser parser(bindWA->currentCmpContext()); |
| |
| retExpr = |
| parser.getItemExprTree( |
| "CASE WHEN @A1 <> 0 then @A1 ELSE NULL END;", |
| 0, BINDITEMEXPR_STMTCHARSET, 1, child(0)); |
| } |
| break; |
| } |
| |
| case ITM_NVL: |
| { |
| // if my children's attributes EXCEPT for nullability are not the |
| // same as mine, use a CASE stmt. |
| const NAType &typ1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| const NAType &typ2 = |
| child(1)->castToItemExpr()->getValueId().getType(); |
| |
| NABoolean useCase = FALSE; |
| if (NOT typ1.isCompatible(typ2)) |
| useCase = TRUE; |
| else if ((child(0)->castToItemExpr()->isASubquery()) || |
| (child(1)->castToItemExpr()->isASubquery())) |
| { |
| useCase = TRUE; |
| } |
| else if (typ1.getTypeQualifier() == NA_CHARACTER_TYPE) |
| { |
| // |
| // For character types, if the collation is not the same for |
| // the two arguments to NVL, then revert to a CASE stmt. |
| // |
| const CharType &cTyp1 = (CharType&)typ1; |
| if ( cTyp1.getCollation() != ((CharType&)typ2).getCollation() ) |
| useCase = TRUE; |
| } |
| if ( ( NOT useCase) && (NOT typ1.supportsSQLnull()) ) |
| { |
| retExpr = child(0)->castToItemExpr(); |
| break; // That's all that this NVL needs. |
| } |
| if ( NOT useCase ) |
| { |
| if (typ2.supportsSQLnull()) |
| useCase = TRUE; |
| // convert NVL to CASE for all CHAR types, including fixed CHARs. |
| // This is necessary because query caching does limited type |
| // synthesization when replacing a fixed char literal with a varchar |
| // typed parameter. In fact, if NVL appears inside |
| // "select nvl(t.c1, 'a') from t" |
| // the type of nvl() is assumed to be that of t.c1 if no conversion |
| // is done. If t.c1 is fixed char, then type will mismatch when |
| // the execution of the cached plan delivers a varchar value. The |
| // length value will appear as 1st two bytes of the output value! |
| // |
| else if ((DFS2REC::isAnyCharacter(typ1.getFSDatatype())) || |
| (DFS2REC::isAnyCharacter(typ2.getFSDatatype()))) |
| useCase = TRUE; |
| else |
| { |
| // typ1 is nullable and typ2 is non-nullable. |
| // create a new typ1 with the same null attr as typ2 |
| // and the same coercibilit as typ2. |
| NAType * newTyp1 = typ1.newCopy(bindWA->wHeap()); |
| newTyp1->resetSQLnullFlag(); |
| if (newTyp1->getTypeQualifier() == NA_CHARACTER_TYPE) |
| ((CharType*)newTyp1)->setCoercibility(((CharType&)typ2).getCoercibility()); |
| if (NOT(*newTyp1 == typ2)) |
| { |
| ie = Function::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| if (*newTyp1 == getValueId().getType()) |
| { |
| // Left child is the same type as result. |
| // Insert a cast node to convert right child to |
| // result type. |
| child(1) = new (bindWA->wHeap()) |
| Cast(child(1), &getValueId().getType()); |
| child(1)->bindNode(bindWA); |
| } |
| else |
| useCase = TRUE; |
| } |
| delete newTyp1; |
| } |
| |
| } |
| |
| if (useCase) |
| { |
| Parser parser(bindWA->currentCmpContext()); |
| |
| retExpr = |
| parser.getItemExprTree( |
| "CASE WHEN @A1 is null then @A2 ELSE @A1 END;", |
| 0, BINDITEMEXPR_STMTCHARSET, 2, child(0), child(1)); |
| } |
| |
| break; |
| } |
| case ITM_JSONOBJECTFIELDTEXT: |
| { |
| break; |
| } |
| case ITM_QUERYID_EXTRACT: |
| { |
| // type cast any params |
| ValueId vid1 = child(0)->getValueId(); |
| SQLChar c1(NULL, ComSqlId::MAX_QUERY_ID_LEN); |
| vid1.coerceType(c1, NA_CHARACTER_TYPE); |
| |
| ValueId vid2 = child(1)->getValueId(); |
| SQLChar c2(NULL, 40, FALSE); |
| vid2.coerceType(c2, NA_CHARACTER_TYPE); |
| |
| const CharType &typ1 = (CharType&)child(0)->getValueId().getType(); |
| const CharType &typ2 = (CharType&)child(1)->getValueId().getType(); |
| |
| if (typ1.getTypeQualifier() != NA_CHARACTER_TYPE || typ2.getTypeQualifier() != NA_CHARACTER_TYPE) |
| { |
| // 4043 The operand of a $0~String0 function must be character. |
| *CmpCommon::diags() << DgSqlCode(-4043) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| CharInfo::CharSet chld_cs = ((const CharType&)typ1).getCharSet(); |
| if ( chld_cs == CharInfo::UNICODE ) |
| { |
| child(0) = |
| new (bindWA->wHeap()) Translate(child(0), Translate::UNICODE_TO_ISO88591); |
| child(0) = child(0)->bindNode(bindWA); |
| } |
| chld_cs = ((const CharType&)typ2).getCharSet(); |
| if ( chld_cs == CharInfo::UNICODE ) |
| { |
| child(1) = |
| new (bindWA->wHeap()) Translate(child(1), Translate::UNICODE_TO_ISO88591); |
| child(1) = child(1)->bindNode(bindWA); |
| } |
| if (bindWA->errStatus()) |
| return NULL; |
| // Right child is the string containing the query id attribute that |
| // needs to be extracted. |
| // Upshift it so it could be compared at runtime. |
| child(1) = new (bindWA->wHeap()) Upper(child(1)); |
| child(1) = child(1)->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| } |
| break; |
| |
| case ITM_ASCII: |
| { |
| // Since the ASCII(<str_expr>) function requires an ISO88591 string arg, |
| // but we want to allow unprefixed string literals (which may be UCS2 |
| // when running on a system that is using the Unicode Config), |
| // we need the following code. |
| } |
| break; |
| |
| case ITM_AES_ENCRYPT: |
| case ITM_AES_DECRYPT: |
| break; |
| default: |
| { |
| } |
| break; |
| } // switch |
| |
| if (retExpr) |
| { |
| // Make sure the original expression is bound |
| ie = Function::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| |
| // then bind the replacement expression |
| ie = retExpr->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| } |
| else |
| { |
| if (getOperatorType() == ITM_EXTRACT || |
| getOperatorType() == ITM_EXTRACT_ODBC) |
| { |
| if (isAUserSuppliedInput()) |
| { |
| ItemExpr * extractChild = NULL; |
| if ( child(0) && (child(0)->getOperatorType() == ITM_CAST) && |
| child(0)->child(0) ) |
| extractChild = child(0)->child(0)->castToItemExpr(); |
| else if (child(0)) |
| extractChild = child(0)->castToItemExpr(); |
| |
| const NAType *type = synthTypeWithCollateClause(bindWA); |
| if (!type) return this; |
| |
| char xFld[2]; str_itoa(((Extract*) this)->getExtractField(),xFld); |
| char xVid[7]; str_itoa(extractChild->getValueId(),xVid); |
| NAString functionText = getText() + xFld + "-" + xVid; |
| ie = ItemExpr::bindUserInput(bindWA,type,functionText); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // add this value id to BindWA's input function list. |
| bindWA->inputFunction().insert(getValueId()); |
| return ie; |
| } |
| } // !extract |
| |
| ie = Function::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| } // !retExpr |
| |
| ////////////////////////////////////////////////////////////////////// |
| // Put a special Cast node on top of each child node that is a column. |
| // Do not do this if this function is a Cast function. |
| // This special Cast function casts the child to its existing |
| // data attributes. Later, if the child is replaced by a VEG and |
| // resolved to a type with different attribute in preCodeGen, |
| // this cast will convert it to its original data attributes. |
| // If the child was not resolved to a different type, then precodegen |
| // ignores this cast and replaces it with the original child. |
| /////////////////////////////////////////////////////////////////////// |
| NABoolean doCastFix = TRUE; |
| if ((doCastFix) && (protectFromVEGs())) |
| { |
| for (Int32 i = 0; i < ie->getArity(); i++) |
| { |
| if ((child(i)) && |
| ((child(i)->getOperatorType() == ITM_REFERENCE) || |
| (child(i)->getOperatorType() == ITM_BASECOLUMN))) |
| { |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) Cast(ie->child(i), |
| &ie->child(i)->getValueId().getType()); |
| |
| // mark this cast so we do not generate code for it if its attrs |
| // are the same as its child's. |
| ((Cast*)newChild)->setMatchChildType(TRUE); |
| |
| newChild = newChild->bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| ie->setChild(i, newChild); |
| } |
| } |
| } |
| |
| // 10-100719-1893 |
| // set the replacementExpr so that we remember any substitution done |
| // for example a NVL() expression gets translated to a CASE expression |
| // and we want to remember this so that any references to the same NVL() |
| // gets replaced correctly. AVG(NVL()) is a good example as the AVG() itself |
| // gets translated to SUM(NVL())/COUNT(NVL()). If we don't remember the |
| // CASE translation of the NVL(), then only the SUM() reference to the NVL() |
| // expression getting translated correctly. |
| |
| setReplacementExpr(ie); |
| |
| return ie; |
| } |
| |
| |
| ItemExpr *Upper::bindNode(BindWA *bindWA) |
| { |
| // solution 10-040212-3234: the upshift transformation is happening |
| // in code generator. This is too late. Suppose a relational operator |
| // is outputting the Upper ItemExpression node. Now suddenly, it has |
| // to output the child. This presents a few problems. Perhaps, the |
| // transformatin can't happen no later than the normalization when the |
| // outputs are decided. |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // Upper inherits from BuiltinFunction .. Function .. ItemExpr. |
| ItemExpr *boundExpr = BuiltinFunction::bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // If our child is already upshifted, we can remove this Upper from the tree. |
| // BuiltinFunction::bindNode might have added a Cast node underneath Upper |
| // with the MATCH_CHILD_TYPE flag set. We need to remove it too |
| // else we can get into trouble with unmapped ValueIds at code generation |
| // time. (e.g. If a hash join equi-join predicate refers to the Cast, the |
| // Cast expression will be in the join child's characteristic outputs but |
| // when we generate the join predicate we skip the Cast looking for the |
| // underlying column. That might not be in the join child's characteristic |
| // outputs.) |
| if (boundExpr == this) { |
| CMPASSERT(getArity() == 1); |
| ValueId opVid = child(0)->getValueId(); |
| ItemExpr * child0ie = opVid.getItemExpr(); |
| if (child0ie->getOperatorType() == ITM_CAST) |
| { |
| Cast * child0ieCast = (Cast *)child0ie; |
| if (child0ieCast->matchChildType()) |
| // we need to remove the Cast too |
| opVid = child0ieCast->child(0)->getValueId(); |
| } |
| const CharType &ct = (const CharType &)opVid.getType(); |
| CMPASSERT(ct.getTypeQualifier() == NA_CHARACTER_TYPE); |
| if (ct.isUpshifted()) setValueId(opVid); |
| } |
| return getValueId().getItemExpr(); |
| } |
| |
| ItemExpr *Abs::bindNode(BindWA *bindWA) |
| { |
| // solution 1438 in Bugzilla: If my child is unsigned |
| // then no Abs function is necessary. It seems safer to |
| // remove the Abs from the query tree as early as possible. |
| // Runtime eval method expects child of Abs to be signed, |
| // so if this transformation is not made we get a runtime |
| // internal error. |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // Abs inherits from MathFunc .. BuiltinFunction .. Function .. ItemExpr. |
| ItemExpr *boundExpr = MathFunc::bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // If our child is unsigned, we can remove this Abs from the tree. |
| if (boundExpr == this) { |
| CMPASSERT(getArity() == 1); |
| ValueId opVid = child(0)->getValueId(); |
| const NumericType &nt = (const NumericType &)opVid.getType(); |
| CMPASSERT(nt.getTypeQualifier() == NA_NUMERIC_TYPE); |
| if (nt.isUnsigned()) setValueId(opVid); |
| } |
| return getValueId().getItemExpr(); |
| } |
| |
| ItemExpr *CharFunc::bindNode(BindWA *bindWA) |
| { |
| switch ( charSet_ ) |
| { |
| case CharInfo::UNICODE: |
| setOperatorType(ITM_UNICODE_CHAR); |
| break; |
| |
| case CharInfo::KANJI_MP: |
| case CharInfo::KSC5601_MP: |
| setOperatorType(ITM_NCHAR_MP_CHAR); |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (!CharInfo::isCharSetSupported(charSet_)) { |
| // 3010 Character set $0~string0 is not yet supported. |
| // 4062 The preceding error actually occurred in function $0~String0. |
| *CmpCommon::diags() << DgSqlCode(-3010) |
| << DgString0(CharInfo::getCharSetName(charSet_)); |
| NAString unparsed(bindWA->wHeap()); |
| unparse(unparsed, DEFAULT_PHASE, USER_FORMAT_DELUXE); |
| *CmpCommon::diags() << DgSqlCode(-4062) << DgString0(unparsed); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // CharFunc inherits from BuiltinFunction .. Function .. ItemExpr. |
| return BuiltinFunction::bindNode(bindWA); |
| } |
| |
| ItemExpr *Concat::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // this will bind/type-propagate all children. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (CmpCommon::getDefault(ALLOW_INCOMPATIBLE_OPERATIONS) == DF_ON) |
| { |
| const NAType &type1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| const NAType &type2 = |
| child(1)->castToItemExpr()->getValueId().getType(); |
| |
| // allow DATE || CHAR (in mode_special_1) |
| // allow NUMERIC || CHAR for all |
| Int32 srcChildIndex = -1; |
| Int32 convType = -1; |
| if ((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (type2.getTypeQualifier() == NA_DATETIME_TYPE)) |
| { |
| // convert child(1)(DATETIME) to char type |
| srcChildIndex = 1; |
| convType = 1; |
| } |
| else if ((type1.getTypeQualifier() == NA_DATETIME_TYPE) && |
| (type2.getTypeQualifier() == NA_CHARACTER_TYPE)) |
| { |
| // convert child(1)(DATETIME) to char type |
| srcChildIndex = 0; |
| convType = 1; |
| } |
| else if ((type1.getTypeQualifier() == NA_NUMERIC_TYPE) && |
| (type2.getTypeQualifier() == NA_CHARACTER_TYPE)) |
| { |
| // convert child(0)(NUMERIC) to char type |
| srcChildIndex = 0; |
| convType = 2; |
| } |
| else if ((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (type2.getTypeQualifier() == NA_NUMERIC_TYPE)) |
| { |
| // convert child(1)(NUMERIC) to char type |
| srcChildIndex = 1; |
| convType = 2; |
| } |
| |
| if ((srcChildIndex >= 0) && |
| ((convType == 2) || |
| ((convType == 1) && |
| (CmpCommon::getDefault(MODE_SPECIAL_1) == DF_ON)))) |
| { |
| Lng32 dLen = 0; |
| if (convType == 1) |
| { |
| DatetimeType &dtType = (DatetimeType&) |
| child(srcChildIndex)->castToItemExpr()->getValueId().getType(); |
| dLen = dtType.getDisplayLength(); |
| } |
| else if (convType == 2) |
| { |
| NumericType &nType = (NumericType&) |
| child(srcChildIndex)->castToItemExpr()->getValueId().getType(); |
| dLen = |
| nType.getDisplayLength(nType.getFSDatatype(), |
| nType.getNominalSize(), |
| nType.getPrecision(), |
| nType.getScale(), |
| 0); |
| } |
| |
| ItemExpr * newChild = NULL; |
| if (convType == 1) |
| { |
| newChild = |
| new (bindWA->wHeap()) |
| Cast(child(srcChildIndex), |
| new (bindWA->wHeap()) |
| SQLChar(bindWA->wHeap(), dLen, |
| child(srcChildIndex)->castToItemExpr()-> |
| getValueId().getType().supportsSQLnull())); |
| newChild = newChild->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| else if (convType == 2) |
| { |
| Parser parser(bindWA->currentCmpContext()); |
| char buf[128]; |
| |
| sprintf(buf, "CAST(CAST(@A1 AS VARCHAR(%d)) AS VARCHAR(%d))", |
| dLen, dLen); |
| newChild = |
| parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 1, child(srcChildIndex)); |
| |
| newChild = newChild->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| |
| setChild(srcChildIndex, newChild); |
| } |
| } |
| |
| // Concat inherits from BuiltinFunction .. Function .. ItemExpr. |
| BuiltinFunction::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return getValueId().getItemExpr(); |
| } |
| |
| ItemExpr *UnLogic::bindNode(BindWA *bindWA) |
| { |
| bindWA->getCurrentScope()->context()->inPredicate() = TRUE; |
| |
| // Check for a DEFAULT child -- 'DEFAULT IS NULL' is illegal |
| ItemExpr *itm = this; |
| if (!checkForSQLnullChild(bindWA, this, TRUE/*Tdm extension allows NULL arg*/)) |
| itm = ItemExpr::bindNode(bindWA); |
| |
| bindWA->getCurrentScope()->context()->inPredicate() = FALSE; |
| return itm; |
| } |
| |
| ItemExpr * ExtractOdbc::bindNode(BindWA * bindWA) |
| { |
| if (nodeIsBound()) return this; |
| |
| bindSelf(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // if my child is a char/varchar, insert a Cast node |
| // to convert from char to timestamp. |
| if (child(0)->castToItemExpr()->getValueId().getType().getTypeQualifier() |
| == NA_CHARACTER_TYPE) |
| { |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) Cast(child(0), new (bindWA->wHeap()) |
| SQLTimestamp(bindWA->wHeap(), TRUE)); |
| setChild(0, newChild); |
| } |
| unBind(); |
| |
| allowsSQLnullArg() = TRUE; // do not raise Nullability type errors during call to bindNode |
| // This is being done so that the same error is raised for YEAR(NULL) |
| // type syntax. |
| ItemExpr * ie = BuiltinFunction::bindNode(bindWA); |
| allowsSQLnullArg() = FALSE; // reset the nullability flag to not allowing NULLs. |
| return ie ; |
| } |
| |
| ItemExpr * DateFormat::quickDateFormatOpt(BindWA * bindWA) |
| { |
| const NAType *naType0 = &child(0)->getValueId().getType(); |
| |
| if ((CmpCommon::getDefault(MODE_SPECIAL_4) == DF_ON) && |
| (child(0)->getOperatorType() == ITM_CONSTANT) && |
| (formatStr_ == "DD-MON-YYYY") && |
| (NOT naType0->isVaryingLen()) && |
| (naType0->getFSDatatype() == REC_BYTE_F_ASCII) && |
| (formatStr_.length() == naType0->getNominalSize())) |
| { |
| NABoolean canXfrm = TRUE; |
| ConstValue * cNode = (ConstValue*)child(0)->castToItemExpr(); |
| NAString cv = *cNode->getRawText(); |
| const char * str = cv.data(); |
| if (NOT ((str[2] == '-') && (str[6] == '-'))) |
| canXfrm = FALSE; |
| |
| NAString dv; |
| if (canXfrm) |
| { |
| dv.append(&str[7], 4); |
| dv += "-"; |
| NAString mon(&str[3], 3); |
| mon.toUpper(); |
| if (mon == "JAN") |
| dv += "01"; |
| else if (mon == "FEB") |
| dv += "02"; |
| else if (mon == "MAR") |
| dv += "03"; |
| else if (mon == "APR") |
| dv += "04"; |
| else if (mon == "MAY") |
| dv += "05"; |
| else if (mon == "JUN") |
| dv += "06"; |
| else if (mon == "JUL") |
| dv += "07"; |
| else if (mon == "AUG") |
| dv += "08"; |
| else if (mon == "SEP") |
| dv += "09"; |
| else if (mon == "OCT") |
| dv += "10"; |
| else if (mon == "NOV") |
| dv += "11"; |
| else if (mon == "DEC") |
| dv += "12"; |
| else |
| canXfrm = FALSE; |
| } |
| |
| ItemExpr * newNode = NULL; |
| if (canXfrm) |
| { |
| dv += "-"; |
| dv.append(&str[0], 2); |
| |
| // nuke the Format node and return a DATE constant |
| newNode = literalOfDate(&dv, TRUE); |
| if (newNode) |
| { |
| newNode = newNode->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| { |
| canXfrm = FALSE; |
| } |
| } |
| else |
| { |
| canXfrm = FALSE; |
| } |
| } |
| |
| if ((canXfrm) && (newNode)) |
| { |
| return newNode; |
| } |
| |
| SqlParser_Diags->clear(); |
| bindWA->resetErrStatus(); |
| } |
| |
| return NULL; |
| } |
| |
| ItemExpr * Format::bindNode(BindWA * bindWA) |
| { |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| const NAType *naType0 = &child(0)->getValueId().getType(); |
| const NumericType * nType0 = NULL; |
| |
| NABoolean formatX = FALSE; |
| NABoolean format9 = FALSE; |
| NABoolean formatExtract = FALSE; |
| Lng32 dotPos = 0; |
| NABoolean formatNumericAsX = FALSE; |
| NABoolean formatStringAsX = FALSE; |
| |
| if ((formatStr_ == "HH24") || |
| (formatStr_ == "D") || |
| (formatStr_ == "MM") || |
| (formatStr_ == "YYYY")) |
| { |
| if (CmpCommon::getDefault(MODE_SPECIAL_4) == DF_OFF) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4065) << DgString0(formatStr_) |
| << DgString1(formatType_ == FORMAT_GENERIC ? "FORMAT" |
| : (formatType_ == FORMAT_TO_CHAR ? "TO_CHAR" : "TO_DATE")); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if ((formatType_ != FORMAT_TO_CHAR) && |
| (naType0->getTypeQualifier() != NA_DATETIME_TYPE)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4071) << DgString0("TO_CHAR"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if ((naType0->getPrecision() != SQLDTCODE_TIMESTAMP) && |
| (naType0->getPrecision() != SQLDTCODE_TIME)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4072) << DgString0("TO_CHAR") << DgString1("time");; |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| formatExtract = TRUE; |
| } |
| else |
| { |
| formatX = TRUE; |
| for (CollIndex i = 0; i < formatStr_.length(); i++) |
| { |
| if ((formatStr_.data()[i] != 'X') && |
| (formatStr_.data()[i] != 'x')) |
| formatX = FALSE; |
| } |
| if (formatX) |
| { |
| if ((naType0->getTypeQualifier() == NA_CHARACTER_TYPE) && |
| ((Lng32)(formatStr_.length()) != naType0->getNominalSize())) |
| formatStringAsX = TRUE; |
| else if ((naType0->getTypeQualifier() == NA_NUMERIC_TYPE) && |
| ((nType0 = ((NumericType*)naType0))->isExact()) && |
| (NOT nType0->isBigNum()) && |
| (nType0->getScale() == 0)) |
| formatNumericAsX = TRUE; |
| else |
| formatX = FALSE; |
| } |
| |
| if (NOT formatX) |
| { |
| format9 = TRUE; |
| // check if format is of the form: 99999[.99] |
| Lng32 numDots = 0; |
| dotPos = -1; |
| for (CollIndex i = 0; i < formatStr_.length(); i++) |
| { |
| if ((formatStr_.data()[i] != '9') && |
| (formatStr_.data()[i] != '.')) |
| format9 = FALSE; |
| else if (formatStr_.data()[i] == '.') |
| { |
| numDots++; |
| dotPos = i; |
| } |
| } |
| if ((format9) && (numDots > 1)) |
| format9 = FALSE; |
| } |
| } |
| |
| ItemExpr * newIE= NULL; |
| if ((formatX) || (format9) || (formatExtract)) |
| { |
| Parser parser(bindWA->currentCmpContext()); |
| char buf[200]; |
| buf[0] = 0; |
| |
| if (formatNumericAsX) |
| { |
| Lng32 dLen = |
| nType0->getDisplayLength(nType0->getFSDatatype(), |
| nType0->getNominalSize(), |
| nType0->getPrecision(), |
| nType0->getScale(), |
| 0); |
| |
| if ((Lng32)(formatStr_.length()) < dLen) |
| { |
| sprintf(buf, "SUBSTRING(CAST(@A1 as CHAR(%d)), %d, " PFSZ ")", |
| dLen, 1, formatStr_.length()); |
| } |
| else if ((Lng32)(formatStr_.length()) >= dLen) |
| { |
| // format using Cast |
| sprintf(buf, "CAST(@A1 as CHAR(" PFSZ "))", formatStr_.length()); |
| } |
| |
| } |
| else if (formatStringAsX) |
| { |
| if ((Lng32)(formatStr_.length()) < naType0->getNominalSize()) |
| { |
| sprintf(buf, "SUBSTRING(@A1, %d, " PFSZ ")", 1, formatStr_.length()); |
| } |
| else if ((Lng32)(formatStr_.length()) > naType0->getNominalSize()) |
| { |
| // format using Cast |
| sprintf(buf, "CAST(@A1 as CHAR(" PFSZ "))", formatStr_.length()); |
| } |
| } |
| else if (format9) |
| { |
| if (dotPos == -1) |
| dotPos = 0; |
| else |
| dotPos = formatStr_.length() - dotPos -1; |
| sprintf(buf, "CAST(@A1 as NUMERIC(%d,%d))", (Lng32)formatStr_.length(), dotPos); |
| } |
| else if (formatExtract) |
| { |
| if (formatStr_ == "YYYY") |
| sprintf(buf, "CAST(EXTRACT (YEAR FROM @A1) AS CHAR(4))"); |
| else if (formatStr_ == "MM") |
| sprintf(buf, "CAST(EXTRACT (MONTH FROM @A1) AS CHAR(2))"); |
| else if (formatStr_ == "HH24") |
| sprintf(buf, "CAST(EXTRACT (HOUR FROM @A1) AS CHAR(2))"); |
| else if (formatStr_ == "D") |
| sprintf(buf, "CAST(DAYOFWEEK(@A1) AS CHAR(2));"); |
| else |
| { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| else |
| { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (strlen(buf) > 0) |
| { |
| newIE = parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 1, child(0)); |
| if (! newIE) |
| { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| newIE = newIE->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| } |
| } |
| else |
| { |
| if (CmpCommon::getDefault(MODE_SPECIAL_1) == DF_OFF) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4065) << DgString0(formatStr_) |
| << DgString1(formatType_ == FORMAT_GENERIC ? "FORMAT" |
| : (formatType_ == FORMAT_TO_CHAR ? "TO_CHAR" : "TO_DATE")); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // In mode_special_1, ignore this format and return the child pointer. |
| newIE = child(0); |
| } |
| |
| return newIE; |
| } |
| |
| /////////////////////////////////////////////////////// |
| // various error checks, details below. |
| ////////////////////////////////////////////////////// |
| NABoolean DateFormat::errorChecks(Lng32 frmt, BindWA *bindWA, |
| const NAType* opType) |
| { |
| Lng32 error = 0; |
| |
| NABoolean toChar = (formatType_ == FORMAT_TO_CHAR); |
| NABoolean toDate = (formatType_ == FORMAT_TO_DATE); |
| NABoolean toTime = (formatType_ == FORMAT_TO_TIME); |
| NABoolean df = ExpDatetime::isDateFormat(frmt); |
| NABoolean tf = ExpDatetime::isTimeFormat(frmt); |
| NABoolean tsf = ExpDatetime::isTimestampFormat(frmt); |
| NABoolean nf = ExpDatetime::isNumericFormat(frmt); |
| NABoolean ms4 = (CmpCommon::getDefault(MODE_SPECIAL_4) == DF_ON); |
| |
| if (NOT (df || tf || tsf || nf)) |
| { |
| // format must be date, time, timestamp or numeric |
| error = 1; // error 4065 |
| } |
| else if ((NOT ms4) && nf) |
| { |
| // format can only be numeric in mode_special_4 |
| error = 1; // error 4065 |
| } |
| |
| if (toDate && NOT (df || tsf)) |
| { |
| // TO_DATE requires date format or timestamp format |
| // unless we are in mode_special_4 in which case |
| // numeric format is accepted |
| if (NOT (ms4 && nf)) |
| error = 1; // error 4065 |
| } |
| |
| if (!error && toTime) |
| { |
| if (NOT tf) |
| { |
| // TO_TIME requires time format |
| error = 1; // error 4065 |
| } |
| // source must be datetime containing time field or a character string |
| else if (((opType->getTypeQualifier() == NA_DATETIME_TYPE) && |
| (opType->getPrecision() == SQLDTCODE_DATE)) || |
| (opType->getTypeQualifier() != NA_CHARACTER_TYPE)) |
| { |
| error = 9; // error 3415 |
| } |
| } |
| |
| if (!error && toChar) |
| { |
| // source must be datetime with to_char function |
| if (opType->getTypeQualifier() != NA_DATETIME_TYPE) |
| error = 2; // error 4071 |
| |
| // cannot convert date source to time format |
| else if (tf && (opType->getPrecision() == SQLDTCODE_DATE)) |
| error = 3; // error 4072 |
| |
| // cannot convert time source to date format or timestamp format |
| // for TO_CHAR only (for DATEFORMAT it is OK) |
| else if ((df || tsf) && (!wasDateformat_) && (opType->getPrecision() == SQLDTCODE_TIME)) |
| error = 8; // error 4072 |
| } |
| |
| if (!error && toDate) |
| { |
| // source must be char or numeric with to_date |
| if ((opType->getTypeQualifier() != NA_CHARACTER_TYPE) && |
| (opType->getTypeQualifier() != NA_NUMERIC_TYPE)) |
| error = 4; //error 4043 |
| |
| // source can only be numeric in mode_special_4 |
| else if ((NOT ms4) && (opType->getTypeQualifier() != NA_CHARACTER_TYPE)) |
| error = 4; // error 4043 |
| |
| // operand must be numeric with nf (numeric format) |
| else if (ms4 && nf && (opType->getTypeQualifier() != NA_NUMERIC_TYPE)) |
| { |
| error = 7; // error 4045 |
| } |
| |
| // numeric must be exact with scale of 0 |
| else if (ms4 && (opType->getTypeQualifier() == NA_NUMERIC_TYPE)) |
| { |
| if (NOT ((NumericType*)opType)->isExact()) |
| error = 5; // error 4046 |
| else if (NOT ((NumericType*)opType)->getScale() == 0) |
| error = 6; // error 4047 |
| } |
| } |
| |
| if (error) |
| { |
| switch (error) |
| { |
| case 1: |
| { |
| *CmpCommon::diags() << DgSqlCode(-4065) << DgString0(formatStr_) |
| << DgString1((toChar ? "TO_CHAR" : |
| (toDate ? "TO_DATE" : "TO_TIME"))); |
| bindWA->setErrStatus(); |
| } |
| break; |
| |
| case 2: |
| { |
| *CmpCommon::diags() << DgSqlCode(-4071) << DgString0(wasDateformat_ ? "DATEFORMAT" : "TO_CHAR"); |
| bindWA->setErrStatus(); |
| } |
| break; |
| |
| case 3: |
| { |
| *CmpCommon::diags() << DgSqlCode(-4072) << DgString0("TO_CHAR") << DgString1("time"); |
| bindWA->setErrStatus(); |
| } |
| break; |
| |
| case 4: |
| { |
| *CmpCommon::diags() << DgSqlCode(-4043) << DgString0("TO_DATE"); |
| bindWA->setErrStatus(); |
| } |
| break; |
| |
| case 5: |
| { |
| *CmpCommon::diags() << DgSqlCode(-4046) << DgString0("TO_DATE"); |
| bindWA->setErrStatus(); |
| } |
| break; |
| |
| case 6: |
| { |
| *CmpCommon::diags() << DgSqlCode(-4047) << DgString0("TO_DATE"); |
| bindWA->setErrStatus(); |
| } |
| break; |
| |
| case 7: |
| { |
| *CmpCommon::diags() << DgSqlCode(-4045) << DgString0("TO_DATE"); |
| bindWA->setErrStatus(); |
| } |
| break; |
| |
| case 8: |
| { |
| *CmpCommon::diags() << DgSqlCode(-4072) << DgString0("TO_CHAR") << DgString1("date"); |
| bindWA->setErrStatus(); |
| } |
| break; |
| |
| case 9: |
| { |
| *CmpCommon::diags() << DgSqlCode(-3415) << DgString0("TO_TIME") |
| << DgString1(" It must be a datetime datatype containing the time field or a character datatype."); |
| bindWA->setErrStatus(); |
| } |
| break; |
| |
| } // switch |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| // used for TO_DATE, TO_CHAR, TO_TIME, DATEFORMAT functions. |
| DateFormat::DateFormat(ItemExpr *val1Ptr, const NAString &formatStr, |
| Lng32 formatType, NABoolean wasDateformat) |
| : CacheableBuiltinFunction(ITM_DATEFORMAT, |
| 1, val1Ptr), |
| formatStr_(formatStr), |
| wasDateformat_(wasDateformat), |
| formatType_(formatType), |
| frmt_(-1), |
| origString_(""), |
| dateFormat_(DATE_FORMAT_NONE) |
| { |
| allowsSQLnullArg() = FALSE; |
| |
| if (formatStr_ == "SYYYYMM") |
| formatStr_ = "YYYYMM"; |
| else if (formatStr_ == "HH:MI:SS") |
| formatStr_ = "HH24:MI:SS"; |
| |
| frmt_ = ExpDatetime::getDatetimeFormat(formatStr_.data()); |
| } |
| |
| ItemExpr * DateFormat::bindNode(BindWA * bindWA) |
| { |
| if (checkForSQLnullChild(bindWA, this, allowsSQLnullArg(), FUNCTION_)) |
| return this; |
| |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| const NAType *naType0 = &child(0)->getValueId().getType(); |
| const DatetimeType* operand = (DatetimeType *)naType0; |
| const NumericType * nType0 = NULL; |
| |
| // if the date time format was not specified in TO_CHAR, supply a |
| // default now based on the datatype of the first operand |
| if (frmt_ == ExpDatetime::DATETIME_FORMAT_UNSPECIFIED) |
| { |
| if ((naType0->getTypeQualifier() == NA_DATETIME_TYPE) && |
| (operand->getPrecision() == SQLDTCODE_TIME)) |
| frmt_ = ExpDatetime::DATETIME_FORMAT_TS4; |
| else |
| frmt_ = ExpDatetime::DATETIME_FORMAT_DEFAULT; |
| formatStr_ = ExpDatetime::getDatetimeFormatStr(frmt_); |
| } |
| |
| // a quick optimization for the date format. |
| ItemExpr *newNode = quickDateFormatOpt(bindWA); |
| if (newNode) |
| return newNode; |
| |
| if (errorChecks(frmt_, bindWA, naType0)) |
| { |
| return this; |
| } |
| |
| dateFormat_ = DateFormat::DATE_FORMAT_NONE; |
| |
| if (ExpDatetime::isDateTimeFormat(frmt_)) |
| { |
| // if DATEFORMAT function was specified, then time portion of the |
| // format depends on the operand. Date portion remains the same as |
| // what was specified (DEFAULT, USA, EUROPEAN). |
| if ((wasDateformat_) && |
| (naType0->getTypeQualifier() == NA_DATETIME_TYPE)) |
| { |
| if (operand->getPrecision() == SQLDTCODE_TIMESTAMP) |
| { |
| if (frmt_ == ExpDatetime::DATETIME_FORMAT_DEFAULT) |
| frmt_ = ExpDatetime::DATETIME_FORMAT_TS3;// YYYY-MM-DD HH24:MI:SS |
| else if (frmt_ == ExpDatetime::DATETIME_FORMAT_USA) |
| frmt_ = ExpDatetime::DATETIME_FORMAT_TS7;// MM/DD/YYYY HH24:MI:SS AM|PM |
| else if (frmt_ == ExpDatetime::DATETIME_FORMAT_EUROPEAN) |
| frmt_ = ExpDatetime::DATETIME_FORMAT_TS10;// DD.MM.YYYY HH24:MI:SS |
| } |
| } |
| |
| if (ExpDatetime::isTimestampFormat(frmt_)) |
| dateFormat_ = DateFormat::TIMESTAMP_FORMAT_STR; |
| else if (ExpDatetime::isTimeFormat(frmt_)) |
| dateFormat_ = DateFormat::TIME_FORMAT_STR; |
| else |
| dateFormat_ = DateFormat::DATE_FORMAT_STR; |
| |
| if (naType0->getTypeQualifier() == NA_NUMERIC_TYPE) |
| { |
| // convert number to char before formatting. |
| // Length of target char is equal to formatStr_. |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Cast(child(0), |
| new (bindWA->wHeap()) |
| SQLChar(bindWA->wHeap(), formatStr_.length(), |
| child(0)->castToItemExpr()-> |
| getValueId().getType().supportsSQLnull())); |
| setChild(0, newChild->bindNode(bindWA)); |
| naType0 = &child(0)->getValueId().getType(); |
| } |
| } |
| else if (ExpDatetime::isNumericFormat(frmt_)) |
| { |
| dateFormat_ = DateFormat::TIME_FORMAT_STR; |
| |
| if (naType0->getTypeName() != LiteralLargeInt) |
| { |
| // convert to largeint. We have already verified that |
| // child is exact with scale of 0. |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Cast(child(0), |
| new (bindWA->wHeap()) |
| SQLLargeInt(bindWA->wHeap(), TRUE, |
| child(0)->castToItemExpr()-> |
| getValueId().getType().supportsSQLnull())); |
| setChild(0, newChild->bindNode(bindWA)); |
| } |
| } |
| else |
| { |
| CMPASSERT(FALSE); // should not reach here |
| } |
| |
| // if source is a timestamp and target is date or time, extract |
| // date or time part from source before formatting. |
| ItemExpr * newChild = NULL; |
| if (naType0->getPrecision() == SQLDTCODE_TIMESTAMP) |
| { |
| if (ExpDatetime::isTimeFormat(frmt_)) |
| { |
| newChild = |
| new (bindWA->wHeap()) |
| Cast(child(0), |
| new (bindWA->wHeap()) |
| SQLTime(bindWA->wHeap(), child(0)->castToItemExpr()-> |
| getValueId().getType().supportsSQLnull(), |
| 0)); |
| } |
| else if (ExpDatetime::isDateFormat(frmt_)) |
| { |
| newChild = |
| new (bindWA->wHeap()) |
| Cast(child(0), |
| new (bindWA->wHeap()) |
| SQLDate(bindWA->wHeap(), child(0)->castToItemExpr()-> |
| getValueId().getType().supportsSQLnull())); |
| } |
| |
| if (newChild) |
| setChild(0, newChild->bindNode(bindWA)); |
| } |
| |
| BuiltinFunction::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return getValueId().getItemExpr(); |
| } |
| |
| ItemExpr *Trim::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // this will bind/type-propagate all children. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (CmpCommon::getDefault(ALLOW_INCOMPATIBLE_OPERATIONS) == DF_ON) |
| { |
| // if argument is numeric, convert it to string. |
| const NAType &type1 = |
| child(1)->castToItemExpr()->getValueId().getType(); |
| if (type1.getTypeQualifier() == NA_NUMERIC_TYPE) |
| { |
| const NumericType &numeric = (NumericType&)type1; |
| Lng32 dLen = |
| numeric.getDisplayLength(numeric.getFSDatatype(), |
| numeric.getNominalSize(), |
| numeric.getPrecision(), |
| numeric.getScale(), |
| 0); |
| |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Cast(child(1), |
| new (bindWA->wHeap()) |
| SQLChar(bindWA->wHeap(), dLen, type1.supportsSQLnull())); |
| |
| newChild = newChild->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| setChild(1, newChild); |
| } |
| } |
| |
| // Trim inherits from BuiltinFunction .. Function .. ItemExpr. |
| BuiltinFunction::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return getValueId().getItemExpr(); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // ItemList (which is not ItemExprList!) has arity 2 but arbitrary degree |
| // (number of list elements), which we capture via this currChildNo() |
| // propagation. |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *ItemList::bindNode(BindWA *bindWA) |
| { |
| ItemList *save = bindWA->getCurrentScope()->context()->inItemList(); |
| bindWA->getCurrentScope()->context()->inItemList() = this; |
| if (save) currChildNo() = save->currChildNo(); |
| |
| ItemExpr::bindNode(bindWA); |
| |
| if (save) save->currChildNo() = currChildNo(); |
| bindWA->getCurrentScope()->context()->inItemList() = save; |
| |
| return this; |
| } // ItemList::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // A method for binding host variables, dynamic parameters and constants. |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *ItemExpr::bindUserInput(BindWA *bindWA, |
| const NAType *type, |
| const NAString &fabricatedName) |
| { |
| AssignmentStHostVars *varPtr; |
| OperatorTypeEnum opTyp = getOperatorType(); |
| |
| // If we have a SET statement of compound statements, we return the value id |
| // stored in the list assignmentStHostVars_ in bindWA. Such list contains the |
| // latest value id assigned to this variable. If it is not there, we bind this ItemExpr |
| if ( (opTyp == ITM_HOSTVAR) && |
| (bindWA->getAssignmentStArea()) && (bindWA->getAssignmentStArea()->getAssignmentStHostVars()) && |
| (varPtr = bindWA->getAssignmentStArea()->getAssignmentStHostVars()->findVar(fabricatedName))) { |
| ValueId vid = varPtr->currentValueId(); |
| if ((vid != NULL_VALUE_ID) && !(((HostVar *)this)->isHVInputAssignment())) { |
| varPtr->currentValueId().getItemExpr()->previousHostVar() = TRUE; |
| varPtr->currentValueId().getItemExpr()->previousName() = ((HostVar *)this)->getName(); |
| //markAsBound(); |
| return varPtr->currentValueId().getItemExpr(); |
| } |
| } |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| CMPASSERT(isAUserSuppliedInput()) ; |
| |
| CMPASSERT(bindWA); |
| // Currently we disallow parameters or hostvars in DDL. |
| if ( bindWA->inDDL() AND ( getOperatorType() == ITM_DYN_PARAM OR |
| getOperatorType() == ITM_HOSTVAR ) ) { |
| *CmpCommon::diags() << DgSqlCode(-3186) << DgString0(fabricatedName); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| |
| const CharType *ct = (type && type->getTypeQualifier() == NA_CHARACTER_TYPE) ? |
| (const CharType*)type : NULL; |
| |
| if (opTyp == ITM_DYN_PARAM AND getText() == "?") { |
| DynamicParam* orig = ((DynamicParam*)this)->getOriginal(); |
| if (orig == NULL) { |
| // we're an original dynamic parameter. |
| setValueId(createValueDesc(bindWA, this, type)); |
| } |
| else { |
| // dynamic parameters must preserve their valueids. |
| // otherwise, the result can be uninitialized variables at runtime. |
| // dynamic parameters are initialized in the root fragment. |
| if (orig->getValueId() == NULL_VALUE_ID) |
| orig->setValueId(createValueDesc(bindWA, this, type)); |
| setValueId(orig->getValueId()); |
| } |
| } |
| else if (opTyp == ITM_CONSTANT AND getText() == "NULL") { |
| setValueId(createValueDesc(bindWA, this, type)); |
| } |
| else if (opTyp == ITM_CACHE_PARAM) { |
| setValueId(createValueDesc(bindWA, this, type)); |
| bindWA->addInputValue(fabricatedName, getValueId()); |
| } |
| else if (ct && ct->getCharSet() == CharInfo::UnknownCharSet) |
| // Do not reuse valueId on an UnknownCharSet const value. |
| setValueId(createValueDesc(bindWA, this, type)); |
| else { |
| NAString fabName(fabricatedName, bindWA->wHeap()); |
| if (ct) { |
| // Collations might be coerced later, in type synth, |
| // going up the parse tree. We need to add this discriminant info! |
| char coco[30]; |
| sprintf(coco, " %d,%d", ct->getCollation(), ct->getCoercibility()); |
| fabName += coco; |
| } |
| ColumnNameMap *xcnmEntry = bindWA->findInputValue(fabName); |
| if (!xcnmEntry) { |
| setValueId(createValueDesc(bindWA, this, type)); |
| bindWA->addInputValue(fabName, getValueId()); |
| } |
| else { |
| if (type && |
| type->getTypeQualifier() == NA_CHARACTER_TYPE) |
| { |
| if (*type == xcnmEntry->getValueId().getType()) |
| { |
| setValueId(xcnmEntry->getValueId()); |
| } |
| else |
| { |
| // CR 10-010314-1733 |
| // For a given constant, the fabricated name needs to be distinct from |
| // other names as this name is used as the hash value. |
| // The name is initialized using the initial |
| // value set by constructor based on the length of the string. |
| // Occasionally, if the length and the initial value are same |
| // then two different constants may be initialized to the same name: this |
| // may cause problems such as type mismatch. |
| |
| // To prevent this, we create two random digits and append to |
| // the name. Since these names are on statement heap, the probability of |
| // two different constants having the same name is almost zero. |
| |
| setValueId(createValueDesc(bindWA, this, type)); |
| char fab[3]; |
| memset(fab, 0, 3); |
| sprintf(fab, "%d", rand() % 100); |
| fab[2] = '\0'; |
| fabName += fab; |
| bindWA->addInputValue(fabName,getValueId()); |
| } |
| // else continue binding this item expression |
| } |
| else |
| { |
| setValueId(xcnmEntry->getValueId()); |
| CMPASSERT(!type || |
| type->getTypeQualifier() == NA_UNKNOWN_TYPE || |
| *type == getValueId().getType()); |
| } |
| } |
| } |
| // |
| // All user inputs **except constants** are treated as |
| // outer references in the current scope. |
| // |
| if (opTyp != ITM_CONSTANT) |
| bindWA->getCurrentScope()->addOuterRef(getValueId()); |
| |
| bindSelf(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| return getValueId().getItemExpr(); |
| } // ItemExpr::bindUserInput() |
| |
| // ----------------------------------------------------------------------- |
| // Convert a backbone of certain nodes (usually AND nodes) into a value id |
| // list and get rid of the backbone (NOTE: this does not strictly |
| // require a left-linear or right-linear backbone, any shape tree with |
| // 0 or more nodes of type 'backboneType' on its top will work) |
| // ----------------------------------------------------------------------- |
| Int32 ItemExpr::convertToValueIdList(ValueIdList &vl, |
| BindWA *bindWA, |
| OperatorTypeEnum backboneType, |
| RelExpr *parent) |
| { |
| // use local stack to implement recursive calls |
| stack<ItemExpr*> stk; |
| ItemExpr *current_ie; |
| |
| stk.push (this); |
| |
| while (!stk.empty()) |
| { |
| current_ie = stk.top(); |
| stk.pop(); |
| |
| if (current_ie->getOperatorType() == backboneType) |
| { |
| if (current_ie->child(1)) |
| stk.push(current_ie->child(1)); |
| if (current_ie->child(0)) |
| stk.push(current_ie->child(0)); |
| } |
| else |
| { |
| // ok, we've reached a non-backbone node, bind the node and insert its |
| // value id into the list. |
| ItemExpr *boundExpr = current_ie; |
| if (!bindWA) |
| current_ie->synthTypeAndValueId(); |
| else |
| { |
| StmtDDLCreateView *pcvn = NULL; |
| if (bindWA->inViewDefinition()) |
| { |
| pcvn = bindWA->getCreateViewParseNode(); |
| if (pcvn->isProcessingViewColList() && |
| parent == pcvn->getQueryExpression()) |
| { |
| if (current_ie->containsSubquery()) |
| pcvn->getViewUsages().setViewIsSurelyNotUpdatableFlag(); |
| if (current_ie->getOperatorType() == ITM_REFERENCE) |
| { |
| pcvn->setItmColRefInColInRowValsFlag(TRUE); |
| ((ColReference*) current_ie)->setParent(parent); |
| } |
| else |
| pcvn->setItmColRefInColInRowValsFlag(FALSE); |
| } |
| } |
| |
| BindScope *currScope = bindWA->getCurrentScope(); |
| if (current_ie->inGroupByOrdinal()) |
| { |
| currScope->context()->inGroupByOrdinal() = TRUE; |
| } |
| if (current_ie->isGroupByExpr()) |
| { |
| currScope->context()->inGroupByExpr() = TRUE; |
| } |
| boundExpr = (ItemExpr *) (current_ie->bindNodeRoot(bindWA)); |
| if (current_ie->inGroupByOrdinal()) |
| { |
| currScope->context()->inGroupByOrdinal() = FALSE; |
| } |
| if (current_ie->isGroupByExpr()) |
| { |
| currScope->context()->inGroupByExpr() = FALSE; |
| } |
| |
| if (!boundExpr || bindWA->errStatus()) |
| return TRUE; // error |
| |
| if (pcvn && |
| pcvn->isProcessingViewColList() && |
| parent == pcvn->getQueryExpression() && |
| NOT pcvn->isItmColRefInColInRowVals()) |
| { // see notes in BindUtil_CollectColumnUsageInfo() |
| pcvn->incrCurViewColNum(); |
| } |
| else |
| if (parent && |
| bindWA->getCurrentScope()->context()->counterForRowValues()) |
| { |
| bindWA->getCurrentScope()->context()->counterForRowValuesIncr(); |
| } |
| } |
| |
| if (current_ie->getOperatorType() == ITM_REFERENCE && |
| ((ColReference*) current_ie)->getColRefNameObj().isStar() && |
| ((ColReference*) current_ie)->getStarExpansion()) |
| { |
| ((ColReference*) current_ie)->getStarExpansion()->getValueIdList(vl); |
| } |
| else |
| { |
| vl.insert(boundExpr->getValueId()); |
| } |
| |
| |
| if (bindWA && (boundExpr->getValueId() != NULL_VALUE_ID) && |
| (boundExpr->getValueId().getType().isLob())) |
| { |
| BindScope *currScope = bindWA->getCurrentScope(); |
| |
| BindContext *currContext = currScope->context(); |
| if (currContext->inOrderBy() || |
| currContext->inExistsPredicate() || |
| currContext->inGroupByClause() || |
| currContext->inHavingClause() || |
| currContext->inUnion() || |
| currContext->inJoin() |
| ) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4322); |
| bindWA->setErrStatus(); |
| return TRUE; // error |
| } |
| } |
| |
| // If the valueId of the column corresponds to the USER(x) function |
| // and appears at any place other than the outer select list, then give |
| // an error. If the user(x) function appears directly in the query |
| // then these errors will be caught while binding the user function itself. |
| // This check is for case where column derived from user(x) is being |
| // used in the query. This will have to be caught after it has got its valuId |
| // and that valueId corresponds to the user(x) function. |
| |
| if (bindWA && (boundExpr->getOperatorType() == ITM_USER)) |
| { |
| BindScope *prevScope = NULL; |
| BindScope *currScope = bindWA->getCurrentScope(); |
| |
| while (currScope) |
| { |
| BindContext *currContext = currScope->context(); |
| if (currContext->inSubquery() || |
| currContext->inOrderBy() || |
| currContext->inExistsPredicate() || |
| currContext->inGroupByClause() || |
| currContext->inWhereClause() || |
| currContext->inHavingClause() || |
| currContext->inUnion() || |
| currContext->inJoin() |
| ) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4310) |
| << DgString0("USER(x)"); |
| bindWA->setErrStatus(); |
| return TRUE; // error |
| } |
| prevScope = currScope; |
| currScope = bindWA->getPreviousScope(prevScope); |
| } |
| } |
| else if ( bindWA && |
| (bindWA->getCurrentScope()->context()->inOrderBy() || |
| bindWA->getCurrentScope()->context()->inGroupByClause()) ) |
| { |
| // Temporary fix till random is supported in ORDER BY, GROUP BY |
| // For now do not allow random in order by clause, Group By |
| // and distinct. |
| if (boundExpr->containsOpType(ITM_RANDOMNUM)) { |
| *CmpCommon::diags() << DgSqlCode(-4313); |
| bindWA->setErrStatus(); |
| return TRUE; // error |
| } |
| } |
| } |
| } |
| |
| if (stk.empty()) |
| return FALSE; |
| |
| return TRUE; |
| } // ItemExpr::convertToValueIdList() |
| |
| // |
| // MACROS used by SEMI-NON-RECURSIVE version of ItemExpr::convertToValueIdSet(...) below |
| // |
| #define AVR_STATE0 0 |
| #define AVR_STATE1 1 |
| #define AVR_STATE2 2 |
| |
| // ----------------------------------------------------------------------- |
| // Convert a backbone of certain nodes (usually AND nodes) into a value id |
| // set and get rid of the backbone (NOTE: this does not strictly |
| // require a left-linear or right-linear backbone, any shape tree with |
| // 0 or more nodes of type 'backboneType' on its top will work) |
| // ----------------------------------------------------------------------- |
| Int32 ItemExpr::convertToValueIdSet(ValueIdSet &vs, |
| BindWA *bindWA, |
| OperatorTypeEnum backboneType, |
| NABoolean tfmSubq, |
| NABoolean flattenLists) |
| { |
| // |
| // convertToValueIdSet() used to be called recursively not just |
| // for all the items in an expression but for all the items in the node |
| // tree for an entire query. Consequently, we must eliminate the recursive |
| // calls to convertToValueIdSet() by keeping the information needed by |
| // each "recursive" level in the HEAP and using a "while" loop to look |
| // at each node in the tree in the same order as the old recursive technique |
| // would have done. |
| // The information needed by each "recursive" level is basically just |
| // * a pointer to what node (ItemExpr *) to look at next, and |
| // * a "state" value that tells us where we are in the convertToValueIdSet() |
| // code for the ItemExpr node that we are currently working on |
| // |
| ARRAY( ItemExpr * ) IEarray(HEAP, 10) ; //Initially 10 elements (no particular reason to choose 10) |
| ARRAY( Int16 ) state(HEAP, 10) ; //These ARRAYs will grow automatically as needed.) |
| |
| Int32 currIdx = 0 ; |
| IEarray.insertAt( currIdx, this ) ; //Initialize first array element |
| state.insertAt( currIdx, AVR_STATE0 ) ; |
| |
| Int32 status = FALSE ; |
| |
| while( currIdx >= 0 ) |
| { |
| ItemExpr * currIE = IEarray[currIdx] ; |
| |
| if ( currIE->getOperatorType() == backboneType ) |
| { |
| switch ( state[currIdx] ) |
| { |
| case AVR_STATE0: |
| // this is a backbone node, recurse and then delete the backbone node |
| CMPASSERT( currIE->getArity() == 2 ); |
| |
| state.insertAt( currIdx, AVR_STATE1 ) ; |
| currIdx++ ; //"Recurse" down to child 0 |
| state.insertAt( currIdx, AVR_STATE0 ) ; // and start that child's state at 0 |
| IEarray.insertAt( currIdx, currIE->child(0) ) ; |
| |
| // if (status) return status; Commented out since return will be done later |
| continue ; |
| |
| case AVR_STATE1: |
| state.insertAt( currIdx, AVR_STATE2 ) ; |
| currIdx++ ; //"Recurse" down to child 1 |
| state.insertAt( currIdx, AVR_STATE0 ) ; // and start that child's state at 0 |
| IEarray.insertAt( currIdx, currIE->child(1) ) ; |
| |
| // if (status) return status; Commented out since return will be done later |
| continue ; |
| |
| case AVR_STATE2: |
| state.insertAt( currIdx, AVR_STATE0 ) ; // We are done processing 'currIE' |
| break; |
| } |
| |
| // If user had specified selectivity for the compound predicate, then make |
| // sure the child predicates get proportionate share of the selectivity. |
| if( currIE->isSelectivitySetUsingHint() ) |
| { |
| double newSelFactor = sqrt( currIE->getSelectivityFactor() ); |
| |
| currIE->child(0)->setSelectivitySetUsingHint(); |
| currIE->child(0)->setSelectivityFactor( newSelFactor ); |
| |
| currIE->child(1)->setSelectivitySetUsingHint(); |
| currIE->child(1)->setSelectivityFactor( newSelFactor ); |
| } |
| } |
| else |
| { |
| // ok, we've reached a non-backbone node, bind the node and insert its |
| // value id into the set. |
| ItemExpr *boundExpr = currIE; |
| if (!bindWA) |
| currIE->synthTypeAndValueId() ; |
| else |
| { |
| boundExpr = (ItemExpr *) currIE->bindNodeRoot(bindWA) ; |
| if (bindWA->errStatus()) |
| return TRUE; |
| if (! boundExpr) |
| { |
| bindWA->setErrStatus(); |
| return TRUE; // error |
| } |
| |
| if ( bindWA->getCurrentScope()->context()->inOrderBy() || |
| bindWA->getCurrentScope()->context()->inGroupByClause() ) |
| { |
| // Temporary fix till random is supported in ORDER BY, GROUP BY |
| // For now do not allow random in ORDER BY clause, GROUP BY |
| // and DISTINCT. |
| if (boundExpr->containsOpType(ITM_RANDOMNUM)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4313); |
| bindWA->setErrStatus(); |
| return TRUE; // error |
| } |
| } |
| |
| if ( bindWA && (boundExpr->getValueId() != NULL_VALUE_ID) && |
| ( boundExpr->getValueId().getType().isLob() ) ) |
| { |
| BindScope *currScope = bindWA->getCurrentScope(); |
| |
| BindContext *currContext = currScope->context(); |
| if (currContext->inOrderBy() || |
| currContext->inExistsPredicate() || |
| currContext->inGroupByClause() || |
| currContext->inHavingClause() || |
| currContext->inUnion() || |
| currContext->inJoin() |
| ) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4322); |
| bindWA->setErrStatus(); |
| return TRUE; // error |
| } |
| } |
| } |
| |
| if ( currIE->getOperatorType() == ITM_REFERENCE && |
| ((ColReference *)currIE)->getColRefNameObj().isStar() && |
| ((ColReference *)currIE)->getStarExpansion()) |
| { |
| const ColumnDescList& cl = *(((ColReference *)currIE)->getStarExpansion()); |
| for (CollIndex i = 0; i < cl.entries(); i++) |
| vs.insert(cl[i]->getValueId()); |
| } |
| else if ( tfmSubq && backboneType == ITM_AND |
| && currIE->containsSubquery() ) |
| { |
| // If *all* my children are MVPs, insert my transform (not me). |
| // |
| // If a "raw-mode" retry could transform *any* MVPs, |
| // insert the raw transform (containing all my subqueries) *AND* |
| // insert me too. |
| // |
| // See dissectOutSubqueries in NormItemExpr.cpp for rationale. |
| // |
| ItemExpr *tfm = currIE->transformMultiValuePredicate(FALSE/*do NOT flatten*/); |
| if (tfm) |
| tfm->convertToValueIdSet(vs, bindWA, backboneType, FALSE); |
| else |
| { |
| if (!bindWA) // do not do this at bindtime |
| tfm = currIE->transformMultiValuePredicate(FALSE, ANY_CHILD_RAW); |
| if (tfm) |
| tfm->convertToValueIdSet(vs, bindWA, backboneType, FALSE); |
| vs.insert(boundExpr->getValueId()); |
| } |
| |
| if(tfm && ( tfm->getOperatorType() == ITM_AND)) |
| { |
| tfm->synthTypeAndValueId(TRUE); |
| |
| // check for the added prefix predicate |
| ItemExpr * leftChildOfAND = (ItemExpr *)tfm->child(0); |
| |
| if((leftChildOfAND->isARangePredicate()) && |
| ((BiRelat *)leftChildOfAND)->isDerivedFromMCRP()) |
| { |
| BiRelat * addedComparison = (BiRelat *) leftChildOfAND; |
| addedComparison->translateListOfComparisonsIntoValueIds(); |
| } |
| } |
| } |
| else if ( ( flattenLists == TRUE ) |
| && ( currIE->getOperatorType() == ITM_ITEM_LIST ) ) |
| { |
| status = currIE->convertToValueIdSet(vs, bindWA, ITM_ITEM_LIST, |
| tfmSubq, flattenLists); |
| if (status) return status; |
| } |
| else |
| { |
| vs.insert(boundExpr->getValueId()); |
| } |
| } |
| if ( state[currIdx] == AVR_STATE0 ) |
| currIdx-- ; // Return to the parent node & continue working on it |
| |
| } // end of while( currIdx >= 0 ) |
| |
| return status ; |
| } // ItemExpr::convertToValueIdSet |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Aggregate |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *Aggregate::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| if(isOLAP()) { |
| ItemExpr *olap = transformOlapFunction( bindWA ); |
| if(!olap) { |
| return NULL; |
| } |
| |
| return olap->bindNode(bindWA); |
| } |
| |
| // Genesis 10-000222-6892: |
| // An AVG is really "SUM / COUNT_NONULL"; |
| // the fix saves the outer Agg Scope (bindWA->outerAggScope())from the first part (SUM) |
| // to be applied to the second part (COUNT). |
| // |
| // VARIANCE and STDDEV are likewise compositions of primitive aggr-funcs. |
| // See Variance::bindNode() and subaggBindNode() |
| // where the outer Agg Scope (bindWA->outerAggScope())is saved + reapplied in a similar fashion. |
| // |
| // Lines which have been CLONED or COPIED are marked "AggBind#n". |
| |
| if (origOpType() != ITM_AVG) // AggBind#1 support |
| bindWA->outerAggScope() = NULL; |
| |
| else if (getOperatorType() == ITM_AVG) { |
| |
| ItemExpr *sum = new (bindWA->wHeap()) |
| Aggregate(ITM_SUM, child(0), isDistinct(), ITM_AVG, '!'); |
| ItemExpr *cnt = new (bindWA->wHeap()) |
| Aggregate(ITM_COUNT_NONULL, child(0), isDistinct(), ITM_AVG, '!'); |
| ItemExpr *avg = new (bindWA->wHeap()) |
| BiArith(ITM_DIVIDE, sum, cnt); |
| avg->setOrigOpType(ITM_AVG); |
| |
| bindWA->outerAggScope() = NULL; // SUM may change this for CNT |
| |
| // |
| // NOTE: Removed code from right here. That code was calling bindNode |
| // on the *sum and *cnt nodes. The code was not needed since the |
| // following avg->bindNode() operation will call bindChildren() to bind |
| // the children. Furthermore, now that we are supporting OLAP functions, |
| // the bindNode() operation on each of the children may return a pointer |
| // to a different node (a ITM_NOTCOVERED node) than the original child |
| // nodes. bindChildren() will handle that properly, whereas the code |
| // that has been deleted from here was ignoring the possibility that |
| // sum->bindNode() and/or cnt->bindNode() might return different ptrs. |
| // |
| |
| avg = avg->bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| |
| markAsBound(); |
| setValueId(avg->getValueId()); |
| return getValueId().getItemExpr(); // AVG node is never seen again |
| |
| } // top-level AVG |
| |
| BindScope *currScope = bindWA->getCurrentScope(); |
| BindContext *context = currScope->context(); |
| |
| if (context->inAggregate()) { |
| // 4009 An aggregate is not allowed inside an aggregate. |
| *CmpCommon::diags() << DgSqlCode(-4009); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // an aggregate is not allowed to be referenced as a group by expr. |
| if (bindWA->getCurrentScope()->context()->inGroupByExpr()) { |
| *CmpCommon::diags() << DgSqlCode(-4197) |
| << DgString0("GROUP BY"); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // an aggregate is not allowed to be referenced as a group by ordinal. |
| if (bindWA->getCurrentScope()->context()->inGroupByOrdinal()) { |
| *CmpCommon::diags() << DgSqlCode(-4185); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| context->inAggregate() = TRUE; |
| |
| CMPASSERT(NOT context->colRefInAgg()); |
| CMPASSERT(NOT context->outerColRefInAgg() || |
| origOpType() == ITM_STDDEV || origOpType() == ITM_VARIANCE); |
| |
| if (checkForSQLnullChild(bindWA, this, FALSE, FUNCTION_)) return this; |
| |
| |
| // The Aggregates are bound in the environment (RETDesc) of |
| // the child of the OLAP Sequence if one exists |
| // |
| RelSequence *sequenceNode = (RelSequence *)currScope->getSequenceNode(); |
| RETDesc *currentRETDesc = NULL; |
| NABoolean isOLAPSequence = FALSE; |
| |
| // An empty requiredOrder() indicates that this Sequence is for OLAP. |
| // (may want to have a better way of indicating this. |
| // |
| if (sequenceNode && |
| (((const RelSequence *)sequenceNode)->requiredOrder().entries() == 0)) { |
| |
| currentRETDesc = currScope->getRETDesc(); |
| |
| currScope->setRETDesc(sequenceNode->child(0)->getRETDesc()); |
| isOLAPSequence = TRUE; |
| } |
| |
| if ((CmpCommon::getDefault(MODE_SPECIAL_1) == DF_ON) && |
| (getOperatorType() == ITM_SUM)) |
| { |
| // bind/type-propagate all children. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| const NAType &type1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| |
| if (type1.getTypeQualifier() == NA_CHARACTER_TYPE) |
| { |
| // convert to double precision. This will handle all precision, |
| // scale and type specified in the char value. |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Cast(child(0), |
| new (bindWA->wHeap()) |
| SQLDoublePrecision(bindWA->wHeap(), type1.supportsSQLnull())); |
| setChild(0, newChild->bindNode(bindWA)); |
| } |
| |
| } |
| |
| ItemExpr::bindNode(bindWA); // Aggregate is directly derived from ItemExpr |
| |
| // Restore current RETDesc. |
| if(isOLAPSequence) { |
| currScope->setRETDesc(currentRETDesc); |
| } |
| |
| if (bindWA->errStatus()) return NULL; |
| |
| // Now that we allow UDFs and subqueries as inputs to aggregates, we need |
| // to make sure they have a degree of 1. |
| |
| CollIndex childDegree = 0; |
| Subquery * subq = NULL; |
| UDFunction * udf = NULL; |
| Int32 origArity = getArity(); |
| |
| for (Int32 chld=0; chld < origArity; chld++) |
| { |
| switch (child(chld)->getOperatorType()) |
| { |
| case ITM_ROW_SUBQUERY: |
| case ITM_USER_DEF_FUNCTION: |
| { |
| subq = (Subquery *) child(chld)->castToItemExpr(); |
| udf = (UDFunction *) child(chld)->castToItemExpr(); |
| Lng32 myDegree = |
| (child(chld)->getOperatorType() == ITM_ROW_SUBQUERY)? |
| subq->getSubquery()->getDegree() : |
| udf->getRoutineDesc()->getOutputColumnList().entries(); |
| childDegree += myDegree; |
| // If the subquery or MVF was the only given input, and it has a |
| // degree of 2, assign child1 of the aggregate to be the |
| // second element of the subquery/MVF output. |
| // This will allow us to use a subquery or MVF to specify all |
| // inputs to STDDEV or VARIANCE |
| if ((childDegree == 2) && (getArity() == 1)) |
| { |
| ItemExprList *mDegreeList = (ItemExprList *) new(bindWA->wHeap()) |
| ItemExprList(child(chld)->castToItemExpr() |
| ,bindWA->wHeap()); |
| |
| ItemExpr * ie = mDegreeList->convertToItemExpr(); |
| ie->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| ValueIdList mDegreeVlist; |
| ie->convertToValueIdList(mDegreeVlist, bindWA, ITM_ITEM_LIST); |
| |
| child(1) = mDegreeVlist[1].getItemExpr(); |
| // we do not need to adjust the arity, since the aggregate |
| // arity method looks at how many child pointers are not null. |
| } |
| } |
| break; |
| |
| default: |
| childDegree += 1; |
| break; |
| } |
| } |
| if (getOperatorType() == ITM_STDDEV || getOperatorType() == ITM_VARIANCE) |
| { |
| if (childDegree < 1 || childDegree > 2 ) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4077) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| else |
| { |
| // We don't want to test the ITM_ONEROW since it is an internal |
| // constructs and will often have a subquery or UDF as a child |
| // The subquery or UDF outputs will eventually get a separate ITM_ONEROW |
| // per output |
| if (childDegree != 1 && getOperatorType() != ITM_ONEROW ) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4476) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| |
| context->colRefInAgg() = FALSE; |
| context->inAggregate() = FALSE; |
| |
| // Return now to Variance::bindNode() after binding child of the initial |
| // STDDEV or VARIANCE. The important thing is that returning now preserves |
| // the BindContext, in particular outerColRefInAgg() and (outer)aggScope. |
| // |
| if (getOperatorType() == ITM_STDDEV || getOperatorType() == ITM_VARIANCE) |
| return getValueId().getItemExpr(); |
| |
| // If second or subsequent part of any other decomposed aggregate like AVG, |
| // reestablish the outer agg scope (stored in bindWA->outerAggScope()) |
| if (bindWA->outerAggScope()) { // reestablish AggBind#1 |
| context->outerColRefInAgg() = TRUE; // |
| context->aggScope() = bindWA->outerAggScope(); // reestablish AggBind#2 |
| } |
| |
| if (getOperatorType() == ITM_COUNT_NONULL && |
| !isDistinct() && |
| !child(0)->getValueId().getType().supportsSQLnullLogical() && |
| !(bindWA->inDDL() && bindWA->isBindingMvRefresh()) ) { |
| context->outerColRefInAgg() = FALSE; |
| context->aggScope() = NULL; |
| ItemExpr *countStar = new (bindWA->wHeap()) SystemLiteral(1); |
| countStar = countStar->bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| setChild(0, countStar); |
| setOperatorType(ITM_COUNT); |
| } |
| |
| // Outer column reference |
| if (context->outerColRefInAgg()) { // AggBind#1 |
| currScope->addOuterRef(getValueId()); // AggBind#2 |
| context->aggScope()->addLocalRef(getValueId()); // AggBind#3 |
| context->outerColRefInAgg() = FALSE; // AggBind#4 |
| bindWA->outerAggScope() = context->aggScope(); // save for next time, maybe |
| CMPASSERT(context->aggScope() != currScope); |
| } |
| // No column reference, i.e. constant ref: COUNT(*) internally is COUNT(1) |
| else if (context->aggScope() == NULL) |
| context->aggScope() = currScope; |
| else |
| CMPASSERT(context->aggScope() == currScope); // Local column reference |
| |
| // If this is an aggregate for a scalar GroupByAgg make it nullable |
| // if it isn't already and if it isn't a count |
| if (context->aggScope()->context()->inScalarGroupBy()) { |
| |
| // Mark the aggregate expr as one that is computed by a scalar GroupByAgg |
| setInScalarGroupBy(); |
| |
| if (getOperatorType() != ITM_COUNT && |
| getOperatorType() != ITM_COUNT_NONULL) { |
| // Note that synthesizeNullableType() returns self if already nullable |
| const NAType* nullableType = |
| getValueId().getType().synthesizeNullableType(bindWA->wHeap()); |
| getValueId().changeType(nullableType); |
| } |
| } |
| |
| if (isDistinct()) |
| setDistinctValueId(child(0)->getValueId()); |
| |
| // This may introduce a GroupByAgg in RelRoot::bindNode(). |
| ValueId aggrId = context->aggScope()->addUnresolvedAggregate(getValueId()); |
| |
| // May have found an equivalent aggregate. If so, make sure we use |
| // the replacement. |
| // |
| setValueId(aggrId); |
| |
| context->aggScope() = NULL; // AggBind#6 |
| |
| // If there is an Sequence operator for OLAP functions, then add |
| // this non-OLAP Aggregate to the outputs of the Sequence operator. |
| // All outputs of the Sequence operator have an NotCovered on top of |
| // them |
| // |
| if(isOLAPSequence && |
| !context->inOtherSequenceFunction() && |
| // no need to add aggregate to sequenced columns if it is in having clasue |
| // and we don't want to add NotCovered on top of it |
| !context->inHavingClause()) |
| { |
| ItemExpr *notCov = new (bindWA->wHeap()) NotCovered (this); |
| ItemExpr *boundExpr = notCov->bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| |
| // Add the aggregate to the sequenced columns. These represent the |
| // outputs of the Sequence Operator. More outputs will be added |
| // when the Sequence operator is bound. |
| // |
| sequenceNode->addSequencedColumn(boundExpr->getValueId()); |
| |
| return boundExpr; |
| |
| } else { |
| return getValueId().getItemExpr(); |
| } |
| } // Aggregate::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // Variance::bindNode() |
| // |
| // Variance is a subclass of Aggregate. |
| // This class implements the compiler side of the Variance and Stddev |
| // aggregates. This new class redefines the {con,de}structor, the bindNode |
| // method, and the getText method. The other methods are NEVER called |
| // for this class and have (for now) been redefined to ASSERT if called. |
| // |
| // The bindNode method does some type checking and error reporting and then |
| // replaces the Variance node with a tree of nodes rooted by a ScalarVariance |
| // node. This new tree is bound and returned as the result of bindNode of |
| // Variance. Because of this, the Variance node should never appear after |
| // binding. The Variance is translated in the following way: |
| // |
| // (unweighted) |
| // Variance(<expr>) -> |
| // |
| // ScalarVariance(SUM(Cast(<expr>) * Cast(<expr>)), |
| // SUM(Cast(<expr>)), |
| // Cast(COUNT(Cast(<expr>)))) |
| // |
| // (weighted) |
| // Variance(<expr>, <weight>) -> |
| // |
| // ScalarVariance(SUM(Cast(<expr>) * Cast(<expr>) * Cast(<weight>)), |
| // SUM(Cast(<expr>) * Cast(<weight>)), |
| // SUM(Cast(<weight>))) |
| // |
| // All Cast expressions are cast to Nullable SQLDoublePrecision. |
| // Each occurrence of a common subexpression is constructed once, then reused. |
| // ----------------------------------------------------------------------- |
| |
| static Aggregate *subaggBindNode(BindWA *bindWA, |
| BindScope *currScope, |
| BindScope *outerAggScope, |
| Aggregate *&agg, // INOUT |
| const Aggregate *origAgg, |
| ValueId distinctId) |
| { |
| agg->setOriginalChild(origAgg->getOriginalChild()); |
| agg->setOrigOpType(origAgg->origOpType()); |
| |
| if (outerAggScope) { // AggBind#1 |
| BindContext *context = currScope->context(); |
| CMPASSERT(context->aggScope() == NULL || |
| context->aggScope() == outerAggScope); |
| context->aggScope() = outerAggScope; // AggBind#6,1 |
| context->outerColRefInAgg() = TRUE; // AggBind#4,1 |
| } |
| |
| agg = (Aggregate *)agg->bindNode(bindWA); // INOUT pointer |
| if (bindWA->errStatus()) return NULL; |
| |
| Aggregate *AggTmp = agg; |
| |
| // |
| // Now that we support OLAP functions, the agg->bindNode() operation above |
| // may have returned a pointer to an ITM_NOTCOVERED node. If so, we |
| // need to check its child(0) node rather than the ITM_NOTCOVERED node. |
| // |
| if (AggTmp->getOperatorType() == ITM_NOTCOVERED ) |
| AggTmp = (Aggregate *)(ItemExpr *)AggTmp->child(0); |
| |
| CMPASSERT(AggTmp->isAnAggregate()); |
| |
| if (distinctId != NULL_VALUE_ID) |
| AggTmp->setDistinctValueId(distinctId); |
| |
| return agg; //Return ptr to original node (NOTCOVERED node, if there is one) |
| } |
| |
| ItemExpr *Variance::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| if (isOLAP()) |
| { |
| if (getArity() > 1) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4078) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| ItemExpr *olap = Aggregate::bindNode(bindWA); |
| if (! olap || bindWA->errStatus()) return NULL; |
| |
| setValueId(olap->getValueId()); |
| return getValueId().getItemExpr(); |
| } |
| // Variance/Stddev must have either one or two arguments. |
| // The optional second argument is the weighting parameter. |
| // |
| if (getArity() < 1 || getArity() > 2) { |
| *CmpCommon::diags() << DgSqlCode(-4077) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // The steps to bind this node are: |
| // |
| // 1) Bind the children of this node. |
| // 2) Check the datatypes of the children. |
| // 3) Construct the replacement ItemExpr tree. |
| // 4) Bind the newly constucted tree. |
| // 5) return this as the result of binding Variance. |
| |
| // Variance is directly derived from class Aggregate. |
| // |
| ItemExpr *result; |
| ItemExpr *tmp = Aggregate::bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| |
| // Needed to do this check after binding our inputs in case we had |
| // a MVF or subquery > 1 as an input. |
| const Int32 weighted = (getArity() >= 2); |
| |
| // Variance/Stddev does not support both Distinct and Weighted simultaneously. |
| // |
| if (weighted && isDistinct()) { |
| *CmpCommon::diags() << DgSqlCode(-4078) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // |
| // Now that we support OLAP functions, the bindNode() operation above |
| // may have returned a pointer to an ITM_NOTCOVERED node. If so, we |
| // need to work with its child(0) node rather than the ITM_NOTCOVERED node. |
| // |
| if (tmp->getOperatorType() == ITM_NOTCOVERED ) |
| tmp = tmp->child(0); |
| |
| ItemExpr *boundChild0 = tmp->child(0); |
| ItemExpr *boundChild1 = tmp->child(1); |
| |
| BindScope *currScope = bindWA->getCurrentScope(); |
| BindContext *context = currScope->context(); |
| BindScope *outerAggScope = NULL; |
| if (context->outerColRefInAgg()) { // AggBind#1 |
| outerAggScope = context->aggScope(); |
| CMPASSERT(outerAggScope); |
| } |
| |
| // Get the types of the children determined by binding. |
| // Make sure they are NUMERIC or INTERVAL (later) |
| // |
| const NAType& oper0 = boundChild0->getValueId().getType(); |
| const NAType& oper1 = weighted ? boundChild1->getValueId().getType() : oper0; |
| |
| // Only NUMERIC datatypes are supported. INTERVAL will be supported later. |
| // |
| if (oper0.getTypeQualifier() != NA_NUMERIC_TYPE || |
| oper1.getTypeQualifier() != NA_NUMERIC_TYPE) { |
| *CmpCommon::diags() << DgSqlCode(-4079) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // Cast the children nodes to this type. |
| // This will force the calculations to be done using |
| // double precision floating point. |
| // Assumes that the type propogation will make the types |
| // of the child of the ScalarVariance node all double precision floats. |
| // |
| const NAType *desiredType = new (bindWA->wHeap()) SQLDoublePrecision(bindWA->wHeap(), TRUE); |
| |
| // Cast the first child to the desired type. |
| // This is the itemExpr which should be distinct. |
| // |
| ItemExpr *variChild = new (bindWA->wHeap()) Cast(boundChild0, desiredType); |
| ValueId distinctId = isDistinct() ? boundChild0->getValueId() : NULL_VALUE_ID; |
| |
| // Cast the second child (if it exists) to the desired type. |
| // If it does not exist, it is set to NULL, and should not be used. |
| // |
| ItemExpr *variWeight = weighted |
| ? new (bindWA->wHeap()) Cast(boundChild1, desiredType) |
| : NULL; |
| |
| // The weighted child is the ItemExpr |
| // (Cast(<expr>) * Cast(<weight>)) -- weighted |
| // (Cast(<expr>)) -- unweighted |
| // |
| ItemExpr *weightedChild = weighted |
| ? new (bindWA->wHeap()) BiArith(ITM_TIMES, |
| variChild, |
| variWeight) |
| : variChild; |
| |
| // The Sum of Val Squared is the ItemExpr |
| // (SUM(Cast(<expr>) * Cast(<expr>) * Cast(<weight>))) -- weighted |
| // (SUM(Cast(<expr>) * Cast(<expr>))) -- unweighted |
| // |
| Aggregate *sumOfValSquared = |
| new (bindWA->wHeap()) |
| Aggregate(ITM_SUM, |
| new (bindWA->wHeap()) |
| BiArith(ITM_TIMES, |
| variChild, |
| weightedChild), |
| isDistinct()); |
| |
| result = subaggBindNode(bindWA, currScope, outerAggScope, |
| sumOfValSquared, this, distinctId); |
| if (bindWA->errStatus()) return NULL; |
| |
| // Sum of Val is the ItemExpr (SUM(Cast(<expr>) * Cast(<weight>))) |
| // for the weighted case and (SUM(Cast(<expr>)) for the unweighted case. |
| // |
| Aggregate *sumOfVal = |
| new (bindWA->wHeap()) |
| Aggregate(ITM_SUM, |
| weightedChild, |
| isDistinct()); |
| |
| result = subaggBindNode(bindWA, currScope, outerAggScope, |
| sumOfVal, this, distinctId); |
| if (bindWA->errStatus()) return NULL; |
| |
| // Count of Val is the ItemExpr (SUM(Cast(<weight>))) for the |
| // weighted case and (Cast(COUNT(Cast(<expr>)))) for the unweighted case. |
| // |
| Aggregate *countOfVal = |
| weighted |
| ? new (bindWA->wHeap()) Aggregate(ITM_SUM, |
| variWeight, |
| isDistinct()) |
| : new (bindWA->wHeap()) Aggregate(ITM_COUNT_NONULL, |
| weightedChild, |
| isDistinct()); |
| |
| result = subaggBindNode(bindWA, currScope, outerAggScope, |
| countOfVal, this, distinctId); |
| if (bindWA->errStatus()) return NULL; |
| |
| // The result is the ItemExpr |
| // |
| // (ScalarVariance(SUM(Cast(<expr>) * Cast(<expr>) * Cast(<weight>)), |
| // SUM(Cast(<expr>) * Cast(<weight>)), |
| // SUM(Cast(<weight>)))) |
| // for the weighted case and |
| // |
| // (ScalarVariance(SUM(Cast(<expr>) * Cast(<expr>)), |
| // SUM(Cast(<expr>)), |
| // Cast(COUNT(<expr>)))) |
| // for the unweighted case. |
| // |
| // Bind the newly constructed ItemExpr Tree and return it as the |
| // result of binding the Variance node. The Variance node should |
| // not be seen after this point. |
| |
| result = new (bindWA->wHeap()) |
| ScalarVariance(getOperatorType(), |
| sumOfValSquared, |
| sumOfVal, |
| weighted |
| ? (ItemExpr *)countOfVal |
| : (ItemExpr *)new (bindWA->wHeap()) |
| Cast(countOfVal, desiredType) |
| ); |
| result->setOrigOpType(origOpType()); |
| |
| result = result->bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| |
| // Make sure that the assumptions about type propagation producing the |
| // desired types are correct. |
| // |
| NumericType &type_ret = |
| (NumericType &)(result->getValueId().getType()); |
| |
| NumericType &type_op0 = |
| (NumericType &)(result->child(0)->getValueId().getType()); |
| |
| NumericType &type_op1 = |
| (NumericType &)(result->child(1)->getValueId().getType()); |
| |
| NumericType &type_op2 = |
| (NumericType &)(result->child(2)->getValueId().getType()); |
| |
| CMPASSERT(type_ret.getTypeQualifier() == NA_NUMERIC_TYPE && |
| type_op0.getTypeQualifier() == NA_NUMERIC_TYPE && |
| type_op1.getTypeQualifier() == NA_NUMERIC_TYPE && |
| type_op2.getTypeQualifier() == NA_NUMERIC_TYPE && |
| !type_ret.isExact() && |
| !type_op0.isExact() && |
| !type_op1.isExact() && |
| !type_op2.isExact() && |
| type_ret.getBinaryPrecision() == SQL_DOUBLE_PRECISION && |
| type_op0.getBinaryPrecision() == SQL_DOUBLE_PRECISION && |
| type_op1.getBinaryPrecision() == SQL_DOUBLE_PRECISION && |
| type_op2.getBinaryPrecision() == SQL_DOUBLE_PRECISION); |
| |
| setValueId(result->getValueId()); |
| return getValueId().getItemExpr(); |
| } // Variance::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class PivotGroup |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *PivotGroup::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * result = NULL; |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| result = Aggregate::bindNode(bindWA); |
| if (! result || bindWA->errStatus()) |
| return NULL; |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class BiArith |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *BiArith::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * result = NULL; |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| if (checkForSQLnullChild(bindWA, this, FALSE, ARITH_, isUnaryNegate())) |
| return this; |
| |
| // Unary negate "-x" is represented as binary minus "0 - x". |
| // Need to change this to "INTERVAL '0...0' qualifier - x" if x is INTERVAL. |
| if (isUnaryNegate()) { |
| CMPASSERT(getOperatorType() == ITM_MINUS); |
| |
| child(1) = child(1)->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| const NAType *naType = &child(1)->getValueId().getType(); |
| |
| if (naType->getTypeQualifier() == NA_UNKNOWN_TYPE) { |
| // Emitting errmsg here is necessary for Tandem extension to allow use of |
| // INSERT INTO t VALUES(..., -DEFAULT, ...) |
| // and because here we've bypassed the checking in ItemExpr::bindSelf, |
| // by doing our own child() processing. |
| // |
| // SELECT -?param FROM t strictly speaking deserves an error message. |
| // We're going to do what the user intended, i.e. to allow it |
| // (the synthType will implicitly CAST the param to some numeric type). |
| // Genesis 10-981110-4799. |
| // |
| if (child(1)->getOperatorType() != ITM_DYN_PARAM && |
| !checkForSQLnullChild(bindWA, this, FALSE, ARITH_, isUnaryNegate())) |
| CMPASSERT(FALSE); |
| } |
| else { |
| |
| if (naType->supportsSQLnull()) { |
| // E.g., in "SELECT -CAST(99 AS INTERVAL DAY) ...", |
| // child1's type is nullable (the CAST), but the child0 we're about |
| // to fabricate needs to have a NONnullable type. |
| NAType *t = naType->newCopy(bindWA->wHeap()); |
| t->setNullable(FALSE); |
| naType = t; |
| } |
| |
| if (naType->getTypeQualifier() == NA_INTERVAL_TYPE) { |
| // NAType : IntervalType : SQLInterval : IntervalQualifier |
| const IntervalQualifier *qualifier = (IntervalQualifier *)naType; |
| NAString *literal; |
| Lng32 tmpvallen = 20; |
| char tmpval[20]; |
| CMPASSERT(tmpvallen >= qualifier->getTotalSize()); |
| qualifier->getZeroValue(tmpval, &tmpvallen, &literal, bindWA->wHeap()); |
| |
| // Replace ConstValue NUMERIC "0" (from Parser) with INTERVAL "0 units" |
| delete child(0).getPtr(); |
| child(0) = new (bindWA->wHeap()) SystemLiteral(qualifier, |
| (void *)tmpval, |
| tmpvallen, |
| literal); |
| } |
| } |
| |
| setIsUnaryNegate(FALSE); |
| } |
| |
| // See the NOTE on side effects of rounding mode on datetime arithmetic |
| // in common/OperTypeEnum.h |
| // Disable rounding on arithmetic operations added to implement |
| // DATE_TRUNC(). Usually these operations are added by Generator while |
| // adjusting scales and Generator propagates OrigOpType these operators. |
| const OperatorTypeEnum srcOrigOpType = origOpType(); |
| if (srcOrigOpType >= ITM_DATE_TRUNC_YEAR && |
| srcOrigOpType <= ITM_TSI_WEEK) |
| setIgnoreSpecialRounding(); |
| |
| // bind/type-propagate all children. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| Int32 childDegree = 0; |
| Subquery * subq = NULL; |
| UDFunction * udf = NULL; |
| Int32 origArity = getArity(); |
| |
| for (Int32 chld=0; chld < origArity; chld++) |
| { |
| if (child(chld)->getOperatorType() == ITM_USER_DEF_FUNCTION) |
| { |
| udf = (UDFunction *) child(chld)->castToItemExpr(); |
| childDegree += udf->getRoutineDesc()->getOutputColumnList().entries(); |
| } |
| else |
| childDegree += 1; |
| } |
| |
| if (childDegree > origArity) |
| { |
| NAString upperFunc(getText(), bindWA->wHeap()); |
| |
| upperFunc.toUpper(); |
| *CmpCommon::diags() << DgSqlCode(-4479) << DgString0(upperFunc) |
| << DgInt1(origArity) << DgInt2(childDegree); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // If special1 mode is on, then <datetime> +|- <number> and |
| // <interval> +|- are allowed. |
| // |
| // <number> is the interval value equivalent of the least sig field |
| // of <datetime> for datetime computation. |
| const NAType *naType0 = &child(0)->getValueId().getType(); |
| const NAType *naType1 = &child(1)->getValueId().getType(); |
| if (isDateMathFunction() && |
| ((naType0->getTypeQualifier() == NA_DATETIME_TYPE) && |
| (naType1->getTypeQualifier() != NA_INTERVAL_TYPE) )) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4116) |
| << DgString0("DATE_ADD or DATE_SUB"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if ((((naType0->getTypeQualifier() == NA_DATETIME_TYPE) && |
| (naType1->getTypeQualifier() == NA_NUMERIC_TYPE)) || |
| ((naType0->getTypeQualifier() == NA_INTERVAL_TYPE) && |
| (naType1->getTypeQualifier() == NA_NUMERIC_TYPE)) || |
| ((naType0->getTypeQualifier() == NA_NUMERIC_TYPE) && |
| (naType1->getTypeQualifier() == NA_INTERVAL_TYPE)) ) || |
| |
| (isDateMathFunction() && |
| (naType0->getTypeQualifier() == NA_DATETIME_TYPE) && |
| (naType1->getTypeQualifier() == NA_INTERVAL_TYPE)) || |
| |
| ((CmpCommon::getDefault(ALLOW_INCOMPATIBLE_OPERATIONS) == DF_ON) && |
| (((naType0->getTypeQualifier() == NA_INTERVAL_TYPE) && |
| (naType1->getTypeQualifier() == NA_INTERVAL_TYPE)) || |
| ((naType0->getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (naType1->getTypeQualifier() == NA_NUMERIC_TYPE)) || |
| ((naType0->getTypeQualifier() == NA_NUMERIC_TYPE) && |
| (naType1->getTypeQualifier() == NA_CHARACTER_TYPE)) || |
| ((naType0->getTypeQualifier() == NA_DATETIME_TYPE) && |
| (naType1->getTypeQualifier() == NA_DATETIME_TYPE))))) |
| { |
| // if datetime +|- numeric, then cast numeric to interval type. |
| // Validate that this is being done for |
| // the appropriate numeric type(exact with scale of zero). |
| if ((naType0->getTypeQualifier() == NA_DATETIME_TYPE) && |
| (naType1->getTypeQualifier() == NA_NUMERIC_TYPE)) |
| { |
| const DatetimeType* datetime = (DatetimeType*)naType0; |
| const NumericType* numeric = (NumericType*)naType1; |
| if (((getOperatorType() == ITM_PLUS) || |
| (getOperatorType() == ITM_MINUS)) && |
| ((numeric->isExact()) && |
| (NOT numeric->isBigNum()) && |
| (numeric->getScale() == 0))) |
| { |
| Lng32 maxDigits = (numeric->getMagnitude() + 9) / 10; |
| maxDigits = MINOF(maxDigits, |
| SQLInterval::MAX_LEADING_PRECISION); |
| SQLInterval * interval = |
| new(bindWA->wHeap()) SQLInterval( |
| bindWA->wHeap(), |
| naType1->supportsSQLnull(), |
| datetime->getEndField(), |
| maxDigits, |
| datetime->getEndField(), |
| 0); |
| |
| ItemExpr * newChild = |
| new(bindWA->wHeap()) Cast(child(1), interval); |
| setChild(1, newChild->bindNode(bindWA)); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| } |
| // if datetime +|- interval, then cast datetime to timestamp type. |
| |
| else if ((naType0->getTypeQualifier() == NA_DATETIME_TYPE) && |
| (naType1->getTypeQualifier() == NA_INTERVAL_TYPE)) |
| { |
| const DatetimeType* datetime = (DatetimeType*)naType0; |
| const IntervalType* interval = (IntervalType*)naType1; |
| if (interval->getEndField() > REC_DATE_DAY) |
| { |
| SQLTimestamp * ts = new(bindWA->wHeap()) SQLTimestamp ( bindWA->wHeap(), naType0->supportsSQLnull(), |
| interval->getFractionPrecision() > datetime->getFractionPrecision() |
| ?interval->getFractionPrecision() |
| :datetime->getFractionPrecision()); |
| |
| ItemExpr * newChild = |
| new(bindWA->wHeap()) Cast(child(0), ts); |
| setChild(0, newChild->bindNode(bindWA)); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| } |
| else if ((naType0->getTypeQualifier() == NA_INTERVAL_TYPE) && |
| (naType1->getTypeQualifier() == NA_NUMERIC_TYPE)) |
| { |
| const IntervalType* interval = (IntervalType*)naType0; |
| const NumericType* numeric = (NumericType*)naType1; |
| if ((numeric->isExact()) && |
| (NOT numeric->isBigNum()) && |
| (numeric->getScale() == 0) && |
| (interval->getFractionPrecision() == 0) && |
| ((getOperatorType() == ITM_PLUS) || |
| (getOperatorType() == ITM_MINUS))) |
| { |
| Lng32 maxDigits = (numeric->getMagnitude() + 9) / 10; |
| maxDigits = MINOF(maxDigits, |
| SQLInterval::MAX_LEADING_PRECISION); |
| SQLInterval * newInterval = |
| new(bindWA->wHeap()) SQLInterval( |
| bindWA->wHeap(), |
| numeric->supportsSQLnull(), |
| interval->getEndField(), |
| maxDigits, |
| interval->getEndField(), |
| 0); |
| |
| ItemExpr * newChild = |
| new(bindWA->wHeap()) Cast(child(1), newInterval); |
| setChild(1, newChild->bindNode(bindWA)); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| } |
| else if ((naType0->getTypeQualifier() == NA_NUMERIC_TYPE) && |
| (naType1->getTypeQualifier() == NA_INTERVAL_TYPE)) |
| { |
| if(isDateMathFunction()){ |
| // 4035 cannot cast between types. |
| *CmpCommon::diags() << DgSqlCode(-4035) |
| << DgString0("NUMERIC") << DgString1("DATE"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| const IntervalType* interval = (IntervalType*)naType1; |
| const NumericType* numeric = (NumericType*)naType0; |
| if ((numeric->isExact()) && |
| (NOT numeric->isBigNum()) && |
| (numeric->getScale() == 0) && |
| (interval->getFractionPrecision() == 0) && |
| ((getOperatorType() == ITM_PLUS) || |
| (getOperatorType() == ITM_MINUS))) |
| { |
| Lng32 maxDigits = (numeric->getMagnitude() + 9) / 10; |
| maxDigits = MINOF(maxDigits, |
| SQLInterval::MAX_LEADING_PRECISION); |
| SQLInterval * newInterval = |
| new(bindWA->wHeap()) SQLInterval(bindWA->wHeap(), |
| numeric->supportsSQLnull(), |
| interval->getEndField(), |
| maxDigits, |
| interval->getEndField(), |
| 0); |
| |
| ItemExpr * newChild = |
| new(bindWA->wHeap()) Cast(child(0), newInterval); |
| setChild(0, newChild->bindNode(bindWA)); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| } |
| else if ((naType0->getTypeQualifier() == NA_INTERVAL_TYPE) && |
| (naType1->getTypeQualifier() == NA_INTERVAL_TYPE)) |
| { |
| const IntervalType* interval1 = (IntervalType*)naType0; |
| const IntervalType* interval2 = (IntervalType*)naType1; |
| if ((getOperatorType() == ITM_DIVIDE) && |
| (interval1->getFractionPrecision() == 0) && |
| (interval2->getFractionPrecision() == 0)) |
| { |
| const Int16 DisAmbiguate = 0; |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Cast(child(0), |
| new (bindWA->wHeap()) |
| SQLNumeric(bindWA->wHeap(), TRUE, |
| interval1->getTotalPrecision(), |
| 0, |
| DisAmbiguate, // added for 64bit proj. |
| child(0)->castToItemExpr()-> |
| getValueId().getType().supportsSQLnull())); |
| setChild(0, newChild->bindNode(bindWA)); |
| if (bindWA->errStatus()) |
| return this; |
| |
| newChild = |
| new (bindWA->wHeap()) |
| Cast(child(1), |
| new (bindWA->wHeap()) |
| SQLNumeric(bindWA->wHeap(), TRUE, |
| interval2->getTotalPrecision(), |
| 0, |
| DisAmbiguate, // added for 64bit proj. |
| child(1)->castToItemExpr()-> |
| getValueId().getType().supportsSQLnull())); |
| setChild(1, newChild->bindNode(bindWA)); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| } |
| else if ((naType0->getTypeQualifier() == NA_DATETIME_TYPE) && |
| (naType1->getTypeQualifier() == NA_DATETIME_TYPE) && |
| (getOperatorType() == ITM_MINUS)) |
| { |
| // timestamp(0) - date = diff in days |
| // date - date = diff in days |
| // |
| // In mode_special_4, |
| // Column of DATE datatype is internally created as TIMESTAMP(0) |
| // and their diff is in days. |
| // timestamp(0) - timestamp(0) = diff in days |
| const DatetimeType* datetime1 = (DatetimeType*)naType0; |
| const DatetimeType* datetime2 = (DatetimeType*)naType1; |
| if (((datetime1->getSubtype() == DatetimeType::SUBTYPE_SQLTimestamp) && |
| (datetime2->getSubtype() == DatetimeType::SUBTYPE_SQLDate)) || |
| ((datetime1->getSubtype() == DatetimeType::SUBTYPE_SQLDate) && |
| (datetime2->getSubtype() == DatetimeType::SUBTYPE_SQLDate)) || |
| ((CmpCommon::getDefault(MODE_SPECIAL_4) == DF_ON) && |
| (datetime1->getSubtype() == DatetimeType::SUBTYPE_SQLTimestamp) && |
| (datetime2->getSubtype() == DatetimeType::SUBTYPE_SQLTimestamp) && |
| (datetime1->getFractionPrecision() == 0) && |
| (datetime2->getFractionPrecision() == 0))) |
| { |
| ItemExpr * newChild = NULL; |
| if (datetime1->getSubtype() == DatetimeType::SUBTYPE_SQLTimestamp) |
| { |
| newChild = new (bindWA->wHeap()) |
| Cast(child(0), |
| new (bindWA->wHeap()) |
| SQLDate(bindWA->wHeap(), datetime1->supportsSQLnull())); |
| setChild(0, newChild->bindNode(bindWA)); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| |
| if (datetime2->getSubtype() == DatetimeType::SUBTYPE_SQLTimestamp) |
| { |
| newChild = new (bindWA->wHeap()) |
| Cast(child(1), |
| new (bindWA->wHeap()) |
| SQLDate(bindWA->wHeap(), datetime2->supportsSQLnull())); |
| setChild(1, newChild->bindNode(bindWA)); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| } |
| } |
| else |
| { |
| Int32 srcChildIndex = -1; |
| Int32 otherChildIndex = -1; |
| if (naType0->getTypeQualifier() == NA_CHARACTER_TYPE) |
| { |
| srcChildIndex = 0; |
| otherChildIndex = 1; |
| } |
| else |
| { |
| srcChildIndex = 1; |
| otherChildIndex = 0; |
| } |
| |
| const NumericType &numeric = (NumericType&) |
| child(otherChildIndex)->getValueId().getType(); |
| |
| if ((numeric.isExact()) && |
| (NOT numeric.isBigNum()) && |
| (numeric.getScale() == 0)) |
| { |
| //doing a char to numeric conversion |
| // convert to largeint. |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Cast(child(srcChildIndex), |
| new (bindWA->wHeap()) |
| SQLLargeInt(bindWA->wHeap(), TRUE, |
| child(srcChildIndex)->castToItemExpr()-> |
| getValueId().getType().supportsSQLnull())); |
| setChild(srcChildIndex, newChild->bindNode(bindWA)); |
| } |
| else |
| { |
| // convert to double precision. This will handle all precision, |
| // scale and type specified in the char value. |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Cast(child(srcChildIndex), |
| new (bindWA->wHeap()) |
| SQLDoublePrecision(bindWA->wHeap(), child(srcChildIndex)->castToItemExpr()->getValueId().getType().supportsSQLnull())); |
| setChild(srcChildIndex, newChild->bindNode(bindWA)); |
| } |
| } |
| } |
| |
| // (BiArith is a directly derived subclass of ItemExpr; safe to invoke this) |
| result = ItemExpr::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| NAType * result_type = (NAType *)(&(getValueId().getType())); |
| if ((result_type->getTypeQualifier() == NA_NUMERIC_TYPE) && |
| (getRoundingMode()) && |
| (getOperatorType() == ITM_DIVIDE) && |
| (((NumericType*)result_type)->isExact()) && |
| (result_type->getFSDatatype() != REC_BIN64_SIGNED)) |
| { |
| // rounded division are only supported for Int64 datatypes. |
| // Make the result to be NUMERIC(MAX_PRECISION, original_result_scale). |
| // Save the current result attribute (attr_result). |
| // Converted rounded result to the original result datatype. |
| |
| const Int16 DisAmbiguate = 0; |
| NAType * orig_result_type = result_type->newCopy(bindWA->wHeap()); |
| result_type = new(bindWA->wHeap()) |
| SQLNumeric(bindWA->wHeap(), TRUE, |
| MAX_NUMERIC_PRECISION, |
| result_type->getScale(), |
| DisAmbiguate, |
| result_type->supportsSQLnull()); |
| getValueId().changeType(result_type); |
| |
| // convert result back to its original type |
| result = new(bindWA->wHeap()) Cast(result, orig_result_type); |
| result = result->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| |
| return result; |
| |
| } // BiArith::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class UnArith |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *UnArith::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * result = NULL; |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| CMPASSERT(getOperatorType() == ITM_NEGATE); |
| |
| child(0) = child(0)->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| result = ItemExpr::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return result; |
| |
| } // UnArith::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Assign |
| // ----------------------------------------------------------------------- |
| // Helper function |
| // Soln:10-040915-9806 This function is used to find the mapping between |
| // the exposed name and the base column name. This was initially introduced |
| // to find the mapping between the view column name and the base column |
| // name to report proper names in error messages. |
| const NAString& findMappedName(ValueId vid,BindWA *bindWA) |
| { |
| ValueIdList colList; |
| RETDesc *myRetDesc = bindWA->getCurrentScope()->getRETDesc(); |
| myRetDesc->getValueIdList(colList); |
| for (CollIndex i = 0; i < colList.entries(); i++) |
| { |
| if(colList[i].getNAColumn() == vid.getNAColumn()) |
| return myRetDesc->findColumn(colList[i])->getColRefNameObj().getColName(); |
| } |
| return vid.getNAColumn()->getColName(); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Assign |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *Assign::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| #ifndef NDEBUG |
| if (getenv("ASSIGN_DEBUG")) { |
| NAString unparsed(bindWA->wHeap()); |
| unparse(unparsed); |
| cout << getValueId() << ": " << unparsed << endl; |
| } |
| #endif |
| |
| ItemExpr *boundExpr = NULL; |
| ItemExpr *boundExpr_0, *boundExpr_1 ; |
| |
| boundExpr_0 = child(0)->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| child(0) = boundExpr_0; |
| |
| if (child(1)) |
| { |
| boundExpr_1 = child(1)->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| child(1) = boundExpr_1; |
| } |
| |
| |
| NABuiltInTypeEnum targetType = child(0)->castToItemExpr()->getValueId().getType().getTypeQualifier() ; |
| if (targetType == NA_LOB_TYPE) |
| { |
| if (child(1)) |
| { |
| NABuiltInTypeEnum sourceType = child(1)->castToItemExpr()->getValueId().getType().getTypeQualifier() ; |
| //If it's a dynamic param with unknown type or if it is a |
| // character type, trasnform the insert. |
| if ((((child(1)->getOperatorType() == ITM_DYN_PARAM) ||(child(1)->getOperatorType() == ITM_ROWSETARRAY_SCAN)) && sourceType == NA_UNKNOWN_TYPE) || sourceType == NA_CHARACTER_TYPE) |
| { |
| ValueId vid1 = child(1)->castToItemExpr()->getValueId(); |
| // Add a stringToLob node |
| ItemExpr *newChild; |
| const NAType &desiredType = child(0)->getValueId().getType(); |
| SQLBlob &lobType = (SQLBlob&)desiredType; |
| short fs_datatype = child(0)->castToItemExpr()->getValueId().getType().getFSDatatype(); |
| |
| NAType * newType = NULL; |
| |
| double lob_input_limit_for_batch = CmpCommon::getDefaultNumeric(LOB_INPUT_LIMIT_FOR_BATCH)*1024; |
| double lob_size = lobType.getLobLength(); |
| if (fs_datatype == REC_CLOB) { |
| newType = new (bindWA->wHeap()) SQLClob(bindWA->wHeap(), lob_input_limit_for_batch < lob_size ? lob_input_limit_for_batch : lob_size, |
| lobType.getLobStorage(), |
| TRUE, FALSE, FALSE, |
| lob_input_limit_for_batch < lob_size ? lob_input_limit_for_batch : lob_size); |
| } |
| else { |
| newType = new (bindWA->wHeap()) SQLBlob(bindWA->wHeap(),lob_input_limit_for_batch < lob_size ? lob_input_limit_for_batch : lob_size , |
| lobType.getLobStorage(), |
| TRUE, FALSE, FALSE, |
| lob_input_limit_for_batch < lob_size ? lob_input_limit_for_batch : lob_size); |
| } |
| |
| CMPASSERT(lob_input_limit_for_batch < INT_MAX); |
| vid1.coerceType(*newType, NA_LOB_TYPE); |
| if (bindWA->getCurrentScope()->context()->inUpdate()) |
| { |
| newChild = new (bindWA->wHeap()) LOBupdate( vid1.getItemExpr(), child(0), NULL,LOBoper::STRING_, FALSE,TRUE); |
| } |
| else |
| { |
| newChild = new (bindWA->wHeap()) LOBinsert( vid1.getItemExpr(),NULL, LOBoper::STRING_, FALSE,TRUE); |
| } |
| newChild->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| setChild(1, newChild); |
| } |
| } |
| } |
| // Assign is a directly derived subclass of ItemExpr; safe to invoke this |
| boundExpr = ItemExpr::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| |
| |
| // |
| if (isUserSpecified()) { |
| // |
| // Ensure the target is a column; |
| // and that it is a user column (4013). |
| // |
| const NAColumn *nacolTgt = child(0).getNAColumn(); |
| if (nacolTgt->isSystemColumn()) { |
| *CmpCommon::diags() << DgSqlCode(-4013) |
| << DgColumnName(nacolTgt->getColName()); |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| // |
| // ## For first release, |
| // ## ensure it is not a clustering column being updated (4033) |
| // ## (when we do allow it we of course must ensure, just as insert does, |
| // ## that the resulting row does not violate PK uniqueness constraint). |
| // |
| if (bindWA->getCurrentScope()->context()->inUpdate()) |
| if (nacolTgt->isClusteringKey() || |
| nacolTgt->isPrimaryKey()) |
| { |
| if (CmpCommon::getDefault(UPDATE_CLUSTERING_OR_UNIQUE_INDEX_KEY) == DF_OFF) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4033) |
| << DgColumnName(findMappedName(child(0).getValueId(),bindWA)); |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| } |
| |
| // |
| // If target requires upshifting, and source is not already upshifted, |
| // then interpose a new upshift-function node and bind it. |
| // This need not be done if not user-specified, i.e. if default value, |
| // for CatMan should be storing upshift columns' default values already |
| // upshifted. |
| // |
| if (nacolTgt->isUpshiftReqd()) { |
| boundExpr = applyUpperToSource(bindWA, boundExpr, 1); |
| if (bindWA->errStatus()) return boundExpr; |
| } |
| |
| // QSTUFF |
| // For ON ROLLBACK assignment statements we need to ensure that the |
| // respective column is not a clustering key or index column and that |
| // the respective column size is of fixed size since we can't |
| // handle variable length columns during abort. |
| if (onRollback()) { |
| |
| NAString str0(bindWA->wHeap()); |
| if (nacolTgt->isIndexKey()) str0 = "Index Key"; |
| else if (nacolTgt->isPrimaryKey()) str0 = "Primary Key"; |
| else if (nacolTgt->isClusteringKey()) str0 = "Clustering Key"; |
| else if (nacolTgt->isPartitioningKey()) str0 = "Partitioning Key"; |
| // if (nacolTgt->isReferencedForHistogram()) str0 = "Histogram Reference"; |
| |
| if (!str0.isNull()) { |
| *CmpCommon::diags() << DgSqlCode(-4177) |
| << DgString0(str0) |
| << DgColumnName(nacolTgt->getColName()); |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| |
| if (DFS2REC::isAnyVarChar(nacolTgt->getType()->getFSDatatype())) { |
| *CmpCommon::diags() << DgSqlCode(-4178) |
| << DgColumnName(nacolTgt->getColName()); |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| // test for not null on rollback |
| if (nacolTgt->getType()->supportsSQLnullLogical()) { |
| *CmpCommon::diags() << DgSqlCode(-4209) |
| << DgColumnName(nacolTgt->getColName()); |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| } // QSTUFF |
| |
| } // isUserSpecified |
| |
| targetType = child(0)->castToItemExpr()->getValueId().getType().getTypeQualifier() ; |
| |
| if ((NOT child(0)->getValueId().getType(). |
| isCompatible(child(1)->getValueId().getType())) && |
| (CmpCommon::getDefault(ALLOW_INCOMPATIBLE_OPERATIONS) == DF_ON) && |
| ((child(1)->getOperatorType() != ITM_CONSTANT) || |
| (NOT ((ConstValue *) child(1).getPtr() )->isNull()))) |
| { |
| // target type is not the same as source type. |
| // Add an explicit CAST node. |
| // All supported incompatible conversions will be handled by CAST. |
| ItemExpr * newChild = |
| new(bindWA->wHeap()) Cast(child(1), |
| &child(0)->getValueId().getType()); |
| newChild->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| setChild(1, newChild); |
| } |
| |
| |
| // If we assign a numeric type and the source has a larger scale then |
| // the target we cast the source to reduce the scale (truncate). |
| // We also cast (truncate) if we deal with char and the source is larger |
| // than the target. |
| targetType = child(0)->castToItemExpr()->getValueId().getType().getTypeQualifier() ; |
| if (targetType == NA_CHARACTER_TYPE) { |
| Lng32 sourceLength = ((CharType&)(child(1)->getValueId().getType())).getStrCharLimit(); |
| Lng32 targetLength = ((CharType&)(child(0)->getValueId().getType())).getStrCharLimit(); |
| Lng32 sourceLength_bytes = ((CharType&)(child(1)->getValueId().getType())).getNominalSize(); |
| Lng32 targetLength_bytes = ((CharType&)(child(0)->getValueId().getType())).getNominalSize(); |
| if ( (targetLength < sourceLength) || (targetLength_bytes < sourceLength_bytes) ){ |
| ItemExpr *newChild; |
| |
| // if the targetLength is smaller than sourceLength, and since the |
| // target type is of character type, make sure to set the |
| // checkTruncationError flag in the cast node if this is an insert |
| // or an update command. If MODE_SPECIAL_1 is on, then turn off the |
| // checkTruncationError flag. Also, turn off string truncation warnings |
| // in this case. |
| |
| OperatorTypeEnum opType = bindWA->getCurrentScope()->context() |
| ->inUpdateOrInsert(); |
| if ((opType == REL_UPDATE) || (opType == REL_INSERT)) |
| { |
| NABoolean specialMode = |
| (CmpCommon::getDefault(MODE_SPECIAL_1) == DF_ON); |
| |
| NABoolean checkForTrunc = TRUE; |
| NABoolean noStringTruncWarn = FALSE; |
| if (specialMode) |
| { |
| checkForTrunc = FALSE; |
| noStringTruncWarn = TRUE; |
| } |
| else |
| { |
| if (CmpCommon::getDefault(TRAF_STRING_AUTO_TRUNCATE) == DF_ON) |
| { |
| checkForTrunc = FALSE; |
| noStringTruncWarn = TRUE; |
| if (CmpCommon::getDefault(TRAF_STRING_AUTO_TRUNCATE_WARNING) == DF_ON) |
| noStringTruncWarn = FALSE; |
| } |
| } |
| |
| newChild = new(bindWA->wHeap()) Cast(child(1), |
| &child(0)->getValueId().getType(), |
| ITM_CAST, |
| checkForTrunc, |
| noStringTruncWarn); |
| } |
| else |
| newChild = new (bindWA->wHeap()) Cast(child(1), |
| &child(0)->getValueId().getType()); |
| setChild(1, newChild->bindNode(bindWA)); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| } |
| } |
| else if (targetType == NA_NUMERIC_TYPE) { |
| NumericType *source = (NumericType*) &child(1)->getValueId().getType(); |
| NumericType *target = (NumericType*) &child(0)->getValueId().getType(); |
| if (target->getScale() < source->getScale()) { |
| ItemExpr * newChild = new(bindWA->wHeap()) Cast(child(1), |
| &child(0)->getValueId().getType()); |
| setChild(1, newChild->bindNode(bindWA)); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| } |
| } |
| else if (targetType == NA_DATETIME_TYPE) { |
| DatetimeIntervalCommonType *source = |
| (DatetimeIntervalCommonType*) &child(1)->getValueId().getType(); |
| DatetimeIntervalCommonType *target = |
| (DatetimeIntervalCommonType*) &child(0)->getValueId().getType(); |
| if (target->getFractionPrecision() < source->getFractionPrecision()) { |
| ItemExpr * newChild = new(bindWA->wHeap()) Cast(child(1), |
| &child(0)->getValueId().getType()); |
| setChild(1, newChild->bindNode(bindWA)); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| } |
| } |
| |
| if (!child(0)->getValueId().getType().supportsSQLnull() && |
| child(1)->getOperatorType() == ITM_CONSTANT && |
| ( (ConstValue *) child(1).getPtr() )->isNull()) |
| { |
| // - Triggers |
| // Check if this table has before triggers on it. |
| // If so - don't create the error. Maybe a before trigger will fix this |
| // NULL value for us. If not - the error will be caught in execution. |
| // Don't check on the temp table, so the error will not be on the wrong table. |
| const NAColumn *nacol = child(0)->getValueId().getNAColumn(TRUE/*no err*/); |
| if (nacol != NULL) |
| { |
| if (nacol->getNATable()->getSpecialType() != |
| ExtendedQualName::TRIGTEMP_TABLE) |
| { |
| QualifiedName table(*nacol->getTableName(), bindWA->wHeap()); |
| if ( nacol->getNATable()->getSpecialType() == |
| ExtendedQualName::GHOST_TABLE) |
| table.setIsGhost(TRUE); |
| ComOperation op = COM_UNKNOWN_IUD; |
| switch (bindWA->getCurrentScope()->context()->inUpdateOrInsert()) |
| { |
| case REL_INSERT: op = COM_INSERT; |
| break; |
| case REL_UPDATE: op = COM_UPDATE; |
| break; |
| default : CMPASSERT(FALSE); |
| } |
| BeforeAndAfterTriggers *allTriggers = |
| bindWA->getSchemaDB()->getTriggerDB()->getTriggers(table, op, bindWA); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| if ((allTriggers==NULL) || (allTriggers->getBeforeTriggers()==NULL)) |
| { |
| // No before triggers found. Go ahead with the error. |
| // 4122 NULL cannot be assigned to NOT NULL column $column. |
| NAString colname(nacol->getFullColRefNameAsAnsiString()); |
| *CmpCommon::diags() << DgSqlCode(-4122) << DgColumnName(colname); |
| bindWA->setErrStatus(); |
| } |
| } |
| } |
| else |
| { |
| NAString colname(""); |
| *CmpCommon::diags() << DgSqlCode(-4122) << DgColumnName(colname); |
| bindWA->setErrStatus(); |
| } |
| } |
| |
| return boundExpr; |
| } // Assign::bindNode() |
| |
| |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Cast |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *Cast::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // Cast inherits from BuiltinFunction .. Function .. ItemExpr. |
| ItemExpr *boundExpr = BuiltinFunction::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| |
| if (getType()->getTypeQualifier() == NA_CHARACTER_TYPE && |
| ((CharType *)getType())->isUpshifted()) |
| boundExpr = applyUpperToSource(bindWA, boundExpr, 0); |
| |
| // COMMENTED OUT -- causing problems in Generator key-building -- ## |
| // FIX LATER -- for now, just catch this problem at run-time instead of compile, |
| // via Executor error 8421 ... |
| // |
| // if (!getType()->supportsSQLnull() && |
| // child(0)->getOperatorType() == ITM_CONSTANT && |
| // ( (ConstValue *) child(0).getPtr() )->isNull()) { |
| // // 4123 NULL cannot be cast to a NOT NULL datatype. |
| // *CmpCommon::diags() << DgSqlCode(-4123); |
| // bindWA->setErrStatus(); |
| // } |
| |
| // in mode_special_1, if a character datatype is being converted |
| // to a numeric type, then treat an empty string or a string with all spaces |
| // to be the same as the value 0. |
| if ((CmpCommon::getDefault(MODE_SPECIAL_1) == DF_ON) && |
| (child(0)->castToItemExpr()->getValueId().getType().getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (getType()->getTypeQualifier() == NA_NUMERIC_TYPE)) |
| { |
| setTreatAllSpacesAsZero(TRUE); |
| } |
| |
| return boundExpr; |
| } // Cast::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Like |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *PatternMatchingFunction::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| NABoolean savedInPred = bindWA->getCurrentScope()->context()->inPredicate(); |
| bindWA->getCurrentScope()->context()->inPredicate() = TRUE; |
| |
| // this will bind/type-propagate all children. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| //================================================================= |
| //10-040212-3209-begin |
| // Like inherits from Function .. ItemExpr. |
| //10-040212-3209-end |
| |
| // case 10-020314-7397 |
| // BuiltInFunction::bindNode() seems to add a cast node on top of |
| // all base columns and this results in a full table scan |
| // for queries like |
| // select * from t where a like 'abc%'; |
| // hence like will inherit from Function not BuiltInFunction |
| //================================================================== |
| ItemExpr *boundExpr = Function::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // update both operands if case insensitive comparions |
| // are to be done. |
| const NAType &type1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| const NAType &type2 = |
| child(1)->castToItemExpr()->getValueId().getType(); |
| |
| if ((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (type2.getTypeQualifier() == NA_CHARACTER_TYPE)) |
| { |
| const CharType &cType1 = (CharType&)type1; |
| const CharType &cType2 = (CharType&)type2; |
| |
| NABoolean doCIcomp = |
| ((cType1.isCaseinsensitive()) && (cType2.isCaseinsensitive())); |
| ItemExpr * newChild = NULL; |
| if (doCIcomp) |
| { |
| if (NOT cType1.isUpshifted()) |
| { |
| newChild = new (bindWA->wHeap()) Upper(child(0)); |
| newChild->bindNode(bindWA); |
| setChild(0, newChild); |
| } |
| if (NOT cType2.isUpshifted()) |
| { |
| newChild = new (bindWA->wHeap()) Upper(child(1)); |
| newChild->bindNode(bindWA); |
| setChild(1, newChild); |
| } |
| // for CaseInSensitive escape character, Soln 10-080310-1225 |
| if (getArity()>2) |
| { |
| const NAType &type3 = |
| child(2)->castToItemExpr()->getValueId().getType(); |
| if (type3.getTypeQualifier() == NA_CHARACTER_TYPE) |
| { |
| const CharType &cType3 = (CharType&)type3; |
| if (NOT cType3.isUpshifted()) |
| { |
| newChild = new (bindWA->wHeap()) Upper(child(2)); |
| newChild->bindNode(bindWA); |
| setChild(2, newChild); |
| } |
| } |
| } |
| } |
| |
| } |
| |
| bindWA->getCurrentScope()->context()->inPredicate() = savedInPred; |
| |
| return applyBeginEndKeys(bindWA, boundExpr, bindWA->wHeap()); |
| |
| } // PatternMatchingFunction::bindNode() |
| |
| NABoolean PatternMatchingFunction::beginEndKeysApplied(CollHeap *heap) |
| { |
| // Called by optimizer, long after binding (thus bindNode has already |
| // called the common method applyBeginEndKeys and done the appropriate |
| // node allocations there). Calling applyBeginEndKeys again in optimizer |
| // rather than setting a flag in the Like object was chosen so we don't |
| // have to analyze the survivability of a new flag across optimizer's |
| // copyTopNode and rules assigning object members here and there. |
| |
| return beginEndKeysApplied_; |
| |
| } // PatternMatchingFunction::beginEndKeysApplied() |
| |
| ItemExpr *Regexp::applyBeginEndKeys(BindWA *bindWA, ItemExpr *boundExpr, |
| CollHeap *heap) |
| { |
| return boundExpr; |
| } |
| |
| ItemExpr *Like::applyBeginEndKeys(BindWA *bindWA, ItemExpr *boundExpr, |
| CollHeap *heap) |
| { |
| CMPASSERT((bindWA && boundExpr) || (!bindWA && !boundExpr)); |
| CMPASSERT(heap); |
| |
| // Assert that Like::bindNode, and importantly, Like::synthesizeType, |
| // has been called -- the latter ensures all LIKE arguments are of |
| // compatible/coercible character string type. |
| CMPASSERT(nodeIsBound()); |
| |
| // Now we want to optimize the common case where the pattern is a literal |
| // and so is the optional escape character, if one. |
| // Note that we don't care about the match value (first argument) here. |
| // Help the optimizer by ANDing another predicate above this node, |
| // letting the optimizer estimate selectivity and avoid a full table scan. |
| // mv LIKE 'ab%yz' -> mv >= 'ab{0}' AND mv LIKE 'ab%yz' AND mv < 'ac{0}' |
| // mv LIKE 'ab_yz' -> mv >= 'ab{0}' AND mv LIKE 'ab_yz' AND mv < 'ac{0}' |
| // where {0} is a sequence of enough ascii-zero characters to fill the |
| // literal comparand out to the same length as mv. |
| // |
| // Further, LIKE processing can be optimized away entirely: |
| // mv LIKE 'ab%' -> mv >= 'ab{0}' AND mv < 'ac{0}' |
| // ('ab%', 'ab%%', 'ab%%%' -- all can have the LIKE optimized away.) |
| // Note that 'ab_' CANNOT have the LIKE optimized away. |
| // |
| // mv LIKE 'ab' -> mv = 'ab' [if mv is CHAR type] |
| // mv LIKE '%' -> if mv is nullable return IS_NOT_NULL else return TRUE |
| // |
| // We do not optimize case like: |
| // mv LIKE 'ab' if mv is of VARCHAR type |
| // |
| // The following begin with wildcards; no optimization is possible: |
| // mv LIKE '%yz' , mv LIKE '_yz' , mv LIKE '_' -- no change |
| // |
| // Some of this code copied from ex_like_clause::eval(). |
| |
| ItemExpr *matchExpr = child(0)->castToItemExpr(); // arity 1 |
| ItemExpr *strExpr = child(1)->castToItemExpr(); // arity 2 |
| |
| const ConstValue *patternNode, *escapeNode = NULL; |
| |
| NABoolean specialMode = (CmpCommon::getDefault(MODE_SPECIAL_1) == DF_ON); |
| |
| if (specialMode) |
| { |
| if(strExpr->getOperatorType() == ITM_UPPER) |
| strExpr = strExpr->child(0); |
| } |
| |
| patternNode = (ConstValue *)strExpr->castToItemExpr(); // arity 2 |
| NABoolean optimizeLike = |
| patternNode->getOperatorType() == ITM_CONSTANT && !patternNode->isNull(); |
| if (getArity() > 2) |
| { |
| ItemExpr *escapeExpr = child(2)->castToItemExpr(); |
| if (specialMode) |
| { |
| if(escapeExpr->getOperatorType() == ITM_UPPER) |
| escapeExpr = escapeExpr->child(0); |
| } |
| escapeNode = (ConstValue *)escapeExpr->castToItemExpr(); // arity 3 |
| optimizeLike = optimizeLike && |
| escapeNode->getOperatorType() == ITM_CONSTANT && !escapeNode->isNull(); |
| } |
| |
| // The following comment is added based on the inspection discussion for fix |
| // for 10-040127-4126 (NF: LIKE with ESCAPE kanji char '_' gets assertion failure ). |
| // |
| // optimizerLike = true means both the pattern and the escape clause contain constants |
| // are not NULL. It is possible that this optimization step is bypassed when either |
| // clause contains a non-constant expression with all components are constants. After |
| // constant-folding, the expression evaluates to a constant value and the optimization |
| // step can be performed, if this function is called (occasionally from the Analyzer |
| // or the Optimizer). If this function is not called, then some error, if any, will be |
| // caught during run-time. |
| // |
| // |
| // On the other hand, the cost of calling binder after const-folding, the infrequent |
| // use of such constant expressions in real-world queries, and the error will get |
| // caught during run-time, this defect can be considered as a "limitation or regret" |
| // of the current architecture. It is a defect nice-to-be-fixed but the overhead of |
| // fixing it and its consequence can be very big for us to bear. |
| // |
| if (optimizeLike) |
| { |
| // Seems like a weird way to cast, but only way to do in on NT C++ |
| const CharType *matchCharType = (const CharType *) |
| &matchExpr->getValueId().getType(); |
| // Already ensured by Like::synthesizeType, but what the heck... |
| |
| CMPASSERT(matchCharType->getTypeQualifier() == NA_CHARACTER_TYPE); |
| |
| // 2/15/98: remove the following assertion. |
| //CMPASSERT(matchCharType->getBytesPerChar() == 1); |
| |
| const char *escapeChar = NULL; |
| Int32 escapeChar_len = 0; |
| if (escapeNode) |
| { |
| CharInfo::CharSet cs = matchCharType->getCharSet(); |
| escapeChar_len = escapeNode->getRawText()->length(); |
| escapeChar = escapeNode->getRawText()->data(); |
| |
| if ( CharInfo::isSingleByteCharSet(cs) && escapeChar_len == 1) /*ok*/; |
| else if (! CharInfo::isSingleByteCharSet(cs) && escapeChar_len == 2) /*ok*/; |
| else |
| { |
| *CmpCommon::diags() << DgSqlCode(EXE_INVALID_ESCAPE_CHARACTER, |
| DgSqlCode::ERROR_); |
| if (bindWA) |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| } // if (escapeNode) |
| |
| // |
| // We currently cannot optimize if CZECH collation is involved |
| // because the Optimizer doesn't understand various things about |
| // CZECH collation ... such as H < CH < I. |
| // |
| // Since all arguments to LIKE must have the same collation (or |
| // they would not be comparable), we need to check only one |
| // of the arguments. |
| // |
| CharInfo::Collation Coll = matchCharType->getCollation(); |
| if ( Coll == CharInfo::CZECH_COLLATION ) |
| { |
| return boundExpr; // Cannot optimize |
| } |
| |
| // 2/15/98: prepare the right version of underscore, |
| // percent any bytesPerChar info. |
| |
| const char* underscoreChar; |
| UInt16 underscoreChar_len = BYTES_PER_NAWCHAR; |
| NAWchar wideUnderscoreChar; |
| switch (matchCharType->getCharSet()) |
| { |
| case CharInfo::UNICODE: |
| underscoreChar = (char*)L"_"; |
| break; |
| |
| case CharInfo::KANJI_MP: |
| wideUnderscoreChar = kanji_char_set::underscore_char(); |
| underscoreChar = (char*)&wideUnderscoreChar; |
| break; |
| |
| case CharInfo::KSC5601_MP: |
| wideUnderscoreChar = ksc5601_char_set::underscore_char(); |
| underscoreChar = (char*)&wideUnderscoreChar; |
| break; |
| |
| case CharInfo::ISO88591: |
| default: |
| underscoreChar = (char*)"_"; |
| underscoreChar_len = 1; |
| break; |
| } |
| |
| const char* percentChar; |
| UInt16 percentChar_len = BYTES_PER_NAWCHAR; |
| NAWchar widePercentChar; |
| switch (matchCharType->getCharSet()) |
| { |
| case CharInfo::UNICODE: |
| percentChar = (char*)L"%"; |
| break; |
| |
| case CharInfo::KANJI_MP: |
| widePercentChar = kanji_char_set::percent_char(); |
| percentChar = (char*)&widePercentChar; |
| break; |
| |
| case CharInfo::KSC5601_MP: |
| widePercentChar = ksc5601_char_set::percent_char(); |
| percentChar = (char*)&widePercentChar; |
| break; |
| |
| case CharInfo::ISO88591: |
| default: |
| percentChar = (char*)"%"; |
| percentChar_len = 1; |
| break; |
| } |
| |
| const char* pattern_str = 0; |
| Int32 pattern_str_len = 0; |
| |
| pattern_str = patternNode->getRawText() -> data(); |
| pattern_str_len = patternNode->getRawText()->length(); |
| |
| CharInfo::CharSet cs = matchCharType->getCharSet(); |
| |
| |
| LikePatternString patternString( pattern_str, |
| pattern_str_len, cs, |
| escapeChar, escapeChar_len, |
| underscoreChar, underscoreChar_len, |
| percentChar, percentChar_len |
| ); |
| |
| LikePattern pattern(patternString, heap, cs); |
| |
| if (pattern.error()) |
| { |
| if(pattern.error() == EXE_INVALID_CHARACTER) |
| { |
| *CmpCommon::diags() << DgSqlCode(pattern.error(), DgSqlCode::ERROR_) |
| << DgString0(CharInfo::getCharSetName(cs)) |
| << DgString1("LIKE PATTERN"); |
| } |
| else |
| *CmpCommon::diags() << DgSqlCode(pattern.error(), DgSqlCode::ERROR_); |
| |
| // fix for 10-040127-4126. |
| // Like::beginEndKeysApplied(CollHeap *heap) actually supplies |
| // a NULL bindWA and NULL boundExpr when calling this method during the ANALYSIS |
| // phase. Need to check the nullness of bindWA before call its members. |
| if (bindWA) |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| |
| if (bindWA) |
| { |
| // we are here from the Binder and not from the Optimizer |
| // so calculate the total number of non_wildcard characters |
| // including underscores and set them in the Like expression. |
| // This will be used later in the Optimizer to estimate the |
| // selectivity |
| setNumberOfNonWildcardChars(patternString); |
| |
| // do all equality and '%' transformations, only if the call is from |
| // the Binder. |
| |
| // We first check if the predicate like '%' is being applied to a |
| // not_nullable column. If the column does not allow NULLS, then |
| // the like % is transformed to '*' or TRUE. Similarly predicates such |
| // as col LIKE '____'(N underscores) for not nullable columns of length |
| // N are converted to TRUE. |
| // For nullable columns, the LIKE predicate is transformed to IS_NOT_NULL. |
| |
| Int32 BytesInNonWildCardChars = getBytesInNonWildcardChars(); |
| if (BytesInNonWildCardChars == 0) |
| { |
| if (( pattern.getNextHeader() && |
| !pattern.getNextHeader()->getNextHeader() && |
| (pattern.getNextHeader()->getType() == LikePatternStringIterator:: PERCENT) && |
| ((pattern.getType() != LikePatternStringIterator:: UNDERSCORE) || // like '%' |
| (matchCharType->getNominalSize() >= pattern.getLength()) && |
| (!matchCharType->isVaryingLen()))) // like '___%', but not like '___%_' col len >= # of underscores |
| || |
| (!pattern.getNextHeader() && |
| (pattern.getType() == LikePatternStringIterator:: UNDERSCORE) && |
| (!matchCharType->isVaryingLen()) && |
| (!CharInfo::isVariableWidthMultiByteCharSet(cs)) && |
| // like '___', column must be same length as pattern |
| (matchCharType->getStrCharLimit() == pattern.getLength()))) |
| { |
| ItemExpr * result = NULL; |
| if (!(matchExpr->getValueId().getType().supportsSQLnullLogical()) ) |
| result = new (heap) BoolVal(ITM_RETURN_TRUE); |
| else |
| { |
| //10-061019-9936 -Begin |
| // For nullable columns, the LIKE predicate is transformed as shown |
| // in case statement below. |
| |
| Parser parser(bindWA->currentCmpContext()); |
| ItemExpr * itmtrue = NULL, *itmnull = NULL; |
| char buf[200]; |
| buf[0] = 0; |
| itmnull = new(heap) BoolVal(ITM_RETURN_NULL); |
| itmtrue = new(heap) BoolVal(ITM_RETURN_TRUE); |
| strcpy(buf, "CASE WHEN @A1 is not null then @A2 ELSE @A3 END;"); |
| |
| result = parser.getItemExprTree(buf,strlen(buf), BINDITEMEXPR_STMTCHARSET, 3, matchExpr, itmtrue,itmnull); |
| |
| //10-061019-9936 -End |
| } |
| boundExpr = result; |
| |
| // Now bind the newly created expression, and return |
| boundExpr = boundExpr->bindNode(bindWA); |
| return boundExpr; |
| } |
| } // if (BytesInNonWildCardChars == 0) |
| |
| // Then check for cases - col like 'ab'. These will be converted to |
| // equality predicates. |
| |
| // If it is a CHAR type and pattern_str_len contains the total number |
| // of characters in the like pattern |
| // Get number of non-wild-characters in the string. If they are equal, then |
| // that implies there are no wild card characters. |
| // Then transform the like predicate as follows: |
| // Col LIKE 'ab' can be transformed to FALSE if Col is not nullable, |
| // and (Col a CHAR(n) with n <> 2, or if Col is a VARCHAR(n) with n < 2) |
| // if col is a CHAR type then it is transormed to an equality predicate |
| |
| if (pattern_str_len == BytesInNonWildCardChars) |
| { |
| // set a flag in the expression to indicate that the pattern is a |
| // string literal (col LIKE 'ab'). This will be used later to set |
| // the selectivity of LIKE predicate, if for some reason the LIKE |
| // predicate could not be transformed. |
| setPatternAStringLiteral(); |
| |
| // take care of all cases if the column is VARCHAR type |
| if (matchCharType->getTypeName() == "VARCHAR") |
| { |
| if ( ( !matchCharType->supportsSQLnullLogical() ) && |
| (matchCharType->getNominalSize() < BytesInNonWildCardChars ) ) |
| { |
| // if the col is VARCHAR type and the length of the col is |
| // less than the length of the pattern, return FALSE |
| ItemExpr * result = new (heap) BoolVal(ITM_RETURN_FALSE); |
| boundExpr = result; |
| |
| // Now bind the newly created equality expression, and return |
| boundExpr = boundExpr->bindNode(bindWA); |
| return boundExpr; |
| } |
| else |
| { |
| // length of the column is equal to or greater than the length |
| // of the pattern. Ideally we should have been able to convert the |
| // LIKE predicate to an equality predicate of exact length, but |
| // because of a bug in equality predicates on VARCHAR columns, we |
| // will set the default selectivity equal to 1/UEC. The bug is |
| // followed by Sol: 10-050412-6599. Once this problem is fixed |
| // the LIKE predicate should be transformed to |
| // col LIKE 'ab' -> col = 'ab' and char_length(col) = 2 |
| // As of now we shall not do anything right now, and take care |
| // of it later while estimating cardinality. |
| |
| return boundExpr; |
| } |
| } // col LIKE 'ab' on a VARCHAR column |
| else |
| { |
| if ( !matchCharType->supportsSQLnullLogical() && |
| (matchCharType->getNominalSize() != BytesInNonWildCardChars ) ) |
| { |
| // col is not nullable, is CHAR type but the length of the col |
| // is not same as the LIKE pattern length. Hence return FALSE |
| ItemExpr * result = new (heap) BoolVal(ITM_RETURN_FALSE); |
| boundExpr = result; |
| |
| // Now bind the newly created equality expression, and return |
| boundExpr = boundExpr->bindNode(bindWA); |
| return boundExpr; |
| |
| } |
| else |
| { |
| if (matchCharType->getNominalSize() == BytesInNonWildCardChars ) |
| { |
| // col is the same length as the pattern, so transform the |
| // predicate into an equality predicate |
| // Left child of the expression would be matchExpr, which is the left |
| // child of Like pred, which is the column |
| // Right child of the equality expression will be the Like pattern |
| // which is a literal. Type of the literl is same as the type of |
| // the column it is being equated to. |
| |
| ItemExpr * child1 = new (heap) SystemLiteral( |
| NAString(pattern_str, pattern_str_len), |
| matchCharType->getCharSet(), |
| matchCharType->getCollation(), |
| matchCharType->getCoercibility() |
| ); |
| if ((specialMode) && (matchCharType->isCaseinsensitive())) |
| child1 = new (bindWA->wHeap()) Upper(child1); |
| |
| BiRelat *eqExpr = |
| new (heap) BiRelat(ITM_EQUAL, |
| matchExpr, |
| child1, |
| FALSE, // specialNulls flag |
| FALSE |
| ); |
| |
| eqExpr->setOriginalLikeExprId(getValueId() ); |
| |
| boundExpr = eqExpr; |
| |
| // Now bind the newly created equality expression, and return |
| boundExpr = boundExpr->bindNode(bindWA); |
| |
| return boundExpr; |
| } // matchCharType->getNominalSize() == BytesInNonWildCardChars |
| } // length of col <> pattern length, and col is NULLable |
| } // col is CHAR (dataType.getTypeName() <> "VARCHAR") |
| |
| // for all other cases, return without any transformation. Apply default |
| // selectivity later. |
| return boundExpr; |
| |
| } // if (pattern_str_len == BytesInNonWildCardChars) |
| } // end of all the transformations for special cases |
| |
| if (pattern.getType() == LikePatternStringIterator::NON_WILDCARD && |
| pattern.getClauseLength() > 0) |
| { |
| // If called by beginEndKeysApplied, return non-NULL pointer, |
| // so beginEndKeysApplied() will return TRUE. |
| if (!bindWA) return this; |
| |
| // Test if pattern ends with percent and has no underscores -- |
| // 'ab%', 'ab%%', 'ab%%%', ..., but not |
| // 'ab', 'ab_', 'ab%yz', 'ab%_', 'ab%_yz', ... -- |
| // If so then we can optimize the LIKE away entirely. |
| // |
| if (pattern.getLength() == pattern.getClauseLength() && |
| pattern.getNextHeader() && |
| pattern.getNextHeader()->getType() == LikePatternStringIterator:: |
| PERCENT && |
| !pattern.getNextHeader()->getNextHeader()) |
| { |
| CMPASSERT(!pattern.getNextClause() && |
| !pattern.getNextHeader()->getNextClause()); |
| CMPASSERT(pattern.getNextHeader()->getLength() == |
| pattern.getNextHeader()->getClauseLength()); |
| // boundExpr==this, so do not delete it! |
| boundExpr = NULL; |
| } |
| |
| // Get the prefix, e.g. "ab", and the minimal key value, e.g. '\0' |
| NAString prefix(pattern.getPattern(), |
| pattern.getClauseLength(), |
| heap); |
| Lng32 zeroChar = matchCharType->getMinSingleCharacterValue(); |
| |
| // Get the maximum length of the comparands. |
| // Note that there's another silly case we don't bother optimizing |
| // but just allow here (the if-test, after which we widen matchLen): |
| // m < p (where m = mv length, p = pattern non-wild prefix length) |
| // could be optimized to |
| // if mv is null return NULL else return FALSE |
| // |
| |
| // matchLen must be number of BYTES, not SQL CHAR's |
| size_t matchLen = matchCharType->getNominalSize(); |
| |
| if (matchLen < prefix.length()) matchLen = prefix.length(); |
| |
| // Zero-pad into prefixZ, e.g. for mv type VARCHAR(4), make "ab\0\0" |
| char *prefixZ = new (heap) char[matchLen]; |
| byte_str_cpy(prefixZ, matchLen, prefix.data(), prefix.length(), |
| (char)zeroChar); |
| |
| ItemExpr *child1 = new (heap) SystemLiteral( |
| NAString(prefixZ, matchLen), |
| matchCharType->getCharSet(), |
| matchCharType->getCollation(), |
| matchCharType->getCoercibility() |
| ); |
| if ((specialMode) && (matchCharType->isCaseinsensitive())) |
| child1 = new (bindWA->wHeap()) Upper(child1); |
| |
| ItemExpr *beginKey = |
| new (heap) BiRelat(ITM_GREATER_EQ, matchExpr, |
| child1, |
| FALSE, // specialNulls flag |
| FALSE // derivative from Like |
| ); |
| ((BiRelat*)beginKey)->setAddedForLikePred(TRUE); |
| |
| // Now set the selectivity that should be applied for this |
| // predicate. |
| |
| // Selectivity from like would depend on the number of non_wildcard |
| // characters in the Like string. |
| // Selectivities are computed as follows: |
| // ab% -> >= ab and < c. The two range predicates are applied the usual way |
| // but the final selectivity is based on the default selectivity of like |
| // predicates adjusted based on the number of non-wildcard characters. This |
| // selectivity is applied to the first range predicate. Selectivity of |
| // the second range predicate is set to 1. |
| // a%b -> >= a and < b and like %b. The selectivities in this case are |
| // computed and applied similar to the previous case. This implies, that |
| // we shall compute the selectivity based on the number of non-wildcard |
| // characters. Apply that to the first range predicate and would use |
| // selectivity equal to 1 for the two remaining predicates (> b and like %b) |
| |
| double selectivity = computeSelForNonWildcardChars(); |
| |
| // If user had specified selectivity for original LIKE predicate via selectivity |
| // hint, then store that as LikeSelectivity on BiRelat predicate and unset the |
| // selecitivity hint on the original LIKE predicate. |
| if(isSelectivitySetUsingHint()) |
| { |
| selectivity = getSelectivityFactor(); |
| beginKey->setSelectivitySetUsingHint(); |
| beginKey->setSelectivityFactor(selectivity); |
| setSelectivitySetUsingHint(FALSE); |
| setSelectivityFactor(-1); |
| } |
| |
| BiRelat *br = (BiRelat *) beginKey; |
| br->setLikeSelectivity(selectivity); |
| // Like pred has non_wildcard beginning and ends in % |
| // Later we will collapse histogram into one interval if this flag is set. |
| // In this simple case, it is better to not flag this BiRelat as |
| // originating from LIKE so that we get a better histogram on it. |
| // We may lose some knowledge of correlation between begin/end keys |
| // but it is better to have 2 unrelated birelats with good stats than |
| // correlated begin/end preds with a single interval histogram. JIRA 2512 |
| if(boundExpr) |
| br->setOriginalLikeExprId(getValueId()); |
| |
| // Compute the value following the beginKey prefix: |
| // If beginKey == 'ab', this will return 'ac'; |
| // if 'a\377', then 'b'; if '\377\377', then '' and FALSE; |
| // if can't compute next key (due to multibyte chars or nondefault |
| // collating sequence), then it returns '' and FALSE. |
| // If we get a TRUE return, then build endKey predicate. |
| // |
| |
| NABoolean foundNextKey = FALSE; |
| |
| if ( matchCharType->getCharSet() == CharInfo::ISO88591 ) |
| { |
| foundNextKey = matchCharType->computeNextKeyValue(prefix); |
| byte_str_cpy(prefixZ, matchLen, prefix.data(), prefix.length(), |
| (char)zeroChar); |
| } |
| else if ( matchCharType->getCharSet() == CharInfo::UCS2 ) |
| { |
| NAWString prefixW((NAWchar*)pattern.getPattern(), |
| pattern.getClauseLength()>>1 |
| ); |
| foundNextKey = matchCharType->computeNextKeyValue(prefixW); |
| byte_str_cpy(prefixZ, matchLen, |
| (char*)prefixW.data(), prefixW.length()<<1, |
| (char)zeroChar |
| ); |
| } |
| else if ( matchCharType->getCharSet() == CharInfo::UTF8 ) |
| { |
| foundNextKey = matchCharType->computeNextKeyValue_UTF8(prefix); |
| byte_str_cpy(prefixZ, matchLen, prefix.data(), prefix.length(), |
| (char)zeroChar); |
| } |
| |
| if ( foundNextKey ) |
| { |
| ItemExpr *child1 = new (heap) SystemLiteral( |
| NAString(prefixZ, matchLen), |
| matchCharType->getCharSet(), |
| matchCharType->getCollation(), |
| matchCharType->getCoercibility() |
| ); |
| if ((specialMode) && (matchCharType->isCaseinsensitive())) |
| child1 = new (bindWA->wHeap()) Upper(child1); |
| |
| ItemExpr *endKey = |
| new (heap) BiRelat(ITM_LESS, matchExpr, |
| child1, |
| FALSE, // specialNulls flag |
| FALSE // partKeyPred flag |
| ); |
| |
| BiRelat *br = (BiRelat *) endKey; |
| br->setAddedForLikePred(TRUE); |
| |
| // set selectivity of the second range predicate equal to 1.0 |
| br->setLikeSelectivity(1.0); |
| if(boundExpr) |
| br->setOriginalLikeExprId(getValueId()); |
| |
| |
| if (boundExpr) |
| boundExpr = new (heap) BiLogic(ITM_AND, boundExpr, endKey); |
| else |
| boundExpr = endKey; |
| } // foundNextKey |
| |
| if (boundExpr) |
| boundExpr = new (heap) BiLogic(ITM_AND, beginKey, boundExpr); |
| else |
| boundExpr = beginKey; |
| |
| CMPASSERT(bindWA); |
| boundExpr = boundExpr->bindNode(bindWA); |
| |
| NADELETEBASIC(prefixZ, heap); |
| |
| beginEndKeysApplied_ = TRUE; |
| |
| } // pattern has a non-wild prefix |
| else |
| { |
| // pattern has a wild card prefix. At this point see if the user |
| // has used the old CQD HIST_DEFAULT_SEL_FOR_LIKE_WILDCARD. If he has |
| // then set a flag here to indicate that the optimizer should use the |
| // old CQD as the default selectivity for '%ab type cases. This is to |
| // maintain upward compatibility for the compiler |
| |
| if (bindWA) |
| { |
| // check the CQD list set by the user to see if HIST_DEFAULT_SEL_FOR_LIKE_WILDCARD |
| // has been set |
| |
| ControlDB *cdb = ActiveControlDB(); |
| |
| for (CollIndex i = 0; i < cdb->getCQDList().entries(); i++) |
| { |
| ControlQueryDefault *cqd = cdb->getCQDList()[i]; |
| if (cqd->getAttrEnum() == HIST_DEFAULT_SEL_FOR_LIKE_WILDCARD) |
| { |
| oldDefaultSelForLikeWildCardUsed_ = TRUE; |
| break; |
| } |
| } // done checking for HIST_DEFAULT_SEL_FOR_LIKE_WILDCARD |
| } |
| } |
| } // optimizeLike: pattern && escape are non-null constants |
| |
| return boundExpr; |
| } // Like::applyBeginEndKeys() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Case |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *Case::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // |
| // Check whether the CASE statement is in the following form: |
| // |
| // CASE val0 WHEN val1 THEN result1 WHEN val2 THEN result2 ... |
| // |
| // If so, convert it to the following form: |
| // |
| // CASE WHEN val0 = val1 THEN result1 WHEN val0 = val2 THEN result2 ... |
| // |
| ItemExpr *caseOperand = removeCaseOperand(); |
| if (caseOperand) { |
| caseOperand = caseOperand->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| ItemExpr *ifThenElse = child(0); |
| CMPASSERT(ifThenElse->getOperatorType() == ITM_IF_THEN_ELSE); |
| // |
| // The ELSE clause may be a NULL pointer if this is part of a CASE |
| // statement created by the generator. |
| // |
| do { |
| ifThenElse->child(0) = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, caseOperand, ifThenElse->child(0)); |
| ifThenElse = ifThenElse->child(2); |
| } while (ifThenElse AND ifThenElse->getOperatorType() == ITM_IF_THEN_ELSE); |
| } |
| |
| if (CmpCommon::getDefault(ALLOW_INCOMPATIBLE_OPERATIONS) == DF_ON) |
| { |
| // if operands are incompatible, insert cast node to convert them |
| // to a common datatype. |
| // Right now, only done for CHAR and NUMERICs. |
| ItemExpr *ifThenElse = child(0); |
| ItemExpr * thenClause = ifThenElse->child(1)->castToItemExpr(); |
| NABoolean done = FALSE; |
| NABoolean charFound = FALSE; |
| NABoolean numericFound = FALSE; |
| Lng32 dLen = 0; |
| Lng32 thenClauseNum = 1; |
| while ((ifThenElse) && (NOT done)) |
| { |
| thenClause = thenClause->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| ifThenElse->setChild(thenClauseNum, thenClause); |
| |
| if ((thenClause->getOperatorType() == ITM_CONSTANT) && |
| ((ConstValue *)thenClause)->isNull()) |
| { |
| // do nothing |
| } |
| else if (thenClause->getValueId().getType().getTypeQualifier() |
| == NA_CHARACTER_TYPE) |
| { |
| if (thenClause->getValueId().getType().getNominalSize() > dLen) |
| dLen = thenClause->getValueId().getType().getNominalSize(); |
| charFound = TRUE; |
| } |
| else if (thenClause->getValueId().getType().getTypeQualifier() |
| == NA_NUMERIC_TYPE) |
| { |
| NumericType &numeric = (NumericType&) |
| thenClause->getValueId().getType(); |
| Lng32 numericDLen = |
| numeric.getDisplayLength(numeric.getFSDatatype(), |
| numeric.getNominalSize(), |
| numeric.getPrecision(), |
| numeric.getScale(), |
| 0); |
| |
| if (numericDLen > dLen) |
| dLen = numericDLen; |
| numericFound = TRUE; |
| } |
| else |
| { |
| done = TRUE; |
| } |
| |
| if (thenClauseNum == 2) |
| ifThenElse = NULL; |
| else |
| { |
| if (ifThenElse->child(2)->getOperatorType() == ITM_IF_THEN_ELSE) |
| { |
| ifThenElse = ifThenElse->child(2); |
| thenClause = ifThenElse->child(1); |
| thenClauseNum = 1; |
| } |
| else |
| { |
| // this is the else clause |
| thenClause = ifThenElse->child(2); |
| thenClauseNum = 2; |
| } |
| } |
| } // while |
| |
| if ((NOT done) && (charFound) && (numericFound)) |
| { |
| ifThenElse = child(0); |
| thenClause = ifThenElse->child(1)->castToItemExpr(); |
| thenClauseNum = 1; |
| while (ifThenElse) |
| { |
| if (thenClause->getValueId().getType().getTypeQualifier() |
| == NA_NUMERIC_TYPE) |
| { |
| // cast to character |
| thenClause = |
| new (bindWA->wHeap()) |
| Cast(thenClause, |
| new (bindWA->wHeap()) |
| SQLChar(bindWA->wHeap(), dLen, |
| thenClause-> |
| getValueId().getType().supportsSQLnull())); |
| |
| thenClause = thenClause->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| ifThenElse->setChild(thenClauseNum, thenClause); |
| } |
| |
| if (thenClauseNum == 2) |
| ifThenElse = NULL; |
| else |
| { |
| if (ifThenElse->child(2)->getOperatorType() == ITM_IF_THEN_ELSE) |
| { |
| ifThenElse = ifThenElse->child(2); |
| thenClause = ifThenElse->child(1); |
| thenClauseNum = 1; |
| } |
| else |
| { |
| // this is the else clause |
| thenClause = ifThenElse->child(2); |
| thenClauseNum = 2; |
| } |
| } |
| } // while |
| } |
| } // allow incompatible operations |
| |
| // Case inherits from BuiltinFunction .. Function .. ItemExpr. |
| ItemExpr *boundExpr = BuiltinFunction::bindNode(bindWA); |
| |
| // Fix for "BR0094.txt", here and in ItemExpr::synthTypeAndValueId() -- |
| // If we are "CASE(select..from..) WHEN..ELSE..", |
| // make sure our result type is NULLABLE, as the subq may produce zero rows. |
| if (caseOperand && caseOperand->isASubquery()) { |
| ValueId vid = boundExpr->getValueId(); |
| const NAType* nullableType = |
| vid.getType().synthesizeNullableType(bindWA->wHeap()); |
| vid.changeType(nullableType); |
| CMPASSERT(boundExpr->getOperatorType() == ITM_CASE); |
| ((Case *)boundExpr)->caseOperandWasNullable() = TRUE; |
| } |
| |
| return boundExpr; |
| } // Case::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ColReference |
| // |
| // (but first, a BindWA method called by ColReference::bindNode below, |
| // and by Natural Join binding code in BindRelExpr.C) |
| // ----------------------------------------------------------------------- |
| |
| // **FOR DML:** |
| // |
| // Mark column as of interest to the Optimizer (particularly ScanOptimizer.C). |
| // Optimizer will expect: |
| // |
| // - Full stats (a ColStats header and as many HistInts as there are) |
| // from each referenced column's *single-column* histogram, and |
| // will want full stats from any *multi-column* histogram |
| // that contains a referenced column (for MDAM). |
| // |
| // - Of columns that are not referenced in this query, those that belong to |
| // an index (any one) must have short stats (a ColStats header, no intervals) |
| // from their *single-column* histogram. As there will always be at least |
| // one key column in a table (SYSKEY at the least), each table will end up |
| // having at least one ColStat, even if no refd cols ("SELECT c FROM t;"), |
| // which is another Optimizer assumption. |
| // |
| // - All other columns -- those not deemed referenced by the criteria below |
| // which also are not index keys -- the Optimizer has no use for any stats. |
| // |
| // The FetchHistograms function (in /ustat) uses this |
| // is-referenced flag, along with the columns' is-indexkey flags, |
| // and applies the rules above to deliver the minimum required stats. |
| // |
| // **FOR DDL -- actually only for CREATE VIEW:** |
| // |
| // On the first reference to a column, anywhere in the query per se |
| // (i.e. excluding constraints), add the column reference to the |
| // view-basetablecolumn list needed for one of the Ansi metadata tables. |
| // On any reference to a column within the top select-list of the view query, |
| // add to the viewcolumn-basetablecolumn list needed by CatMan to enforce |
| // the complicated REFERENCES privilege. |
| // |
| |
| /* |
| Update: 09/14/2009 |
| |
| Columns are marked �REFERENCED_FOR_MULTI_INTERVAL_HISTOGRAM� which require full histograms. |
| Full histogram means detailed histogram data which includes all the histogram interval data. |
| For columns to be marked under this category, they should be part of one of the following groups: |
| |
| - Key columns |
| - Where Clause |
| - Join predicate |
| |
| Columns are marked "REFERENCED_FOR_SINGLE_INTERVAL_HISTOGRAM" which require only single |
| interval histograms. For columns to be marked under this category, they should be part |
| of one of the following groups: |
| |
| - Union clause |
| - GroupBy Clause |
| - Having Clause |
| |
| Columns are marked "REFERENCED_ANYWHERE" which do not fall into either of the above categories. |
| */ |
| |
| void BindWA::markAsReferencedColumn(const ColumnDesc *cd, |
| NABoolean groupByRefForSingleIntHist) |
| { |
| if (cd->getViewFileName()) |
| { |
| setColumnRefsInStoi(cd->getViewFileName(),cd->getViewColPosition()); |
| } |
| |
| markAsReferencedColumn(cd->getValueId(), groupByRefForSingleIntHist); |
| } |
| |
| void BindWA::markAsReferencedColumn(const ValueId &vid, |
| NABoolean markGroupByForSingleInt) |
| { |
| BindContext *context = getCurrentScope()->context(); |
| |
| // Pay attention only to the query per se, not to extra bits brought in from |
| // the metadata. |
| if (context->inAnyConstraint()) return; |
| |
| // If ColReference refers to a union of colrefs, or to an aggregate function, |
| // or an instantiate-null, or whatever, no need to do anything -- |
| // the underlying colrefs will already have been marked by earlier binding. |
| // |
| NAColumn *nacol = vid.getNAColumn(TRUE/*okIfNotColumn*/); |
| |
| if (!nacol) |
| { |
| if (vid.getItemExpr()->getOperatorType() == ITM_VALUEIDUNION) |
| { |
| ValueIdUnion *valIdUnion = (ValueIdUnion *)vid.getItemExpr(); |
| |
| NABoolean groupByRefForSingleIntHist = FALSE; |
| |
| // If the GroupBy is due to UNION DISTINCT, do not mark grouping columns |
| // as referenced for histogram |
| if (valIdUnion->isTrueUnion() ) |
| groupByRefForSingleIntHist = TRUE; |
| |
| for (CollIndex i = 0; i < valIdUnion->getSources().entries(); i++) |
| { |
| markAsReferencedColumn(valIdUnion->getSources()[i], groupByRefForSingleIntHist); |
| } |
| } |
| else |
| { |
| ValueIdSet leafValues; |
| |
| ItemExpr *tempPred = vid.getItemExpr(); |
| if(!tempPred) |
| return; |
| |
| tempPred->findAll(ITM_BASECOLUMN, leafValues, TRUE, TRUE); |
| |
| for ( ValueId id = leafValues.init(); |
| leafValues.next( id ); |
| leafValues.advance( id ) ) |
| { |
| markAsReferencedColumn(id); |
| } |
| } |
| return; |
| } |
| |
| const NATable * naTable = nacol->getNATable(); |
| |
| if ( !naTable->isHiveTable() ) { |
| NAString fileName( naTable->getViewText() ? |
| (NAString)naTable->getViewFileName() : |
| naTable->getClusteringIndex()-> |
| getFileSetName().getQualifiedNameAsString(), |
| wHeap()); |
| |
| setColumnRefsInStoi(fileName.data(),nacol->getPosition()); |
| } |
| |
| if (inDDL()||context->inOrderBy()) return; |
| |
| if ((CURRSTMT_OPTDEFAULTS->incorporateSkewInCosting()) ) |
| { |
| if ( (nacol->isPartitioningKey() ) && |
| (nacol->isUserColumn() ) && |
| (nacol->getNATable()->getSpecialType() == ExtendedQualName::NORMAL_TABLE) ) |
| nacol->setReferencedForMultiIntHist(); |
| } |
| |
| // column references that are in a prdicate - WHERE, HAVING, |
| // column references that are in a GROUP BY. This does not include |
| // GroupBy created implicitly because of UNION DISTINCT |
| // common columns in a NATURAL join, |
| // column references that are in a join predicate |
| // are marked as referenced for histogram |
| |
| BindScope *scope = getCurrentScope(); |
| |
| while (scope) { |
| BindContext *context = scope->context(); |
| if (context->inWhereClause() || |
| context->inHavingClause() || |
| context->inJoinPred() || |
| // If the join has not been fully bound, the joinPred would not have |
| // been set and the predicates would still exist as joinPredTree |
| (context->inJoin() && context->inJoin()->getJoinPredTree()) || |
| context->inGroupByClause() || |
| context->inUnion()) |
| { |
| //if column participates in a join pred mark it, since this |
| //information is later used for reducing the number of histogram |
| //intervals |
| if (context->inJoinPred() || (context->inJoin() && context->inJoin()->getJoinPredTree())) |
| nacol->setHasJoinPred(); |
| |
| //if column participates in a range pred mark it, since this |
| //information is later used for reducing the number of histogram |
| //intervals |
| if (context->inRangePred()) |
| nacol->setHasRangePred(); |
| |
| // if it has already been marked referenced for histogram, we will not |
| // reduce its scope, hence return |
| // isReferencedForHistogram is set to TRUE if histogram is marked for either single interval |
| // or full interval. If the histogram is marked for single interval, but another context |
| // require it to be full histogram, we upgrade |
| if (nacol->isReferencedForMultiIntHist()) |
| return; |
| |
| // the column is referenced in a predicate, used to determine |
| // whether histograms should be fetched for this column reference |
| if(markGroupByForSingleInt || |
| context->inGroupByClause() || |
| context->inHavingClause() || |
| context->inUnion()) |
| nacol->setReferencedForSingleIntHist(); |
| else |
| nacol->setReferencedForMultiIntHist(); |
| |
| return; |
| } |
| scope = getPreviousScope(scope); |
| } |
| |
| // column is referenced anywhere in a query, used by unpack |
| nacol->setReferenced(); |
| |
| } // BindWA::markAsReferencedColumn() |
| |
| ItemExpr *ColReference::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| if (getColRefNameObj().isStar()) |
| return this; |
| |
| BindScope *bindScope; |
| ColumnNameMap *xcnmEntry = bindWA->findColumn(getColRefNameObj(), bindScope); |
| if (bindScope != bindWA->getCurrentScope() && |
| (bindWA->getCurrentScope()->context()->inOlapOrderBy() || |
| bindWA->getCurrentScope()->context()->inOlapPartitionBy())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4391); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // this return has been there for a long time. |
| // No idea what the code below it is doing since it will never be reached. |
| return getValueId().getItemExpr(); |
| |
| // In case the first time this Colreference was seen it was on |
| // left side of a set clause |
| NAColumn *nacol = getValueId().getNAColumn(TRUE/*okIfNotColumn*/); |
| const NATable * naTable = nacol->getNATable(); |
| NAString fileName( naTable->getViewText() ? |
| (NAString)naTable->getViewFileName() : |
| naTable->getClusteringIndex()-> |
| getFileSetName().getQualifiedNameAsString(), |
| bindWA->wHeap()); |
| |
| bindWA->setColumnRefsInStoi(fileName.data(),nacol->getPosition()); |
| |
| } |
| |
| // In mode_special_4, |
| // if name is of the form: IDENTIFIER.NEXTVAL or IDENTIFIER.CURRVAL, |
| // then change it to: seqnum(identifier, next) or seqnum(identifier, current) |
| // If name is: ROWNUM, change it to ROWNUM() function. |
| if (CmpCommon::getDefault(MODE_SPECIAL_4) == DF_ON) |
| { |
| ColRefName &colRefName = getColRefNameObj(); |
| CorrName &cn = 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 (((catName.isNull()) && |
| (schName.isNull()) && |
| ((colName == "NEXTVAL") || |
| (colName == "CURRVAL"))) || |
| (((catName.isNull()) && |
| (schName.isNull()) && |
| (objName.isNull())) && |
| (colName == "ROWNUM"))) |
| { |
| ItemExpr * itemExpr = NULL; |
| |
| if (colName == "ROWNUM") |
| { |
| itemExpr = new(bindWA->wHeap()) RowNumFunc(); |
| } |
| else |
| { |
| CorrName seqName(objName); |
| seqName.setSpecialType(ExtendedQualName::SG_TABLE); |
| |
| itemExpr = |
| new(bindWA->wHeap()) SequenceValue(seqName, |
| (colName == "NEXTVAL" ? FALSE : TRUE), |
| (colName == "NEXTVAL" ? TRUE : FALSE)); |
| } |
| |
| itemExpr = itemExpr->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| ValueId valId = itemExpr->getValueId(); |
| setValueId(valId); |
| |
| bindSelf(bindWA); |
| return itemExpr; |
| } |
| } |
| |
| // override schema |
| if ( ( bindWA->overrideSchemaEnabled() ) |
| // do not override if no column name |
| && ( ! getColRefNameObj().getColName().isNull() ) |
| // do not override if in a constraint (required override should have been done) |
| && ( ! bindWA->getCurrentScope()->context()->inCheckConstraint() )) { |
| bindWA->doOverrideSchema(getCorrNameObj()); |
| } |
| |
| // fix 0-061115-0532 (query cache didn't handle select with embedded |
| // update correctly). New/Old corr. names are recorded here in bindWA. |
| // |
| NABoolean hasSeenNewOrOldName = FALSE; |
| if ( getCorrNameObj().getQualifiedNameObj().getObjectName() == "NEW" ) { |
| bindWA->appendCorrNameToken('N'); |
| hasSeenNewOrOldName = TRUE; |
| } else { |
| if ( getCorrNameObj().getQualifiedNameObj().getObjectName() == "OLD" ) { |
| bindWA->appendCorrNameToken('O'); |
| hasSeenNewOrOldName = TRUE; |
| } |
| } |
| |
| // 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; |
| |
| BindScope *currScope = bindWA->getCurrentScope(); |
| if (getColRefNameObj().isStar()) { // "*" or "CORR.*" |
| |
| if ( hasSeenNewOrOldName == TRUE ) |
| bindWA->appendCorrNameToken('*'); |
| |
| RETDesc* resultTable = currScope->getRETDesc(); |
| CorrName corrName = getCorrNameObj(); |
| const ColumnDescList *colList; |
| |
| if (resultTable == NULL) { // for example values(sas_score("HHH",t.*)); |
| NAString nam("*", bindWA->wHeap()); |
| NAString fmtdList(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList(bindWA->wHeap()); |
| bindWA->getTablesInScope(xtnmList, &fmtdList); // Tables in all scopes |
| if(fmtdList.isNull()) |
| fmtdList = "NONE"; |
| |
| *CmpCommon::diags() << DgSqlCode(-4002) |
| << DgColumnName(nam) |
| << DgTableName(getCorrNameObj().getExposedNameAsAnsiString()) |
| << DgString0(fmtdList) |
| << DgString1(bindWA->getDefaultSchema().getSchemaNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (collateClause()) { |
| // 4034 The operation (T.* COLLATE coll-name) is not allowed. |
| NAString nam("*", bindWA->wHeap()); |
| if (corrName != "") |
| nam.prepend(corrName.getExposedNameAsAnsiString() + "."); |
| *CmpCommon::diags() << DgSqlCode(-4034) |
| << DgString0(nam) |
| << DgString1("COLLATE") |
| << DgString2(CharInfo::getCollationName(collateClause()->collation_)); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (corrName == "") |
| { |
| colList = resultTable->getColumnList(); |
| if(CmpCommon::getDefault(DISPLAY_DIVISION_BY_COLUMNS) == DF_ON) |
| { |
| ColumnDescList *divColList = new(bindWA->wHeap()) |
| ColumnDescList(bindWA->wHeap()); |
| |
| for (CollIndex i=0; i<colList->entries(); i++) |
| divColList->insert(colList->at(i)); |
| |
| const ColumnDescList *sysColList = resultTable->getSystemColumnList(); |
| for (CollIndex i=0; i<sysColList->entries(); i++) |
| { |
| ColumnDesc *colDesc = sysColList->at(i); |
| NAColumn *nacol = colDesc->getValueId().getNAColumn(TRUE); |
| if(nacol->isDivisioningColumn()) |
| divColList->insert(colDesc); |
| } |
| colList = divColList; |
| } |
| } |
| else { |
| colList = resultTable->getQualColumnList(corrName); |
| |
| if (!colList || !colList->entries()) { |
| // 4010 There are no user columns with the qualifier. |
| *CmpCommon::diags() << DgSqlCode(-4010) |
| << DgTableName(corrName.getExposedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // -- MVs |
| // Remove from the list columns that are system added. |
| CMPASSERT(resultTable!=NULL && colList!=NULL); |
| if (!getColRefNameObj().getStarWithSystemAddedCols()) |
| { |
| ColumnDescList *minimalColList = new(bindWA->wHeap()) |
| ColumnDescList(bindWA->wHeap()); |
| for (CollIndex i=0; i<colList->entries(); i++) |
| { |
| ColumnDesc *colDesc = colList->at(i); |
| NAColumn *col = colDesc->getValueId().getNAColumn(TRUE); |
| if (col==NULL || !col->isMvSystemAddedColumn()) |
| minimalColList->insert(colDesc); |
| } |
| colList = minimalColList; |
| } |
| |
| // |
| // If this is in a GROUP BY clause, mark each column as a grouping column. |
| // (Our extension to ANSI) |
| // |
| if (currScope->context()->inGroupByClause() && |
| (!udfSubqInAggGrby_Enabled || |
| !currScope->context()->inUDFunction())) { |
| for (CollIndex i = 0; i < colList->entries(); i++) |
| { |
| (*colList)[i]->setGroupedFlag(); |
| if ((*colList)[i]->getValueId().getItemExpr() |
| ->containsOpType(ITM_RANDOMNUM)) |
| { |
| // Temporary fix till random is supported in ORDER BY, GROUP BY |
| // For now do not allow random in ORDER BY clause, GROUP BY |
| // and DISTINCT. |
| *CmpCommon::diags() << DgSqlCode(-4313); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } |
| |
| ColumnDescList *collapseStar = NULL; |
| if (currScope->context()->inSelectList()) { |
| CMPASSERT( (corrName != "") || // -- Triggers |
| (bindWA->getPreviousScope(currScope) != NULL)); |
| if (corrName == "" && |
| bindWA->getPreviousScope(currScope)-> // simply contained |
| context()->inExistsPredicate()) { |
| // |
| // ANSI 7.9 SR 3a applies to "*" (simply contained only!), not "T.*": |
| // - "exists (select * from (select a,a from t) x)" is equivalent to |
| // "exists (select 1 from (select a,a from t) x)" (arbitrary literal). |
| // Note that since |
| // - "exists (select * from (...group by a,e having...) x)" is equiv to |
| // "exists (select 1 from (...group by a,e having...) x)", |
| // we do not require that columns b,c,d be grouping columns. |
| // |
| // So to bind this case ("*" simply contained in "exists"), |
| // we just expand the * into the first column in the list, |
| // not checking for duplicate/ambiguous column references. |
| // This collapsing to a degree-one (scalar) result makes |
| // BindRelExpr.C/bindRowValues() happy. |
| // Later on, Subquery::transformNode will remove the unnecessary |
| // selected column from the characteristic output. |
| // |
| collapseStar = new (bindWA->wHeap()) ColumnDescList(bindWA->wHeap()); |
| collapseStar->insert((*colList)[0]); |
| colList = collapseStar; |
| } else { // not bare "*" simply contained by an Exists pred |
| // |
| // ANSI 7.9 SR 3b + 4: replace "*" and "T.*" with a sequence of |
| // column references; referenced columns cannot be ambiguous (6.4 SR 4). |
| // Unnamed columns should actually have unique implementation-dependent |
| // names so must not be considered ambiguous. |
| // |
| for (CollIndex i = 0; i < colList->entries(); i++) { |
| ColumnDesc *columnDesc = (*colList)[i]; |
| if (NOT columnDesc->getColRefNameObj().isEmpty()) { // named column |
| ColumnNameMap *xcnmEntry = bindWA->findColumn(*columnDesc); |
| if (xcnmEntry->isDuplicate()) { |
| // 4011: Ambiguous star column reference. |
| *CmpCommon::diags() << DgSqlCode(-4011) |
| << DgColumnName(columnDesc->getColRefNameObj(). |
| getColRefAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } // named column |
| } // for-loop |
| // |
| // If the table is a grouped table, each column in the select list |
| // must be a grouping column. |
| // |
| if (resultTable->isGrouped()) { |
| for (CollIndex i = 0; i < colList->entries(); i++) { |
| ColumnDesc *columnDesc = (*colList)[i]; |
| if (NOT columnDesc->isGrouped()) { |
| // 4012: col must be grouping col; on this tbl star ref is illegal |
| *CmpCommon::diags() << DgSqlCode(-4012) |
| << DgColumnName(columnDesc->getColRefNameObj(). |
| getColRefAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } // isGrouped |
| } // not bare "*" simply contained by an Exists pred |
| } // inSelectList |
| // |
| setStarExpansion(colList); |
| // The collapseStar case is not a real column ref, so skip these two things: |
| if (!collapseStar) { |
| BindUtil_UpdateNameLocForStarExpansion(bindWA, *colList, |
| getColRefNameObj().getNamePosition(), |
| getParent()); |
| for (CollIndex i = 0; i < colList->entries(); i++) |
| bindWA->markAsReferencedColumn((*colList)[i]); |
| } |
| |
| bindSelf(bindWA); |
| return this; |
| } // ColReference::bindNode -- reference to "*" or "CORR.*" |
| |
| Lng32 sqlCode = 0; |
| BindScope *bindScope; |
| ColumnNameMap *xcnmEntry = bindWA->findColumn(getColRefNameObj(), bindScope); |
| |
| // When nametype is SHORTANSI in RETDesc::addColumnDesc() columns |
| // are fully qualified before being inserted into into xcnm_.insert(). |
| // For a statement like : |
| // Select sys_vol_subvol.table.column |
| // from sys_vol_subvol.table; |
| // or |
| // set schema 'sys_vol_subvol'; |
| // select table.column |
| // from table; |
| // xcnm_.insert() inserts \sys.vol.subvol.table.column. |
| // But in the above bindWA->findColumn() it is still looking for |
| // sys_vol_subvol.table.column, so in the below changes it looks for |
| // \sys.vol.subvol.table.column and finds it successfully. |
| // Fix for CR-10-000719-1267. |
| |
| |
| |
| |
| if ((xcnmEntry == NULL) && |
| (NOT getColRefNameObj().getCorrNameObj().getQualifiedNameObj().getObjectName().isNull()) && |
| (CmpCommon::context()->sqlSession()->volatileSchemaInUse())) |
| { |
| CorrName newCorrName = |
| CmpCommon::context()->sqlSession()->getVolatileCorrName |
| (getColRefNameObj().getCorrNameObj()); |
| |
| newCorrName.applyDefaults(bindWA, bindWA->getDefaultSchema()); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| ColRefName *cstColRefName = NULL; |
| cstColRefName = new(bindWA->wHeap()) |
| ColRefName(getColRefNameObj().getColName(), |
| newCorrName, bindWA->wHeap()); |
| |
| xcnmEntry = bindWA->findColumn(*cstColRefName, bindScope); |
| if (xcnmEntry) |
| getColRefNameObj().getCorrNameObj() = newCorrName; |
| } |
| |
| NAString colRefStr( xcnmEntry ? |
| xcnmEntry->getColRefNameObj().getColRefAsAnsiString() |
| : |
| getColRefNameObj().getColRefAsAnsiString(), |
| bindWA->wHeap()) ; |
| |
| // VO, Genesis solution 10-040107-2237: |
| // If the column WAS specified as delimited, but |
| // looks like a regular identifier, then add the quotes |
| if ( getColRefNameObj().isDelimited() && // colRef WAS "FOO" |
| colRefStr[(StringPos)0] != '"' // colRef IS FOO |
| ) |
| colRefStr = NAString('"') + colRefStr + NAString('"'); |
| |
| if ( xcnmEntry == NULL || |
| xcnmEntry->isQualifiedColumnAmbiguous() || |
| |
| ( xcnmEntry->isDuplicate() && |
| NOT getColRefNameObj().isQualified() ) |
| |
| ) |
| { |
| NAString fmtdList(bindWA->wHeap()); |
| LIST(TableNameMap*) xtnmList(bindWA->wHeap()); |
| |
| if (xcnmEntry == NULL) { |
| if (getCorrNameObj() == "") |
| sqlCode = -4001; // col not found. |
| else if (!bindWA->findCorrName(getCorrNameObj(), bindScope)) |
| sqlCode = -4002; // corr.col not found. table "corr" not exposed. |
| else |
| sqlCode = -4003; // corr.col not a col of specified table "corr". |
| bindWA->getTablesInScope(xtnmList, &fmtdList); // Tables in all scopes |
| //10-031030-0943 -begin |
| //If the fmtdList is empty then dont give a blank |
| //string fill it with "NONE" so that the error message is meaningful |
| if(fmtdList.isNull()) |
| fmtdList = "NONE"; |
| //10-031030-0943 -end |
| } |
| else { |
| sqlCode = -4004; // col is ambiguous. |
| bindScope->getTablesInScope(xtnmList, &fmtdList); // Tables in ambig scope |
| } |
| |
| // Genesis case 10-971208-5113 |
| // Tandem extension allows an ORDER BY column to be absent from the SELECT |
| // list except when aggregation or GROUP BY are involved. Of course, those |
| // columns must be in the tables exposed. |
| // |
| // If we are getting -4001,-4002 or -4003 when binding an ORDER BY column, |
| // a couple of scenarios are possible: |
| // |
| // 1. There is no aggregation or GROUP BY in the query, and the column is |
| // really not in tables exposed (in this case, xtnmList.entries() > 0). |
| // 2. There is aggregation or GROUP BY in the query, and the column is not |
| // found in the SELECT-list. |
| // |
| if ( (sqlCode == -4001 || sqlCode == -4002 || sqlCode == -4003) && |
| currScope->context()->inOrderBy() && |
| currScope->getRETDesc()->isGrouped() ) |
| { |
| sqlCode = (!xtnmList.entries()) ? -4120 : -4121; |
| *CmpCommon::diags() << DgSqlCode(sqlCode) |
| << DgColumnName(colRefStr) |
| << DgString0(fmtdList); |
| } |
| // genesis case 10-031030-7250:"NE:R2 MX1013 INSERT/SELECT not able to |
| // handle ORDER BY with all columns". We want to reject insert-selects |
| // of the form "insert into t(a) select a from s order by b" because |
| // they can cause lots of block splits. Ideally, we want the source to |
| // be in the same clustering key sequence as the target. |
| else if ((sqlCode == -4001 || sqlCode == -4002 || sqlCode == -4003) && |
| currScope->context()->inOrderBy() && |
| currScope->context()->inInsert()) { |
| *CmpCommon::diags() << DgSqlCode(-4135) << DgColumnName(colRefStr); |
| } |
| else |
| { |
| *CmpCommon::diags() << DgSqlCode(sqlCode) |
| << DgColumnName(colRefStr) |
| << DgTableName(getCorrNameObj().getExposedNameAsAnsiString()) |
| << DgString0(fmtdList) |
| << DgString1(bindWA->getDefaultSchema().getSchemaNameAsAnsiString()); |
| |
| // Genesis 10-970902-0878: |
| // user typed "foo" when they meant 'foo', |
| // or "FOO" when they meant 'FOO'. |
| // |
| if (getCorrNameObj() == "") // sqlCode could be -4001 or -4004 |
| if (colRefStr[(StringPos)0] == '"') |
| { |
| NAString literalStr(colRefStr, bindWA->wHeap()); |
| literalStr[(StringPos)0] = '\''; |
| literalStr[literalStr.length()-1] = '\''; // literalStr is 'foo' |
| *CmpCommon::diags() << DgSqlCode(4104) |
| << DgColumnName(colRefStr) |
| << DgString0(literalStr); |
| } |
| } // endif (sqlCode == -4001 ... && currScope->context()->inOrderBy()) |
| |
| } // xcnmEntry error (col not found, or duplicate/ambiguous) |
| |
| // Genesis 10-970929-8459: |
| // 'SELECT * FROM ta JOIN tb ON a=b,c;' |
| // is perfectly legal unambiguous Ansi, but ambiguous for SQL/MX |
| // because our Tandem-extension allowing Sql-row-value-constructor |
| // does not require parens around a value list; |
| // thus 'c' in example above is parsed as a column ref |
| // but user may well have (here, they did!) intended it as a table ref. |
| // |
| // Hence here we emit 4101 in addition to the preceding errmsg. |
| // So in this case we misinterpret legal Ansi syntax, emitting an error. |
| // That's unfortunate, but fixing SqlParser productions for search_cond |
| // is prohibitive at this time. Also note that |
| // 'SELECT * FROM ta JOIN tb ON a,a2=b,b2,c;' |
| // is not legal Ansi, but likewise ambiguous for our parser. |
| // 'SELECT * FROM ta JOIN tb ON (a,a2=b,b2),c;' -- legal-Ansi, unambig-Tdm |
| // 'SELECT * FROM ta JOIN tb ON a,a2=(b,b2),c;' -- legal-Ansi, unambig-Tdm |
| // 'SELECT * FROM c, ta JOIN tb ON a,a2=b,b2;' -- unambig-Tdm |
| // (The last has the side-effect of reordering the output *-list.) |
| // |
| // 4101: If $0~String0 is intended to be a further table reference |
| // in the FROM clause, the preceding join search condition must be |
| // enclosed in parentheses. |
| // (Or the rightmost row-value-ctor must be parenthesized. |
| // Or the table ref must come *first* in the list of FROM tbl-refs. |
| // But this is all too wordy for a single error message!) |
| // |
| // Note that if 'c' in the above query is unambiguously found in scope |
| // (column of 'ta' or 'tb'), then we need to first emit error 4042, then 4101. |
| // |
| if (getColRefNameObj().getCorrNameObj().getQualifiedNameObj().getCatalogName() |
| .isNull()) { // a 4-part name can only be a colref, so 4101 doesn't apply |
| |
| // Does this colref appear in the rightmost arg of a BiRelat or Function |
| // which appears rightmost in the join pred? |
| // Note that 'mPred->containsRightmost(this)' |
| // could NOT replace the mPredChNo lines below; e.g. in a case like |
| // 'SELECT * FROM ta JOIN tb ON a=b,c,d;' |
| // -- c is not rightmost in the list but it is cause for error 4101. |
| // |
| BindContext *context = bindWA->getCurrentScope()->context(); |
| ItemExpr *jPred = context->inJoinPred(); |
| ItemExpr *mPred = context->inMultaryPred(); |
| Lng32 mPredChNo = mPred? mPred->currChildNo() : 0; |
| if (jPred->containsRightmost(mPred) && // BiR/Func is rtmost in JoinPrd |
| mPredChNo && // RHS of BiRelat/Function |
| (mPredChNo >= mPred->getArity()-1 || // absolute rightmost item |
| !mPred->child(mPredChNo+1))) { // effective rightmost item |
| |
| // Does this colref appear in a list, at a position greater than |
| // the degree of the LHS comparand list? |
| // |
| ItemExpr *iList = context->inItemList(); |
| Lng32 iListChNo = iList? iList->currChildNo() : 0; |
| Lng32 mPredPrevDegree = mPred->child(mPredChNo-1)->currChildNo(); |
| if (iListChNo && iListChNo >= mPredPrevDegree) { // degree, not arity! |
| |
| if (!sqlCode) { |
| // The operands of a comparison predicate must be of equal degree. |
| // Error emitted here because we'll be setting errStatus and our |
| // caller won't be calling SynthType.C where this usually appears. |
| // |
| sqlCode = -4042; |
| *CmpCommon::diags() << DgSqlCode(sqlCode); |
| } |
| *CmpCommon::diags() << DgSqlCode(-4101) << DgString0(colRefStr); |
| |
| // Rather than emit these errors, we could take this "col" ref |
| // and the remainder of the iList and rewrite them as tbl refs (Scans), |
| // attaching them to the parent query tree in the proper place. |
| // Then we could achieve full Ansi syntax conformance. |
| } |
| } |
| } |
| |
| if (sqlCode) { |
| #ifndef NDEBUG // ##tmp |
| // The following debug code is often useful when debugging |
| // internal queries when the metadata changes. Just set the |
| // environment variable in a debug build to see the output. |
| if (getenv("COLREFERENCE_DEBUG")) { |
| BindContext *ctxt = bindWA->getCurrentScope()->context(); |
| char ii = ctxt->inItemList() ? 'i' : ' '; |
| char jj = ctxt->inJoinPred() ? 'j' : ' '; |
| char mm = ctxt->inMultaryPred() ? 'm' : ' '; |
| Int32 iia = ii == ' ' ? -99 : ctxt->inItemList()->getArity(); |
| Int32 iic = ii == ' ' ? -99 : ctxt->inItemList()->currChildNo(); |
| Int32 jja = jj == ' ' ? -99 : ctxt->inJoinPred()->getArity(); |
| Int32 jjc = jj == ' ' ? -99 : ctxt->inJoinPred()->currChildNo(); |
| Int32 mma = mm == ' ' ? -99 : ctxt->inMultaryPred()->getArity(); |
| Int32 mmc = mm == ' ' ? -99 : ctxt->inMultaryPred()->currChildNo(); |
| cout << getColRefNameObj().getColRefAsAnsiString() |
| << " (" << ii << " " << iia << " " << iic << ") " |
| << " (" << jj << " " << jja << " " << jjc << ") " |
| << " (" << mm << " " << mma << " " << mmc << ") " |
| << endl; |
| } |
| #endif // NDEBUG |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (NULL == xcnmEntry) |
| { |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Continue with no-error, non-star column reference. |
| ValueId valId = xcnmEntry->getValueId(); |
| setValueId(valId); // not bound yet, but this makes more informative errmsg |
| // if ColReference::getText() or unparse() is used |
| |
| const NAType *xcnmType = &valId.getType(); |
| const NAType *thisType = synthTypeWithCollateClause(bindWA, xcnmType); |
| |
| if (thisType != xcnmType) { |
| if (!thisType) return this; |
| |
| // We have a new type because an explicit COLLATE clause was specified |
| // in the query (e.g., SELECT charColumn COLLATE SJIS FROM ...) |
| // We must now CAST(BaseColumn AS xxx COLLATE zzz). |
| // Yes, we must do this even if BaseColumn's collation is IMPLICITly "zzz". |
| // Yes, we must CAST, not changeType, as that would change the BaseColumn |
| // from IMPLICIT to EXPLICIT for *all* ColRef's! |
| // |
| // Compare propagateCoAndCoToChildren() in SynthType.cpp. |
| // |
| ItemExpr *itemExpr = valId.getItemExpr(); |
| itemExpr = new (bindWA->wHeap()) Cast(itemExpr, thisType); |
| itemExpr = itemExpr->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| valId = itemExpr->getValueId(); |
| setValueId(valId); |
| } |
| |
| const NAType &naType = valId.getType(); |
| if (!naType.isSupportedType() && !bindScope->context()->inSelectList()) { |
| *CmpCommon::diags() << DgSqlCode(-1010); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // If the column reference is in a GROUP BY, mark it as a grouping column. |
| // |
| if ((currScope->context()->inGroupByClause()) AND |
| (bindScope == currScope) AND |
| (!udfSubqInAggGrby_Enabled || |
| (!currScope->context()->inUDFunction()))) |
| xcnmEntry->getColumnDesc()->setGroupedFlag(); |
| // |
| // If a local column reference is in a HAVING clause or in the select list of |
| // a grouped table, or an outer reference is in a subquery that is in a |
| // HAVING clause or in the select list of a grouped table, the column |
| // reference must be a grouping column or be specified within an aggregate. |
| // |
| if (bindScope->context()->inHavingClause() OR ( |
| bindScope->context()->inSelectList() AND |
| bindScope->getRETDesc()->isGrouped() AND |
| (NOT bindScope->context()->inGroupByOrdinal()) |
| )) |
| if (NOT xcnmEntry->getColumnDesc()->isGrouped() AND |
| //NOT (currScope->context()->inAggregate() || currScope->context()->inUDFunction())) { |
| NOT (currScope->context()->inAggregate() )) { |
| // 4005: col must be grouping col or specified within an aggregate |
| *CmpCommon::diags() << DgSqlCode(-4005) |
| << DgColumnName(getColRefNameObj().getColRefAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // If the bindScope's table later on turns into a grouped table |
| // (no groupby columns exist, but if an aggregate on a column of that table, |
| // as an outer ref, will turn the table into a grouped table of one group), |
| // then this nonaggregated column reference will become illegal, by |
| // ANSI 7.9 SR 7. Mark this here and check later in RelRoot::bindNode(). |
| // Also added check for the case where we have a subquery inside an |
| // aggregate. |
| if (bindScope->context()->inSelectList() AND |
| NOT bindScope->getRETDesc()->isGrouped() AND |
| NOT (currScope->context()->inAggregate() OR |
| (udfSubqInAggGrby_Enabled AND |
| (bindScope->context()->inAggregate() AND |
| bindScope->context()->inSubquery()))) AND |
| NOT bindScope->context()->unaggColRefInSelectList()) |
| bindScope->context()->unaggColRefInSelectList() = valId.getItemExpr(); |
| // |
| // ANSI 6.5 SR 4 states that, |
| // "If an outer reference is in an aggregate, it must be the only column |
| // reference in the aggregate." |
| // As an extension (the second if-test here), we say that all column refs |
| // in an aggregate must come from the same scope. |
| // |
| if (currScope->context()->outerColRefInAgg() || |
| ((bindScope != currScope) && currScope->context()->colRefInAgg())) |
| if (currScope->context()->aggScope() != bindScope) { |
| // 4006: within aggregate all col refs must be from same scope |
| *CmpCommon::diags() << DgSqlCode(-4006); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (currScope->context()->inAggregate()) { |
| currScope->context()->colRefInAgg() = TRUE; |
| currScope->context()->aggScope() = bindScope; // outer OR local/curr |
| } |
| // If the column reference is an outer reference, add it to the outer |
| // references list unless the outer reference is in an aggregate. If it's |
| // in an aggregate, the aggregate will be added to the outer references list |
| // when the aggregate node has been bound. |
| // |
| if (bindScope != currScope) { |
| if (currScope->context()->inAggregate()) |
| currScope->context()->outerColRefInAgg() = TRUE; |
| else |
| currScope->addOuterRef(valId); |
| } |
| |
| if (bindScope != currScope) |
| { |
| //Paramaters and outer references are not supported with rank function. |
| if (currScope->context()->inTDFunction()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4369); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| //Paramaters and outer references in the PARTITION BY or ORDER BY clause of a window function are not supported. |
| if (currScope->context()->inOlapOrderBy() || |
| currScope->context()->inOlapPartitionBy()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4391); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| //4391 |
| |
| BindUtil_UpdateNameLocForColRef(bindWA, getColRefNameObj(), xcnmEntry, |
| getParent()); |
| if (!currScope->context()->inComputedColumnExpr()) |
| bindWA->markAsReferencedColumn(xcnmEntry->getColumnDesc()); |
| |
| bindSelf(bindWA); |
| return valId.getItemExpr(); |
| } // ColReference::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ConstValue |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *ConstValue::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| const NAType *type = synthTypeWithCollateClause(bindWA); |
| if (!type) return this; |
| |
| // Fabricate a name for the constant such that its type prefixes the value. |
| NAString typeName(type->getTypeSQLname(), bindWA->wHeap()); |
| size_t len = typeName.length(); |
| |
| // If numeric constant, allocate space to hold the scale identifier. |
| // Scale is implicit for exact numeric value and is not stored |
| // with the constant value. So it is needed to differentiate between |
| // two constants which 'look' the same except for their scale. |
| // For example, 2 and .2 are both smallint, with length of 1, |
| // still are different constants. |
| char scale_val[4]; // max 2 digits of scale + 1 for null |
| char *scale_buf = NULL; |
| size_t scale_len = 0; |
| if (type->getTypeQualifier() == NA_NUMERIC_TYPE) { |
| scale_buf = scale_val; |
| str_itoa(((NumericType *)type)->getScale(), scale_buf); |
| scale_len = strlen(scale_buf); |
| } |
| |
| size_t value_len = getStorageSize(); |
| |
| char *buf = new char[len + scale_len + 2*value_len + 1]; |
| memset(buf,0, len + scale_len + 2*value_len +1); |
| memcpy(buf,typeName.data(),len); |
| |
| if (scale_buf) { |
| memcpy(&buf[len], scale_buf, scale_len); |
| len += scale_len; |
| } |
| |
| // Now encode the actual value into the fabricated name such that no |
| // null bytes appear (because the name will be used as a hash key |
| // and RogueWave will use C string comparison) -- |
| // so we precede every value byte with a tag byte. |
| |
| char *bufp = &buf[len]; |
| char *valp = (char *)value_; |
| len += 2*value_len; |
| while (value_len--) |
| { |
| if (*valp) |
| { |
| *bufp++ = 'n'; // tag byte |
| *bufp++ = *valp++; |
| } |
| else |
| { |
| *bufp++ = 'z'; // tag byte |
| *bufp++ = 'z'; // embedded null does not appear! |
| valp++; |
| } |
| } |
| |
| buf[len++] = '\0'; |
| NAString fabricatedName(buf,len,bindWA->wHeap()); |
| delete [] buf; |
| ItemExpr * result = ItemExpr::bindUserInput(bindWA,type,fabricatedName); |
| ConstValue* cv = dynamic_cast<ConstValue*>(result); |
| CURRENTQCACHE->getHQC() |
| ->collectBinderRetConstVal4HQC(this, cv); |
| |
| return result; |
| } // ConstValue::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class DefaultSpecification |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *DefaultSpecification::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // If immediately contained in an Insert, and binding the source VALUES list |
| BindScope *scope = bindWA->getCurrentScope(); |
| BindContext *context = scope->context(); |
| if (context->inInsert() && |
| context->updateOrInsertScope() == scope && |
| context->counterForRowValues()) { |
| |
| Insert *insert = (Insert *)context->updateOrInsertNode(); |
| if (insert && |
| insert->getOperatorType() == REL_UNARY_INSERT && |
| insert->canBindDefaultSpecification()) { |
| |
| const char *defaultValueStr = |
| insert->getColDefaultValue(bindWA, *context->counterForRowValues()); |
| |
| // If column has NO DEFAULT, then getColDefaultValue() emitted error -4107 |
| // and set bindWA errstatus. |
| if (!defaultValueStr) return NULL; |
| |
| // The DEFAULT specification replaces itself with a ConstValue |
| // whose value is the default value for the column corresponding |
| // to this position in the source tuple and target column list. E.g., |
| // INSERT INTO T(C,B,A) VALUES(1,2,DEFAULT) |
| // the DEFAULT == position 3 == *context->counterForRowValues() |
| // and getColDefaultValue(3) gets the default literal for column A. |
| // |
| // After this, the DefaultSpecification node is not seen again. |
| // |
| |
| // Set the special parser flag to allow IDENTITY as a function. |
| |
| ULng32 savedParserFlags = Get_SqlParser_Flags (0xFFFFFFFF); |
| Set_SqlParser_Flags(ALLOW_VOLATILE_SCHEMA_IN_TABLE_NAME); |
| |
| Parser parser(bindWA->currentCmpContext()); |
| |
| ItemExpr *defaultValueExpr = |
| parser.getItemExprTree(defaultValueStr); |
| |
| Assign_SqlParser_Flags (savedParserFlags); |
| |
| // It is possible to have a SQL/MP default value that SQL/MX |
| // cannot parser. In these case SQL/MX is not compatible with |
| // SQL/MP and an error is reported. |
| // |
| if(!defaultValueExpr) |
| { |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| ItemExpr *boundExpr = NULL; |
| |
| boundExpr = defaultValueExpr->bindNode(bindWA); |
| |
| if (bindWA->errStatus()) return NULL; |
| |
| if (defaultValueExpr->getOperatorType() == ITM_SEQUENCE_VALUE) |
| { |
| insert->setSystemGeneratesIdentityValue(TRUE); |
| } |
| |
| // Remember the fact that the literal used to be a DEFAULT spec |
| if (boundExpr->getOperatorType() == ITM_CONSTANT) |
| ((ConstValue *)boundExpr)->setWasDefaultSpec(); |
| |
| boundExpr->setWasDefaultClause(TRUE); |
| |
| setValueId(boundExpr->getValueId()); |
| return getValueId().getItemExpr(); |
| } |
| } |
| |
| // 4096 A DEFAULT specification is allowed only when simply contained |
| // in the VALUES list of an INSERT. |
| *CmpCommon::diags() << DgSqlCode(-4096); |
| bindWA->setErrStatus(); |
| return NULL; |
| |
| } // DefaultSpecification::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class SleepFunction |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *SleepFunction::bindNode(BindWA *bindWA) |
| { |
| |
| if (bindWA->inDDL() && (bindWA->inCheckConstraintDefinition())) |
| { |
| StmtDDLAddConstraintCheck *pCkC = bindWA->getUsageParseNodePtr() |
| ->castToElemDDLNode() |
| ->castToStmtDDLAddConstraintCheck(); |
| *CmpCommon::diags() << DgSqlCode(-4131); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| const NAType *type = synthTypeWithCollateClause(bindWA); |
| if (!type) return this; |
| |
| ItemExpr * ie = ItemExpr::bindUserInput(bindWA,type,getText()); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // add this value id to BindWA's input function list. |
| bindWA->inputFunction().insert(getValueId()); |
| |
| return ie; |
| } // SleepFunction::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class UnixTimestamp |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *UnixTimestamp::bindNode(BindWA *bindWA) |
| { |
| |
| if (bindWA->inDDL() && (bindWA->inCheckConstraintDefinition())) |
| { |
| StmtDDLAddConstraintCheck *pCkC = bindWA->getUsageParseNodePtr() |
| ->castToElemDDLNode() |
| ->castToStmtDDLAddConstraintCheck(); |
| *CmpCommon::diags() << DgSqlCode(-4131); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| const NAType *type = synthTypeWithCollateClause(bindWA); |
| if (!type) return this; |
| |
| ItemExpr * ie = ItemExpr::bindUserInput(bindWA,type,getText()); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // add this value id to BindWA's input function list. |
| bindWA->inputFunction().insert(getValueId()); |
| |
| return ie; |
| } // UnixTimestamp::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class CurrentTimestamp |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *CurrentTimestamp::bindNode(BindWA *bindWA) |
| { |
| |
| if (bindWA->inDDL() && (bindWA->inCheckConstraintDefinition())) |
| { |
| StmtDDLAddConstraintCheck *pCkC = bindWA->getUsageParseNodePtr() |
| ->castToElemDDLNode() |
| ->castToStmtDDLAddConstraintCheck(); |
| *CmpCommon::diags() << DgSqlCode(-4131); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| const NAType *type = synthTypeWithCollateClause(bindWA); |
| if (!type) return this; |
| // |
| // ANSI requires that multiple references to CURRENT_DATE, CURRENT_TIME, |
| // or CURRENT_TIMESTAMP in the same SQL statement be effectively evaluated |
| // simultaneously, so all CurrentTimestamp functions are treated as input |
| // values and are given the same value id. |
| // |
| ItemExpr * ie = ItemExpr::bindUserInput(bindWA,type,getText()); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // add this value id to BindWA's input function list. |
| bindWA->inputFunction().insert(getValueId()); |
| |
| return ie; |
| } // CurrentTimestamp::bindNode() |
| |
| |
| //++Triggers |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class UniqueExecuteId |
| // ----------------------------------------------------------------------- |
| ItemExpr *UniqueExecuteId::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| const NAType *type = synthesizeType(); |
| if (!type) { |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // |
| // functions of this type are treated as input |
| // values and are given the same value id. |
| // |
| ItemExpr * ie = ItemExpr::bindUserInput(bindWA,type,getText()); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // add this value id to BindWA's input function list. |
| bindWA->inputFunction().insert(getValueId()); |
| |
| return ie; |
| } // UniqueExecuteId::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class GetTriggersStatus |
| // ----------------------------------------------------------------------- |
| ItemExpr *GetTriggersStatus::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| const NAType *type = synthesizeType(); |
| if (!type) { |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // |
| // functions of this type are treated as input |
| // values and are given the same value id. |
| // |
| ItemExpr * ie = ItemExpr::bindUserInput(bindWA,type,getText()); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // add this value id to BindWA's input function list. |
| bindWA->inputFunction().insert(getValueId()); |
| |
| return ie; |
| } // GetTriggersStatus::bindNode() |
| |
| //--Triggers |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class CurrentTimestampRunning |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *CurrentTimestampRunning::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| const NAType *type = synthTypeWithCollateClause(bindWA); |
| if (!type) return this; |
| |
| ItemExpr *boundExpr = ItemExpr::bindNode(bindWA); |
| return boundExpr; |
| } // CurrentTimestampRunning::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Parameter |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *Parameter::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| OperatorTypeEnum opTyp = getOperatorType(); |
| // All user inputs are treated as outer references in the current scope. |
| bindWA->getCurrentScope()->addOuterRef(getValueId()); |
| return getValueId().getItemExpr(); |
| } |
| if (bindWA->getCurrentScope()->context()->inTDFunction()) |
| { |
| //Paramaters and outer references are not supported with rank function. |
| *CmpCommon::diags() << DgSqlCode(-4369); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (bindWA->getCurrentScope()->context()->inOlapOrderBy() || |
| bindWA->getCurrentScope()->context()->inOlapPartitionBy()) |
| { |
| //Paramaters and outer references in the PARTITION BY or ORDER BY clause of a window function are not supported. |
| *CmpCommon::diags() << DgSqlCode(-4391); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if ( bindWA->bindingCall () && ITM_DYN_PARAM == getOperatorType ()) |
| { |
| // Are we in a trigger? |
| // This needs to be ahead of trying access host areas |
| if (bindWA->isInTrigger()) { |
| *CmpCommon::diags() << DgSqlCode(-11046); |
| bindWA->setErrStatus (); |
| return this; |
| } |
| |
| // We do not allow Rowsets in CALL yet. |
| if ( bindWA->getHostArraysArea()->hasDynamicRowsets() ) |
| { |
| *CmpCommon::diags() << |
| DgSqlCode(-UDR_BINDER_NO_ROWSET_IN_CALL); |
| bindWA->setErrStatus (); |
| return this; |
| } |
| |
| setPMOrdPosAndIndex(bindWA->getCurrParamMode(), |
| (Int32) bindWA->getCurrOrdinalPosition(), |
| getHVorDPIndex()); |
| bindWA->addHVorDPToSPDups(this); |
| |
| if (!bindWA->getDupWarning() && bindWA->checkMultiOutSPParams(this)) |
| { |
| *CmpCommon::diags() |
| << DgSqlCode(UDR_BINDER_MULTI_HOSTVAR_OR_DP_IN_PARAMS) |
| << DgString0(((DynamicParam *) this)->getName()) |
| << DgTableName(bindWA->getCurrSPName().getQualifiedNameAsString()); |
| |
| bindWA->setDupWarning (TRUE); |
| } |
| |
| // CLI support for CALL stmt OUT params |
| if ( COM_INPUT_COLUMN == bindWA->getCurrParamMode () || |
| COM_INOUT_COLUMN == bindWA->getCurrParamMode () ) |
| { |
| bindWA->getSpInParams().insert ( this ); |
| } // if INPUT or INOUT |
| if ( COM_OUTPUT_COLUMN == bindWA->getCurrParamMode () || |
| COM_INOUT_COLUMN == bindWA->getCurrParamMode () ) |
| { |
| // OUT param suppport, CLI will use this |
| // During RelRoot::bindNode this is copied into the RelRoot's |
| // private area |
| bindWA->getSpOutParams().insert( this ); |
| } // if OUTPUT or INOUT |
| } // binding a CALL statement |
| |
| const NAType *type = synthTypeWithCollateClause(bindWA); |
| if (!type) return this; |
| |
| if(getOperatorType() == ITM_ROUTINE_PARAM) |
| { |
| // Don't want to send the ROUTINE_PARAMs through bindUserInput |
| // as they are used as fake inputs, but real outputs. Thus they |
| // cannot be a UserInput |
| setValueId(createValueDesc(bindWA, this, type)); |
| bindSelf(bindWA); |
| if (bindWA->errStatus()) return this; |
| return getValueId().getItemExpr(); |
| } else |
| return ItemExpr::bindUserInput(bindWA,type,getText()); |
| } // Parameter::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class HostVar |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *HostVar::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| const NAType *type = synthTypeWithCollateClause(bindWA); |
| if (!type) return this; |
| |
| if ( bindWA->bindingCall ()) |
| { |
| // Are we in a trigger |
| if (bindWA->isInTrigger()) { |
| *CmpCommon::diags() << DgSqlCode(-11046); |
| bindWA->setErrStatus (); |
| return this; |
| } |
| |
| // No rowsets as CALL parameters yet |
| if ( NA_ROWSET_TYPE == getType()->getTypeQualifier() ) |
| { |
| *CmpCommon::diags() << |
| DgSqlCode(-UDR_BINDER_NO_ROWSET_IN_CALL); |
| bindWA->setErrStatus (); |
| return this; |
| } |
| |
| setPMOrdPosAndIndex(bindWA->getCurrParamMode(), |
| (Int32) bindWA->getCurrOrdinalPosition(), |
| hvIndex_); |
| bindWA->addHVorDPToSPDups(this); |
| |
| if (!bindWA->getDupWarning() && bindWA->checkMultiOutSPParams(this)) |
| { |
| *CmpCommon::diags() |
| << DgSqlCode(UDR_BINDER_MULTI_HOSTVAR_OR_DP_IN_PARAMS) |
| << DgString0(getName()) |
| << DgTableName(bindWA->getCurrSPName().getQualifiedNameAsString()); |
| |
| bindWA->setDupWarning(TRUE); |
| } |
| |
| // CLI support for CALL stmt OUT params |
| if ( COM_INPUT_COLUMN == bindWA->getCurrParamMode () || |
| COM_INOUT_COLUMN == bindWA->getCurrParamMode () ) |
| { |
| bindWA->getSpInParams().insert ( this ); |
| } // if INPUT or INOUT |
| |
| if ( COM_OUTPUT_COLUMN == bindWA->getCurrParamMode () || |
| COM_INOUT_COLUMN == bindWA->getCurrParamMode () ) |
| { |
| // OUT param suppport, CLI will use this |
| // During RelRoot::bindNode this is copied into the RelRoot's |
| // private area |
| bindWA->getSpOutParams().insert( this ); |
| } // if OUTPUT or INOUT |
| } // if bindingCall |
| else |
| { |
| // We can be here during RelRoot::bindNode also. At this point |
| // bindingCall () will be FALSE, but we still need to set |
| // the variable's PMOrdPos etc. |
| // Also, if we come here during a post-bind phase |
| // and the bindWA's HVorDP list is empty, 'h' below will be NULL |
| HostVar *h = (HostVar *) bindWA->getHVorDPFromSPDups (this); |
| if (h) |
| { |
| setPMOrdPosAndIndex (h->getParamMode (), |
| h->getOrdinalPosition (), |
| h->getHVorDPIndex ()); |
| } |
| } |
| |
| ItemExpr * ie = ItemExpr::bindUserInput(bindWA, type, getText()); |
| |
| return ie; |
| } // HostVar::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class RenameCol |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *RenameCol::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| // |
| // Bind the child nodes. |
| // |
| bindSelf(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // We don't allow rename of MVF or Subq by default |
| // If the CQD is turned on, we will allow a query like this |
| // |
| // select mvf() as x from t1; |
| // |
| // and we will silently pick the first output from the mvf() to be |
| // associated with x. Any other outputs from the mvf() will be ingored. |
| // |
| // similarly for a subquery: |
| // |
| // select (select a,b from t1) as x, b from t2; |
| // |
| // will associate the a from the subquery's select list with x, and b will |
| // quitely be ignored. |
| |
| |
| if ( CmpCommon::getDefault(ALLOW_RENAME_OF_MVF_OR_SUBQ) == DF_OFF ) |
| { |
| |
| // Since UDFs can return more than one output, we have to check for that |
| // here, and if it does, disallow it. |
| if (child(0)->getOperatorType() == ITM_USER_DEF_FUNCTION) |
| { |
| UDFunction *udf = (UDFunction *) child(0)->castToItemExpr(); |
| if (udf->getRoutineDesc() && |
| udf->getRoutineDesc()->getOutputColumnList().entries() > 1) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4478); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| } |
| // Since we now allow Subqueries to return multiple columns, |
| // we have to check for that here, and if it does, disallow it. |
| else if (child(0)->getOperatorType() == ITM_ROW_SUBQUERY) |
| { |
| |
| Subquery *subq = (Subquery *) child(0)->castToItemExpr(); |
| if (subq->getSubquery()->getDegree() > 1) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4477); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } |
| |
| setValueId(child(0)->getValueId()); |
| return getValueId().getItemExpr(); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class PositionFunc |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *PositionFunc::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // this will bind/type-propagate all children. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // update both operands if case insensitive comparions |
| // are to be done. |
| const NAType &type1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| const NAType &type2 = |
| child(1)->castToItemExpr()->getValueId().getType(); |
| |
| if ((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (type2.getTypeQualifier() == NA_CHARACTER_TYPE)) |
| { |
| const CharType &cType1 = (CharType&)type1; |
| const CharType &cType2 = (CharType&)type2; |
| |
| NABoolean doCIcomp = |
| ((cType1.isCaseinsensitive()) && (cType2.isCaseinsensitive())); |
| |
| ItemExpr * newChild = NULL; |
| if ((doCIcomp) && |
| (NOT cType1.isUpshifted())) |
| { |
| newChild = new (bindWA->wHeap()) Upper(child(0)); |
| setChild(0, newChild); |
| } |
| |
| if ((doCIcomp) && |
| (NOT cType2.isUpshifted())) |
| { |
| newChild = new (bindWA->wHeap()) Upper(child(1)); |
| setChild(1, newChild); |
| } |
| } |
| |
| // if third(start position) and fourth(occurence) child operands are |
| // specified, then convert them to INT. |
| if (child(2)) |
| { |
| ValueId vid3 = child(2)->getValueId(); |
| SQLInt si(NULL); |
| |
| vid3.coerceType(si, NA_NUMERIC_TYPE); |
| |
| const NAType &type3 = vid3.getType(); |
| |
| if (type3.getTypeQualifier() != NA_NUMERIC_TYPE) { |
| // 4053 The third operand of a POSITION function must be numeric. |
| *CmpCommon::diags() << DgSqlCode(-4053) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (((NumericType&)type3).getScale() != 0) { |
| // 4047 The third operand of a POSITION function must have a scale of 0. |
| *CmpCommon::diags() << DgSqlCode(-4047) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (type3.getFSDatatype() != REC_BIN32_SIGNED) |
| { |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Cast(child(2), |
| new (bindWA->wHeap()) |
| SQLInt(bindWA->wHeap(), TRUE, type3.supportsSQLnull())); |
| newChild = newChild->bindNode(bindWA); |
| setChild(2, newChild); |
| } |
| } |
| |
| if (child(3)) |
| { |
| ValueId vid4 = child(3)->getValueId(); |
| SQLInt si(NULL); |
| |
| vid4.coerceType(si, NA_NUMERIC_TYPE); |
| |
| const NAType &type4 = vid4.getType(); |
| |
| if (type4.getTypeQualifier() != NA_NUMERIC_TYPE) { |
| // 4053 The third operand of a POSITION function must be numeric. |
| *CmpCommon::diags() << DgSqlCode(-4053) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (((NumericType&)type4).getScale() != 0) { |
| // 4047 The third operand of a POSITION function must have a scale of 0. |
| *CmpCommon::diags() << DgSqlCode(-4047) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (type4.getFSDatatype() != REC_BIN32_SIGNED) |
| { |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Cast(child(3), |
| new (bindWA->wHeap()) |
| SQLInt(bindWA->wHeap(), TRUE, type4.supportsSQLnull())); |
| newChild = newChild->bindNode(bindWA); |
| setChild(3, newChild); |
| } |
| } |
| |
| BuiltinFunction::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return getValueId().getItemExpr(); |
| } // PositionFunc::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Replace |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *Replace::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // this will bind/type-propagate all children. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // update both operands if case insensitive comparions |
| // are to be done. |
| const NAType &type1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| const NAType &type2 = |
| child(1)->castToItemExpr()->getValueId().getType(); |
| |
| if ((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (type2.getTypeQualifier() == NA_CHARACTER_TYPE)) |
| { |
| const CharType &cType1 = (CharType&)type1; |
| const CharType &cType2 = (CharType&)type2; |
| |
| NABoolean doCIcomp = |
| ((cType1.isCaseinsensitive()) && (cType2.isCaseinsensitive())); |
| |
| ItemExpr * newChild = NULL; |
| /* if ((doCIcomp) && |
| (NOT cType1.isUpshifted())) |
| { |
| newChild = new (bindWA->wHeap()) Upper(child(0)); |
| setChild(0, newChild); |
| } |
| */ |
| |
| if ((doCIcomp) && |
| (NOT cType2.isUpshifted())) |
| { |
| newChild = new (bindWA->wHeap()) Upper(child(1)); |
| setChild(1, newChild); |
| |
| } |
| } |
| |
| BuiltinFunction::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return getValueId().getItemExpr(); |
| } // Replace::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class CharLength |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *CharLength::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // this will bind/type-propagate all children. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| const NAType &type1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| |
| if (type1.getTypeQualifier() == NA_NUMERIC_TYPE) |
| { |
| ItemExpr * newChild = new (bindWA->wHeap()) |
| Trim((Int32)Trim::TRAILING, |
| new (PARSERHEAP()) SystemLiteral(" ", WIDE_(" ")), child(0)); |
| |
| setChild(0, newChild); |
| } |
| |
| BuiltinFunction::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return getValueId().getItemExpr(); |
| } // CharLength::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class OctetLength |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *OctetLength::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // this will bind/type-propagate all children. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| const NAType &type1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| |
| if (type1.getTypeQualifier() == NA_NUMERIC_TYPE) |
| { |
| ItemExpr * newChild = new (bindWA->wHeap()) |
| Trim((Int32)Trim::TRAILING, |
| new (PARSERHEAP()) SystemLiteral(" ", WIDE_(" ")), child(0)); |
| |
| setChild(0, newChild); |
| } |
| |
| BuiltinFunction::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return getValueId().getItemExpr(); |
| } // OctetLength::bindNode() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class SelIndex |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *SelIndex::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| // |
| // Bind the child nodes. |
| // |
| bindSelf(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| BindScope * currScope = bindWA->getCurrentScope(); |
| if ((currScope->context()->inGroupByClause()) || |
| (currScope->context()->inHavingClause())) |
| { |
| // the real value id pointing to the select list element |
| // will be set during phase2 of groupby ordinal transformation. |
| // See RelRoot::transformGroupByWithOrdinalPhase2(). |
| |
| // create a dummy type of type unknown. |
| NAType * type = new(bindWA->wHeap()) SQLUnknown(bindWA->wHeap()); |
| setValueId(createValueDesc(bindWA, this, type)); |
| |
| if ((bindWA->inViewDefinition()) && |
| (getExprInGrbyClause())) |
| { |
| // this will expand names used in the groupby clause |
| // so they could be used during view create processing. |
| getExprInGrbyClause()->bindNode(bindWA); |
| } |
| |
| return this; |
| } |
| |
| // |
| // Check that the select list index is within the allowable range. |
| // |
| const CollIndex i = getSelIndex(); |
| RETDesc *resultTable = bindWA->getCurrentScope()->getRETDesc(); |
| if (i < 1 || i > resultTable->getDegree()) |
| { |
| // 4007: select list index out of range. |
| *CmpCommon::diags() << DgSqlCode(-4007) << DgInt0(i) << DgInt1(resultTable->getDegree()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| setValueId(resultTable->getValueId(i - 1)); |
| return getValueId().getItemExpr(); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Subquery |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *Subquery::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| BindContext *context = bindWA->getCurrentScope()->context(); |
| |
| // 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; |
| |
| if (!udfSubqInAggGrby_Enabled) |
| { |
| |
| if (context->inAggregate()) { |
| // 4008: A subquery is not allowed inside an aggregate. |
| *CmpCommon::diags() << DgSqlCode(-4008); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // a subquery is not allowed to be referenced as a group by ordinal. |
| if (context->inGroupByOrdinal()) { |
| *CmpCommon::diags() << DgSqlCode(-4185); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| |
| // subquery is not allowed in the join predicate of Full Outer Join. |
| if (context->inJoinPred() && |
| (context->inJoin()->getOperatorType() == REL_FULL_JOIN)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4339); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (getArity() && checkForSQLnullChild(bindWA, this)) return this; |
| |
| // Bind the child nodes. |
| // |
| bindSelf(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // |
| // Bind the subquery tree |
| // |
| context = bindWA->getCurrentScope()->context(); |
| NABoolean orig = context->inSubquery(); |
| NABoolean origRow = context->inRowSubquery(); |
| context->inSubquery() = TRUE; |
| context->inRowSubquery() = (isARowSubquery()); |
| |
| tableExpr_ = getSubquery()->bindNode(bindWA); |
| |
| context->inSubquery() = orig; |
| context->inRowSubquery() = origRow; |
| if (bindWA->errStatus()) |
| return this; |
| |
| // QSTUFF |
| // we don't allow streams in subqueries. |
| if (tableExpr_->getGroupAttr()->isStream()){ |
| *CmpCommon::diags() << DgSqlCode(-4168); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // we don't allow destructive selects or embedded inserts in subqueries. |
| // The SeqGenSubquery updating the SG Table and returning the |
| // next value is an exception. |
| if (1 && |
| ((tableExpr_->getGroupAttr()->isEmbeddedUpdateOrDelete()) |
| || (tableExpr_->getGroupAttr()->isEmbeddedInsert()) |
| || (bindWA->isEmbeddedIUDStatement())) |
| ) |
| { |
| NAString type; |
| if (tableExpr_->getGroupAttr()->isEmbeddedUpdate()) |
| type = "UPDATE"; |
| else |
| { |
| if (tableExpr_->getGroupAttr()->isEmbeddedInsert()) |
| type = "INSERT"; |
| else |
| type = "DELETE"; |
| } |
| |
| *CmpCommon::diags() |
| << DgSqlCode(-4167) |
| << DgString0(type); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // QSTUFF |
| |
| // Create a ValueDesc for this ItemExpr. |
| // |
| const NAType *type = synthTypeWithCollateClause(bindWA); |
| if (!type) return this; |
| setValueId(createValueDesc(bindWA, this, type)); |
| return getValueId().getItemExpr(); |
| } // Subquery::bindNode() |
| |
| ItemExpr *QuantifiedComp::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| ItemExpr *boundExpr = Subquery::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // if left child is incompatible with right child, insert a node |
| // to convert left to right. |
| if ((CmpCommon::getDefault(ALLOW_INCOMPATIBLE_OPERATIONS) == DF_ON) && |
| (createdFromINlist())) |
| { |
| const NAType &type1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| const NAType &type2 = |
| getSubquery()->selectList()->castToItemExpr()->getValueId().getType(); |
| |
| if (type1.getTypeQualifier() != type2.getTypeQualifier()) |
| { |
| if ((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (type2.getTypeQualifier() == NA_NUMERIC_TYPE)) |
| { |
| // only supporting char lhs at this time. Add more later. |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Cast(child(0), |
| new (bindWA->wHeap()) |
| SQLDoublePrecision(bindWA->wHeap(), |
| child(0)->castToItemExpr()->getValueId(). |
| getType().supportsSQLnull())); |
| newChild = newChild->bindNode(bindWA); |
| if(bindWA->errStatus()) |
| return NULL; |
| |
| if(newChild) |
| setChild(0, newChild); |
| } |
| else |
| { |
| emitDyadicTypeSQLnameMsg(-4041, type1, type2); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| } |
| |
| return boundExpr; |
| } |
| |
| ItemExpr *Substring::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // this will bind/type-propagate all children. |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (CmpCommon::getDefault(MODE_SPECIAL_1) == DF_ON) |
| { |
| // allow substring on exact, binary numeric operand with scale of zero. |
| const NAType &type1 = |
| child(0)->castToItemExpr()->getValueId().getType(); |
| |
| if (type1.getTypeQualifier() == NA_NUMERIC_TYPE) |
| { |
| NumericType &nType = (NumericType&)type1; |
| if ((nType.isExact()) && |
| (nType.getScale() == 0) && |
| (nType.isSimpleType())) |
| { |
| Parser parser(bindWA->currentCmpContext()); |
| char buf[1000]; |
| |
| Lng32 dlen = |
| nType.getDisplayLength(nType.getFSDatatype(), |
| nType.getNominalSize(), |
| nType.getPrecision(), |
| nType.getScale(), |
| 0); |
| ItemExpr * parseTree ; |
| // right justify the string representation of numeric operand |
| // and then do substring. |
| if (getNumChildren() == 2) |
| { |
| sprintf(buf, "SUBSTRING(SPACE(%d - CHAR_LENGTH(CAST(@A1 AS VARCHAR(%d)))) || CAST(@A1 AS VARCHAR(%d)), @A2)", |
| dlen, dlen, dlen); |
| parseTree = parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 2, child(0), child(1)); |
| } |
| else |
| { |
| CMPASSERT(getNumChildren() == 3); |
| sprintf(buf, "SUBSTRING(SPACE(%d - CHAR_LENGTH(CAST(@A1 AS VARCHAR(%d)))) || CAST(@A1 AS VARCHAR(%d)), @A2, @A3)", |
| dlen, dlen, dlen); |
| parseTree = parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 3, child(0), child(1), child(2)); |
| } |
| |
| parseTree = parseTree->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| else |
| return parseTree; |
| } |
| } |
| else if (type1.getTypeQualifier() == NA_DATETIME_TYPE) |
| { |
| // Convert stored date to numeric and then substring. |
| // Numeric value of a date is: (YYYY-1900)*10000 + (MM*100) + DD |
| // Then cast this numeric value as CHAR(7) before doing |
| // the substring. |
| DatetimeType &dtType = (DatetimeType&)type1; |
| if (dtType.getPrecision() == SQLDTCODE_DATE) |
| { |
| // Cast DATE to INT |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) |
| Cast(child(0), |
| new (bindWA->wHeap()) |
| SQLInt(bindWA->wHeap(), TRUE, type1.supportsSQLnull())); |
| newChild = newChild->bindNode(bindWA); |
| |
| // Cast INT to CHAR(7). |
| newChild = |
| new (bindWA->wHeap()) |
| Cast(newChild, |
| new (bindWA->wHeap()) |
| SQLChar(bindWA->wHeap(), 7, type1.supportsSQLnull())); |
| newChild = newChild->bindNode(bindWA); |
| setChild(0, newChild); |
| } |
| } |
| } |
| |
| // Substring inherits from BuiltinFunction .. Function .. ItemExpr. |
| BuiltinFunction::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return getValueId().getItemExpr(); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Exists |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *Exists::bindNode(BindWA *bindWA) |
| { |
| BindContext *context = bindWA->getCurrentScope()->context(); |
| NABoolean orig = context->inExistsPredicate(); |
| context->inExistsPredicate() = TRUE; |
| ItemExpr *boundExpr = Subquery::bindNode(bindWA); |
| context->inExistsPredicate() = orig; |
| return boundExpr; |
| } // Exists::bindNode() |
| |
| |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class UDFunction |
| // ----------------------------------------------------------------------- |
| ItemExpr *UDFunction::bindNode(BindWA *bindWA) |
| { |
| NARoutine *udf = 0, *udfAction = 0; |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| ItemExpr *boundExpr = NULL; |
| |
| // IS req 8: Check if UDF is in certain query contexts. |
| // No error check on return, these use NAList/NACollection and NAAbort if out of range. |
| BindScope *curScope = bindWA->getCurrentScope(); |
| BindContext *curContext = bindWA->getCurrentScope()->context(); |
| curContext->inUDFunction() = TRUE; |
| |
| // UDFs not allowed in argument list of a sequence function. |
| if (curContext->inSequenceFunction()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4461) |
| << DgString0(functionName_.getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // UDFs not allowed in ORDER BY clause of OLAP window function. |
| if (((curScope->getSequenceNode() && |
| ((RelRoot *)curScope->getSequenceNode())->getHasOlapFunctions()) || |
| (curScope->getSequenceNode() && |
| ((RelSequence *)curScope->getSequenceNode())->getHasOlapFunctions()) || |
| (curContext->inOtherSequenceFunction())) && |
| curContext->inOrderBy()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4462) |
| << DgString0(functionName_.getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // UDFs not allowed in ON clause of a full outer join. |
| if (curContext->inJoin() && |
| curContext->inJoin()->isFullOuterJoin() && |
| curContext->inJoinPred()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4463) |
| << DgString0(functionName_.getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // UDFs not allowed in WHEN clause of an AFTER trigger. |
| if (bindWA->getUsageParseNodePtr() && |
| bindWA->getUsageParseNodePtr()->getOperatorType() == DDL_CREATE_TRIGGER) |
| { |
| StmtDDLCreateTrigger *trigger = (StmtDDLCreateTrigger *) bindWA->getUsageParseNodePtr(); |
| if (trigger->isAfter() && |
| (curContext->inPredicate() || curContext->inRangePred())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4464) |
| << DgString0(functionName_.getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| // UDFs not allowed in WHERE clause of an DELETE [FIRST N] query. |
| if (curContext->deleteNode() && curContext->inWhereClause() && curContext->firstN()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4465) |
| << DgString0(functionName_.getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // UDFs not allowed in WHERE clause of an UPDATE [FIRST N] query. |
| if (curContext->inUpdate() && curContext->inWhereClause() && curContext->firstN()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4466) |
| << DgString0(functionName_.getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // UDFs not allowed in WHERE clause of an INSERT .. SELECT [FIRST N] query. |
| if (bindWA->isInsertSelectStatement()) |
| { |
| BindScope *prevScope = bindWA->getPreviousScope(curScope); |
| if (prevScope) // Must use previous scope for insert/select. |
| { |
| BindContext *prevContext = prevScope->context(); |
| if (prevContext && |
| curContext->inWhereClause() && prevContext->firstN()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4473) |
| << DgString0(functionName_.getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } |
| // UDFs not allowed in check constraint. |
| if (curContext->inCheckConstraint()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4470) |
| << DgString0(functionName_.getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Check for NARoutine for this UDF in cache (NARoutineDB) |
| NAString func = functionName_.getExternalName(); |
| NAString action = ""; |
| NAHeap *heap = CmpCommon::statementHeap(); |
| |
| NAString dftUdfLoc = ActiveSchemaDB()->getDefaults().getValue(UDF_METADATA_SCHEMA); |
| NAString dftUdfCat, dftUdfSch; |
| |
| ComObjectName functionName1(functionName_); |
| ComObjectName functionName2(functionName_); |
| |
| size_t index = dftUdfLoc.first('.'); |
| if (index > 1 && index < dftUdfLoc.length()) { |
| dftUdfCat = dftUdfLoc(0, index); |
| dftUdfSch = dftUdfLoc(index+1, dftUdfLoc.length()-index-1); |
| if (dftUdfSch.first('.') != NA_NPOS) // A delimited name was used, such that |
| { // we can't be sure assign was correct. |
| dftUdfCat = CmpSeabaseDDL::getSystemCatalogStatic().data(); |
| dftUdfSch = SEABASE_UDF_SCHEMA; // If there is a '.' in Sch, set to well known cat.sch |
| // Without this the QualifiedName creation in NARoutineDBKey |
| } // below may assert. |
| } |
| |
| |
| // Find UDF in cache or metadata |
| TrafDesc *udfMetadata = NULL; |
| TrafDesc *oldUdfMetadata = NULL; // not used, just to get code to compile |
| CmpSeabaseDDL cmpSBD(heap); |
| |
| try |
| { |
| if (CmpCommon::getDefault(COMP_BOOL_191) == DF_OFF) // temporary switch for |
| { // real and old metadata. |
| Int32 catSchNameChosen = 1; // Will be set to 1 or 2 based on |
| // which cat.sch action is found in. |
| |
| |
| // Set functionName1 to current cat.sch - unless cat.sch specified. |
| // If catalog not specified, add current catalog to name. |
| if (functionName1.getCatalogNamePartAsAnsiString() == "") |
| functionName1.setCatalogNamePart( |
| bindWA->getDefaultSchema().getCatalogName()); |
| // If schema not specified, add current schema to name. |
| if (functionName1.getSchemaNamePartAsAnsiString() == "") |
| functionName1.setSchemaNamePart( |
| bindWA->getDefaultSchema().getSchemaName()); |
| // Set functionName2 to default UDF cat.sch. |
| functionName2.setCatalogNamePart(dftUdfCat); |
| functionName2.setSchemaNamePart(dftUdfSch); |
| |
| QualifiedName functionName1AsQualName(functionName1, heap); |
| QualifiedName functionName2AsQualName(functionName2, heap); |
| // in open source, only the SEABASE catalog is allowed. |
| // Return an error if some other catalog is being used. |
| if ((NOT functionName1AsQualName.isSeabase()) && (NOT functionName1AsQualName.isHive())) |
| { |
| *CmpCommon::diags() |
| << DgSqlCode(-1002) |
| << DgCatalogName(functionName1AsQualName.getCatalogName()); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // Check the NARoutine cache for NARoutine first. |
| // 1. Look for UDF in current or specified cat/schema. |
| |
| NARoutineDBKey functionKey1(functionName1, heap); |
| udf = bindWA->getSchemaDB()->getNARoutineDB()->get(bindWA, &functionKey1); |
| catSchNameChosen = 1; |
| |
| // 2. If UDF not found in cache w/ current or spec'd cat.sch AND |
| // cat.sch NOT specified in query, look in cache for default cat.sch. |
| if (NULL == udf && |
| functionName_.getSchemaNamePartAsAnsiString() == "") |
| { |
| // Look in NARoutine cache with default cat.sch. |
| NARoutineDBKey functionKey2(functionName2, heap); |
| udf = bindWA->getSchemaDB()->getNARoutineDB()->get(bindWA, &functionKey2); |
| catSchNameChosen = 2; |
| } |
| |
| // 3. If UDF not found in NARoutine cache, look up in metadata. |
| if (NULL == udf) |
| { |
| udfMetadata = cmpSBD.getSeabaseRoutineDesc( |
| functionName1AsQualName.getCatalogName(), |
| functionName1AsQualName.getSchemaName(), |
| functionName1AsQualName.getObjectName()); |
| catSchNameChosen = 1; |
| } |
| // 4. If UDF not found in current or specified cat.sch AND |
| // cat.sch NOT specified in query, look up in metadata with |
| // default cat.sch. |
| if (NULL == udf && NULL == udfMetadata && |
| functionName_.getSchemaNamePartAsAnsiString() == "") |
| { |
| // Look for UDF in default cat.schema if cat.sch not |
| // spec'd on command line. |
| udfMetadata = cmpSBD.getSeabaseRoutineDesc( |
| functionName2AsQualName.getCatalogName(), |
| functionName2AsQualName.getSchemaName(), |
| functionName2AsQualName.getObjectName()); |
| |
| catSchNameChosen = 2; |
| } |
| if (catSchNameChosen == 1) |
| { |
| |
| functionName_.setCatalogNamePart(functionName1.getCatalogNamePartAsAnsiString()); |
| functionName_.setSchemaNamePart(functionName1.getSchemaNamePartAsAnsiString()); |
| } |
| else |
| { |
| functionName_.setCatalogNamePart(functionName2.getCatalogNamePartAsAnsiString()); |
| functionName_.setSchemaNamePart(functionName2.getSchemaNamePartAsAnsiString()); |
| } |
| // Remove -1001 through -1004 (not found errors) - the binder reports these. |
| CollIndex i; |
| for (Int32 err=-1004; err<=-1001; err++) |
| while ((i=CmpCommon::diags()->returnIndex(err)) != NULL_COLL_INDEX) |
| CmpCommon::diags()->deleteError(i); |
| // Remove -1389 (not found error) |
| while ((i=CmpCommon::diags()->returnIndex(-1389)) != NULL_COLL_INDEX) |
| CmpCommon::diags()->deleteError(i); |
| } |
| else |
| { |
| *CmpCommon::diags() << DgSqlCode(-4222) |
| << DgString0("UDF"); |
| bindWA->setErrStatus(); |
| return this; |
| // we are now guaranteed that COMP_BOOL_191 is OFF for closed source |
| } |
| } |
| catch ( ... ) |
| { |
| // Print out something?? |
| //CatExceptionTypeEnum eType = e.getExceptionType(); |
| *CmpCommon::diags() << DgSqlCode(-4457) |
| << DgString0(functionName_.getExternalName()) |
| << DgString1("Exception occurred during UDF lookup"); |
| } |
| // Set expanded UDF name for triggers, MVs, ... |
| ParNameLocList *pNameLocList = bindWA->getNameLocListPtr(); |
| if (pNameLocList) |
| { |
| ParNameLoc *pNameLoc |
| = pNameLocList->getNameLocPtr(getNamePosOfUdfInQuery()); |
| if (pNameLoc) |
| pNameLoc->setExpandedName(functionName_.getExternalName()); |
| } |
| |
| if (NULL == udf) // UDF/UUDF not found in NARoutine cache. |
| { |
| // IS req 5: Check if UDF/UUDF name found in metadata, if not output error(s). |
| if ((CmpCommon::getDefault(COMP_BOOL_191) == DF_OFF && |
| NULL == udfMetadata) || |
| (CmpCommon::getDefault(COMP_BOOL_191) == DF_ON && |
| (NULL == oldUdfMetadata |
| ) )) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4450) |
| << DgString0(functionName_.getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // IS req 6: Check ROUTINE_TYPE column of ROUTINES table. |
| // Emit error if invalid type. |
| ComRoutineType udrType = COM_UNKNOWN_ROUTINE_TYPE; |
| if (CmpCommon::getDefault(COMP_BOOL_191) == DF_OFF) |
| { |
| udrType = udfMetadata->routineDesc()->UDRType ; |
| } |
| |
| if (udrType != COM_SCALAR_UDF_TYPE && |
| udrType != COM_UNIVERSAL_UDF_TYPE) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4457) |
| << DgString0(functionName_.getExternalName()) |
| << DgString1("Only scalar or universal user-defined functions are supported"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| NAHeap *routineHeap; |
| if (bindWA->getSchemaDB()->getNARoutineDB()->cachingMetaData()) |
| { |
| // Create new heap for this NARoutine. The reason for this is to be able to |
| // track the size of this object. Otherwise we might use the context heap. |
| const Lng32 size = 16 * 1024; // The initial size |
| routineHeap = new CTXTHEAP NAHeap("NARoutine Heap", (NAHeap *)CTXTHEAP, size); |
| } |
| // If not caching, put NARoutine on statement heap. |
| else routineHeap=CmpCommon::statementHeap(); |
| |
| // IS req 3, 7.3. Instantiate NARoutine object. |
| // NARoutine data members will be assigned from udfMetadata. |
| Int32 errors=0; |
| NAString empty=""; |
| if (CmpCommon::getDefault(COMP_BOOL_191) == DF_OFF) |
| udf = new (routineHeap) |
| NARoutine(functionName_, |
| udfMetadata, |
| bindWA, |
| errors, |
| routineHeap); |
| if ( NULL == udf || errors != 0) |
| { |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Add NARoutine to the NARoutineDB cache. |
| if (bindWA->getSchemaDB()->getNARoutineDB()->cachingMetaData()) |
| bindWA->getSchemaDB()->getNARoutineDB()->put(udf); |
| } // if (NULL == udf ) -- NARoutine not in cache. |
| |
| // Create the routineDesc and initialize it with the NARoutine for the |
| // UDF. Action proceesing handled later. See below. |
| RoutineDesc *rdesc = new (bindWA->wHeap()) RoutineDesc(bindWA, udf); |
| if (rdesc == NULL) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4457) |
| << DgString0(functionName_.getExternalName()) |
| << DgString1("Internal Error creating the RoutineDesc"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| setRoutineDesc(rdesc); // Assign the RoutineDesc pointer in UDFunction. |
| |
| // Ideally it would be nice to call Function::bindNode() right here |
| // to bind the children (inputs) to the function, but that would |
| // invoke UDFunction::synthType() which requires the outputs to be |
| // setup. Instead we will make sure the inputs are bound by the call |
| // to setInOrOutParam() |
| |
| // Create item list for input arguments to be stored. |
| // In the Action case we might need more, but they will be |
| // allocated when we insert into the List. |
| ItemExprList * inParams = new(CmpCommon::statementHeap()) |
| ItemExprList(udf->getInParamCount(), |
| CmpCommon::statementHeap()); |
| Int32 minUdfInputs = 0; |
| Int32 maxUdfInputs = udf->getInParamCount(); |
| // Check input and output arguments from the ROUTINE_PARAMS table. |
| // Get expected num of inputs by checking if an argument is optional. |
| // If inputs from ROUTINE_PARAMS do not match input arguments from |
| // function, emit error. |
| // |
| NABoolean foundOptional = FALSE; |
| for (Int32 i=0; i<maxUdfInputs; i++) |
| { |
| // Check if parameter specified as optional. 'i' should never |
| // be out of range - based on assignment, however if it is, getColumn() |
| // via NAList::operator[] will issue NAAbort(). |
| if (udf->getInParams()[i]->isOptional()) |
| foundOptional = TRUE; |
| else if (foundOptional == TRUE) |
| { |
| // An optional argument cannot be followed by a required argument. |
| *CmpCommon::diags() << DgSqlCode(-4457) |
| << DgString0(functionName_.getExternalName()) |
| << DgString1("User-defined functions cannot have an optional argument followed by a required argument"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // If input not optional, then it is required, so increment minimum expected inputs. |
| else |
| minUdfInputs++; |
| } |
| |
| // Make sure that outputs are not optional. |
| for (Int32 i=0; i<udf->getOutParamCount(); i++) |
| { |
| // Check to make sure output not spec'd as optional. 'i' should never |
| // be out of range - based on assignment, however if it is, getColumn() |
| // via NAList::operator[] will issue NAAbort(). |
| if (udf->getOutParams()[i]->isOptional()) |
| { |
| // An optional argument MUST be an input. |
| *CmpCommon::diags() << DgSqlCode(-4457) |
| << DgString0(functionName_.getExternalName()) |
| << DgString1("User-defined functions cannot have an optional argument defined as an output"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // IS req 7.2. If this is not a UUDR, then check that inputs = expected. |
| // Cannot use the rdesc to check for UUDF as the action hasn't been |
| // created yet. For universal functions, do not check inputs/outputs |
| // from the metadata - we rely only on action metadata info. |
| if (!udf->isUniversal()) |
| { |
| // Non-deterministic UDFs not allowed in the query expression of a |
| // CREATE VIEW statement with CHECK OPTION. Note that this check is |
| // somewhat redundant since a CHECK OPTION must be used with an updatable |
| // view, but a UDF cannot be part of an updatable view. However, this |
| // latter condition is only found at execution time. Action determinism |
| // is checked later. |
| if (!udf->isDeterministic() && |
| bindWA->getUsageParseNodePtr() && |
| bindWA->getUsageParseNodePtr()->getOperatorType() == DDL_CREATE_VIEW) |
| { |
| StmtDDLCreateView *view = (StmtDDLCreateView *) bindWA->getUsageParseNodePtr(); |
| if (view->isWithCheckOptionSpecified()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4467) |
| << DgString0(functionName_.getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // IS req 8: Multi-valued (MV) UDFs are not allowed in TRANSPOSE clause. |
| if (curContext->inTransposeClause() && |
| udf->getOutParamCount() > 1 ) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4468) |
| << DgString0(functionName_.getExternalName()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // IS req 7: Process information from the ROUTINE_PARAMS, |
| // REPLICAS, and TEXT metadata tables. |
| |
| // Set up routine params in routine descriptor. |
| for (Int32 i=0; i<udf->getInParamCount(); i++) |
| { |
| // This will update the input params lists in the rdesc to |
| // reflect those of the UDF. |
| if (rdesc->createRoutineParam(bindWA, i, udf, |
| &(rdesc->getUDFInParamColumnList()), |
| &(rdesc->getUDFOutputColumnList()) ) == FALSE) |
| { |
| bindWA->setErrStatus(); |
| return FALSE; |
| } |
| } |
| |
| // Bind all the children, so we will know their output degree. |
| for (Int32 i=0; i<getNumChildren(); i++) |
| { |
| child(i).getPtr()->bindNode(bindWA); // If 'i' out of range, will CMPASSERT. |
| if (bindWA->errStatus()) return FALSE; |
| } |
| // Process UDF input arguments. If an input argument is a subquery or UDF, |
| // it may have multiple outputs. In that case, we add to the inputs of the |
| // UDF here by creating ValueIdProxy class for each added argument. |
| Int32 numInputs = 0; |
| for (Int32 i=0; i<getNumChildren(); i++) |
| { |
| ItemExpr *cmdArg = child(i).getPtr(); // If 'i' out of range, will CMPASSERT. |
| |
| // Function arguments are in descending order. |
| Int32 childOutputDegree = child(i).getPtr()->getOutputDegree(); |
| for (Int32 j=0; j<childOutputDegree; j++) |
| { |
| if (childOutputDegree == 1) // The argument only represents one input. |
| //setInOrOutParam(rdesc, cmdArg, numInputs, COM_INPUT_COLUMN, bindWA); |
| setInOrOutParam(rdesc, cmdArg, numInputs, COM_INPUT_COLUMN, inParams, bindWA); |
| else |
| { |
| // We create a ValueIdProxy for each element in the subquery's |
| // select list or for each output parameter of a multi-valued UDF. |
| // The first one of these will be marked to be transformed. This |
| // allows us to get the correct degree of statements containing |
| // MV UDFs or subqueries with degree > 1 at bind time. |
| ValueIdProxy *proxyOutput = |
| new (CmpCommon::statementHeap()) |
| ValueIdProxy(cmdArg->getValueId(), |
| cmdArg->getOutputItem(j)->getValueId(), |
| j, j==0 /* transform first derived child */ ); |
| //setInOrOutParam(rdesc, proxyOutput, numInputs+j, |
| setInOrOutParam(rdesc, proxyOutput, numInputs+j, |
| COM_INPUT_COLUMN, inParams, bindWA); |
| } |
| |
| if (bindWA->errStatus()) return this; |
| } |
| numInputs += childOutputDegree; |
| } |
| |
| // Check that number of inputs to UDF is correct. |
| if (numInputs < minUdfInputs || |
| numInputs > maxUdfInputs) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4452) |
| << DgString0(functionName_.getExternalName()) |
| << DgInt0(minUdfInputs) |
| << DgInt1(getNumChildren()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // IS req 7.2. Check that there is at least one output parameter |
| // specified by metadata. |
| if (udf->getOutParamCount() == 0) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4457) |
| << DgString0(functionName_.getExternalName()) |
| << DgString1("User-defined functions must have at least one registered output value"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| else |
| { |
| // Check output arguments against information in the COLS table. |
| for (Int32 i=0; i<udf->getOutParamCount(); i++) |
| { |
| // This will update the output params lists in the rdesc to |
| // reflect those of the UDF. |
| if (rdesc->createRoutineParam(bindWA, i+maxUdfInputs, udf, |
| &(rdesc->getUDFInParamColumnList()), |
| &(rdesc->getUDFOutputColumnList())) == FALSE) |
| { |
| bindWA->setErrStatus(); |
| return this; |
| } |
| // Function arguments are in descending order. |
| setInOrOutParam(rdesc, rdesc->getOutputColumnList()[i].getItemExpr(), |
| i, COM_OUTPUT_COLUMN, inParams, bindWA); |
| if (bindWA->errStatus()) return this; |
| } |
| } |
| } // end if (!udf->isUniversal() |
| |
| |
| // |
| // Proceed to save the UDF referenced name. |
| // Obtain the UDF usages list. |
| // But, first check to see if this UDF reference already exists in the UDF Info List. |
| // |
| |
| if (bindWA->getUsageParseNodePtr()) |
| { |
| LIST(OptUDFInfo *) *udfList = NULL; |
| |
| switch (bindWA->getUsageParseNodePtr()->getOperatorType()) |
| { |
| case DDL_CREATE_TRIGGER: |
| { |
| udfList = &bindWA->getUDFList(); |
| break; |
| } |
| case DDL_CREATE_VIEW: |
| { |
| // Set the UDF list for view processing |
| StmtDDLCreateView *view = (StmtDDLCreateView *) bindWA->getUsageParseNodePtr(); |
| CMPASSERT(view); |
| udfList = &view->getUDFList(); |
| break; |
| } |
| case DDL_CREATE_MV: |
| { |
| // Set the UDF list for materialized view processing |
| StmtDDLCreateMV *mv = (StmtDDLCreateMV *) bindWA->getUsageParseNodePtr(); |
| CMPASSERT(mv); |
| udfList = &mv->getUDFList(); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| if (udfList != NULL) |
| { |
| OptUDFInfo *udfInfo = NULL; |
| bool isUDFReferenced = false; |
| ULng32 numUdfs = udfList->entries(); |
| |
| for (ULng32 udfIndex = 0; udfIndex < numUdfs; udfIndex++) |
| if ((*udfList)[udfIndex]->getUDFExternalName().compareTo(functionName_.getExternalName()) == 0) |
| { |
| isUDFReferenced = true; |
| break; |
| } |
| |
| // If this is a new UDF reference, create and save on list |
| if (!isUDFReferenced) |
| { |
| OptUDFInfo *udfInfo = new (bindWA->wHeap()) OptUDFInfo(udf->getRoutineID(), |
| functionName_, |
| bindWA->wHeap()); |
| udfList->insert(udfInfo); |
| } |
| } |
| } |
| |
| // Since our inParams list now accurately reflects our real inputs, |
| // we need to update the children's array as it no longer reflects |
| // reality. For example a UDF with a T.* input, would at this point |
| // have the real columns that will be passed to the UDF at runtime in |
| // the inParams list, but the children array only contains the T.* entry. |
| // Since T.* never gets bound, it causes trouble if anyone tries to get |
| // the valueId or type from that child later on, so we get rid of it. |
| |
| // At this point the children represents the parameters that will |
| // be passed to the actual function during execution. |
| // inputVars_ contains the uncasted inputs that nodes above us in |
| // the tree can produce.. |
| |
| children().clear(); |
| |
| |
| for (UInt32 paramPos=0; paramPos<inParams->entries(); paramPos++) |
| { |
| |
| children().insertAt(paramPos, inParams->at(paramPos)); |
| } |
| |
| // Save off a copy of the chilren array |
| ARRAY(ExprValueId) copyOfChildren(HEAP); |
| for (Int32 i=0; i< getArity(); i++) |
| copyOfChildren.insertAt(i, child(i)); |
| |
| ItemExpr *retExpr = Function::bindNode(bindWA); |
| |
| // Now check to see if Function::bindNode() made any changes to the children |
| // if so we need to update our inputVars array. |
| |
| // Make sure the number of parameters didn't change |
| CCMPASSERT( copyOfChildren.entries() == getArity() ); |
| |
| for (Int32 i=0; i < getArity(); i++) |
| { |
| // If Function::bindNode() changed the child, update our inputVars |
| if (child(i)->castToItemExpr() != copyOfChildren[i]->castToItemExpr()) |
| { |
| // If the new child still contains the old valueId, we don't need to |
| // do anything, else we have to remove the original child value from |
| // the inputVars and add in the new one. If the new one is a CAST, |
| // we are going to use the child of the CAST as the input value that |
| // will be used as characteristic input for the IsolatedScalarUDF. |
| if (! child(i)->castToItemExpr()->referencesTheGivenValue( |
| copyOfChildren[i]->getValueId()), TRUE) |
| { |
| if (inputVars_.contains(copyOfChildren[i]->getValueId())) |
| inputVars_ -= copyOfChildren[i]->getValueId(); |
| else if ((copyOfChildren[i]->getOperatorType() == ITM_CAST) && |
| (inputVars_.contains(copyOfChildren[i]->child(0)->getValueId()))) |
| inputVars_ -= copyOfChildren[i]->child(0)->getValueId(); |
| else |
| CCMPASSERT(0); // do we have a multilevel CAST?? |
| |
| if ((child(i)->getOperatorType() == ITM_CAST) ) |
| inputVars_ += child(i)->child(0)->getValueId(); |
| else |
| inputVars_ += child(i)->getValueId(); |
| } |
| } |
| } |
| |
| curContext->inUDFunction() = FALSE; |
| |
| // add the routine to the UdrStoiList. The UdrStoi list is used |
| // to check valid privileges |
| LIST(OptUdrOpenInfo *) udrList = bindWA->getUdrStoiList (); |
| |
| // See if UDF already exists |
| NABoolean udrReferenced = FALSE; |
| for (ULng32 stoiIndex = 0; stoiIndex < udrList.entries(); stoiIndex++) |
| { |
| if ( 0 == |
| udrList[stoiIndex]->getUdrName().compareTo( |
| udf->getSqlName().getQualifiedNameAsAnsiString() |
| ) |
| ) |
| { |
| udrReferenced = TRUE; |
| break; |
| } |
| } |
| |
| // UDF has not been defined, go ahead an add one |
| if ( FALSE == udrReferenced ) |
| { |
| SqlTableOpenInfo *udrStoi = new (bindWA->wHeap ())SqlTableOpenInfo (); |
| udrStoi->setAnsiName ( convertNAString( |
| udf->getSqlName().getQualifiedNameAsAnsiString(), |
| bindWA->wHeap ()) |
| ); |
| |
| OptUdrOpenInfo *udrOpenInfo = new (bindWA->wHeap ()) |
| OptUdrOpenInfo( udrStoi |
| , udf->getSqlName().getQualifiedNameAsAnsiString() |
| , udf |
| ); |
| bindWA->getUdrStoiList().insert(udrOpenInfo); |
| } |
| |
| delete inParams; // clean up the memory used |
| |
| return retExpr; |
| } // UDFunction::bindNode() |
| |
| void |
| UDFunction::setInOrOutParam (RoutineDesc *routine, |
| ItemExpr *argument, |
| Int32 position, |
| ComColumnDirection paramMode, |
| ItemExprList *inParams, |
| BindWA *bindWA) |
| { |
| // This function is based on CallSP::setInOrOutParam(). |
| CollIndex ordinalPosition = position; |
| |
| if (argument == NULL) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4457) |
| << DgString0(functionName_.getExternalName()) |
| << DgString1("Internal error in setInOrOutParam()"); |
| bindWA->setErrStatus(); |
| return; |
| } // if argument == NULL |
| |
| |
| // Obtain the type of the function argument by binding it. |
| ItemExpr *boundExpr = argument->bindNode(bindWA); |
| if (bindWA->errStatus()) return; |
| |
| // If this is supposed to be an input, make checks. |
| if (COM_INPUT_COLUMN == paramMode) |
| { |
| if (routine == NULL) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4457) |
| << DgString0(functionName_.getExternalName()) |
| << DgString1("Internal error in setInOrOutParam(): routine is NULL."); |
| bindWA->setErrStatus(); |
| return; |
| } // if routine == NULL |
| |
| const ValueIdList &formalParams = routine->getInParamColumnList(); |
| if (ordinalPosition >= formalParams.entries()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4457) |
| << DgString0(functionName_.getExternalName()) |
| << DgString1("Internal error in setInOrOutParam(): index position out of range."); |
| bindWA->setErrStatus(); |
| return; |
| } // if routine == NULL |
| |
| // Obtain the type that was read from metadata. |
| const ValueId column = formalParams[ordinalPosition]; |
| const NAType ¶mType = column.getType(); |
| |
| ValueId inputTypeId = boundExpr->getValueId(); |
| |
| // If function argument is character type, get detailed info. |
| 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); |
| } |
| } |
| // Get final type of function argument. |
| const NAType &inputType = inputTypeId.getType(); |
| |
| |
| // Do type checking, |
| // If it is not a compatible type report an error |
| if (!( NA_UNKNOWN_TYPE == inputType.getTypeQualifier() || |
| paramType.isCompatible(inputType) || |
| boundExpr->getOperatorType() == ITM_DYN_PARAM |
| ) |
| ) |
| { |
| // Error, data types dont match |
| if (!routine->isUUDFRoutine()) |
| // Create error for user-defined function. |
| *CmpCommon::diags() << DgSqlCode(-4455) |
| << DgInt0((Lng32) ordinalPosition+1) |
| << DgString0(functionName_.getExternalName()) |
| << DgString1(inputType.getTypeSQLname(TRUE)) |
| << DgString2(paramType.getTypeSQLname(TRUE)); |
| else |
| { |
| // Create error for action. |
| NAString actionName = "UNKNOWN"; |
| if (routine->getActionNARoutine() && |
| routine->getActionNARoutine()->getActionName()) |
| actionName = routine->getActionNARoutine()->getActionName()->data(); |
| *CmpCommon::diags() << DgSqlCode(-4456) |
| << DgInt0((Lng32) ordinalPosition+1) |
| << DgString0(actionName.data()) |
| << DgString1(functionName_.getExternalName()) |
| << DgString2(inputType.getTypeSQLname(TRUE)) |
| << DgString3(paramType.getTypeSQLname(TRUE)); |
| } |
| bindWA->setErrStatus(); |
| return; |
| } // if NOT isCompatible |
| |
| // Store valueId in list. |
| inputVars_ += boundExpr->getValueId(); |
| |
| if (!hasSubquery_) |
| hasSubquery_ = boundExpr->containsSubquery(); |
| |
| if ( ! ( paramType == inputType) ) |
| { |
| // Create a Cast node to cast the argument from the function call |
| // to the type specified by the metadata (if the two are compatible.) |
| Cast *castExpr = new (bindWA->wHeap()) Cast(boundExpr, ¶mType, |
| ITM_CAST, TRUE); |
| ItemExpr *boundCast = castExpr->bindNode(bindWA); |
| if (bindWA->errStatus()) return; |
| // Add to input ItemExpr list. |
| inParams->insert(boundCast); |
| } |
| else |
| { |
| // Add to input ItemExpr list. |
| inParams->insert(boundExpr); |
| } |
| |
| } else if (COM_OUTPUT_COLUMN == paramMode) |
| { |
| outputVars_.insert (boundExpr->getValueId ()); |
| } // if OUTPUT |
| |
| } // UDFunction::setInOrOutParam() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ValueIdUnion |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *ValueIdUnion::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| if ((CmpCommon::getDefault(ALLOW_INCOMPATIBLE_OPERATIONS) == DF_ON) && |
| (isTrueUnion()) && |
| (entries() == 2)) |
| { |
| // allow CHAR || NUMERIC, NUMERIC || CHAR |
| const NAType &type1 = getLeftSource().getType(); |
| const NAType &type2 = getRightSource().getType(); |
| |
| Int32 srcChildIndex = -1; |
| Int32 otherChildIndex = -1; |
| Int32 convType = -1; |
| if ((type1.getTypeQualifier() == NA_NUMERIC_TYPE) && |
| (type2.getTypeQualifier() == NA_CHARACTER_TYPE)) |
| { |
| // convert leftSource(NUMERIC) to char type |
| srcChildIndex = 0; |
| otherChildIndex = 1; |
| if ((getSource(otherChildIndex).getItemExpr()->getOperatorType() == |
| ITM_DATEFORMAT) && |
| (((DateFormat*)(getSource(otherChildIndex).getItemExpr()))->getDateFormat() == |
| DateFormat::TIME_FORMAT_STR)) |
| { |
| // in special1 mode, if one side of a union is numeric and |
| // other side is FORMAT clause, then the numeric needs to |
| // be formatted as well. |
| convType = 1; |
| } |
| else |
| convType = 2; |
| } |
| else if ((type1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (type2.getTypeQualifier() == NA_NUMERIC_TYPE)) |
| { |
| // convert rightSource(NUMERIC) to char type |
| srcChildIndex = 1; |
| otherChildIndex = 0; |
| |
| if ((getSource(otherChildIndex).getItemExpr()->getOperatorType() == |
| ITM_DATEFORMAT) && |
| (((DateFormat*)(getSource(otherChildIndex).getItemExpr()))->getDateFormat() == |
| DateFormat::TIME_FORMAT_STR)) |
| { |
| // in special1 mode, if one side of a union is numeric and |
| // other side is FORMAT clause, then the numeric needs to |
| // be formatted as well. |
| convType = 1; |
| } |
| else |
| convType = 2; |
| } |
| |
| if (srcChildIndex >= 0) |
| { |
| ItemExpr * newChild = NULL; |
| if (convType == 1) |
| { |
| DateFormat * df = |
| ((DateFormat*)getSource(otherChildIndex).getItemExpr()); |
| ConstValue * cv = (ConstValue*)(df->child(1)->castToItemExpr()); |
| newChild = |
| new (bindWA->wHeap()) |
| Format(getSource(srcChildIndex).getItemExpr(), |
| NAString((char*)(cv->getConstValue()), |
| cv->getStorageSize()), |
| FALSE); |
| |
| } |
| else if (convType == 2) |
| { |
| Lng32 dLen = 0; |
| |
| NumericType &nType = (NumericType&) |
| getSource(srcChildIndex).getType(); |
| dLen = |
| nType.getDisplayLength(nType.getFSDatatype(), |
| nType.getNominalSize(), |
| nType.getPrecision(), |
| nType.getScale(), |
| 0); |
| |
| newChild = |
| new (bindWA->wHeap()) |
| Cast(getSource(srcChildIndex).getItemExpr(), |
| new (bindWA->wHeap()) |
| SQLChar(bindWA->wHeap(), dLen, |
| getSource(srcChildIndex).getType(). |
| supportsSQLnull())); |
| } |
| |
| newChild = newChild->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| setSource(srcChildIndex, newChild->getValueId()); |
| } |
| } |
| |
| // ValueIdUnion is a direct derived subclass of ItemExpr; |
| // safe to invoke this |
| ItemExpr *boundExpr = ItemExpr::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return boundExpr; |
| setResult(getValueId()); |
| return getValueId().getItemExpr(); |
| } // ValueIdUnion::bindNode() |
| |
| // |
| // This method is part of the Implicit Casting And Translation feature |
| // which allows the user to UNION a |
| // column of any supported character set with a column of any other |
| // supported character set, providing that one CS can be translated |
| // to the other without errors by throwing in a Translate node to |
| // translate to the most general character set. |
| // |
| ItemExpr * ValueIdUnion::tryToDoImplicitCasting( BindWA *bindWA ) |
| { |
| const NAType *entryType ; |
| CharInfo::CharSet savedMostGeneral_CS = CharInfo::UnknownCharSet; |
| Int32 savedMaxLen = 0; |
| NABoolean savedAllowNull = FALSE; |
| NABoolean savedUpShifted = TRUE; |
| NABoolean savedCaseInsensitive = TRUE; |
| CharInfo::Collation savedCollation = CharInfo::DefaultCollation; |
| |
| enum {iUCS2 = 0, iISO = 1, iUTF8 = 2, iSJIS = 3, iUNK = 4}; |
| Int32 charsets_involved[5] = { 0, 0, 0, 0, 0 }; |
| CollIndex i = 0; |
| |
| // |
| // Count the number of entries for each supported character set. |
| // If we don't have at least two different ones, we just return. |
| // |
| for (i = 0; i < entries(); i++) { |
| entryType = &getSource(i).getType(); |
| if (entryType->getTypeQualifier() == NA_CHARACTER_TYPE) |
| { |
| const CharType * ctI = (CharType *) entryType; |
| Int16 cur_chld_cs_ndx = iUNK; |
| |
| Int16 maxLen = ctI->getMaxLenInBytesOrNAWChars(); |
| if ( savedMaxLen < maxLen ) |
| savedMaxLen = maxLen ; |
| if ( ctI->supportsSQLnullLogical() ) |
| savedAllowNull = TRUE; |
| if ( ! ctI->isUpshifted() ) |
| savedUpShifted = FALSE; |
| if ( ! ctI->isCaseinsensitive() ) |
| savedCaseInsensitive = FALSE; |
| |
| switch ( ctI->getCharSet() ) |
| { |
| case CharInfo::UNICODE : |
| cur_chld_cs_ndx = iUCS2; |
| if ( savedMostGeneral_CS != CharInfo::UNICODE ) |
| { |
| savedMostGeneral_CS = CharInfo::UCS2; |
| savedCollation = ctI->getCollation(); |
| } |
| break; |
| case CharInfo::UTF8: |
| cur_chld_cs_ndx = iUTF8; |
| if ( ( savedMostGeneral_CS == CharInfo::UnknownCharSet ) || |
| ( savedMostGeneral_CS == CharInfo::SJIS ) || |
| ( savedMostGeneral_CS == CharInfo::ISO88591 ) ) |
| { |
| savedMostGeneral_CS = CharInfo::UTF8; |
| savedCollation = ctI->getCollation(); |
| } |
| break; |
| case CharInfo::SJIS: |
| cur_chld_cs_ndx = iSJIS; |
| if ( ( savedMostGeneral_CS == CharInfo::UnknownCharSet ) || |
| ( savedMostGeneral_CS == CharInfo::ISO88591 ) ) |
| { |
| savedMostGeneral_CS = CharInfo::SJIS; |
| savedCollation = ctI->getCollation(); |
| } |
| break; |
| case CharInfo::ISO88591: |
| cur_chld_cs_ndx = iISO; |
| if ( savedMostGeneral_CS == CharInfo::UnknownCharSet ) |
| { |
| savedMostGeneral_CS = CharInfo::ISO88591; |
| savedCollation = ctI->getCollation(); |
| } |
| break; |
| //case CharInfo::KANJI_MP: |
| //case CharInfo::KSC5601_MP: |
| default: |
| break; // Can not translate these currently. |
| } |
| charsets_involved[cur_chld_cs_ndx]++; |
| } |
| } |
| |
| Int32 charsetsCount = 0; |
| for (Int32 j = 0; j < iUNK; j++) |
| if (charsets_involved[j] > 0) |
| charsetsCount++; |
| if ( charsetsCount <= 1) return this; |
| CMPASSERT( savedMostGeneral_CS != CharInfo::UnknownCharSet ); // Shouldn't happen since charsetsCount > 1 |
| CMPASSERT( getValueId() == NULL_VALUE_ID ); // call this before assigning a value id |
| |
| CharType * MostGeneralType = new( bindWA->wHeap() ) |
| SQLVarChar(bindWA->wHeap(), savedMaxLen, |
| savedAllowNull, |
| savedUpShifted, |
| savedCaseInsensitive, |
| savedMostGeneral_CS, |
| savedCollation, |
| CharInfo::COERCIBLE, |
| savedMostGeneral_CS |
| ); |
| // |
| // OK, we have entries for at least two different character sets. |
| // So, we must insert some Translate nodes. Just to keep it |
| // simple, we always translate to the Most General charset |
| // |
| for (i = 0; i < entries(); i++) { |
| entryType = &getSource(i).getType(); |
| if ( entryType->getTypeQualifier() == NA_CHARACTER_TYPE ) |
| { |
| const CharType* ctI = (CharType *) entryType; |
| if ( ctI->getCharSet() != savedMostGeneral_CS ) |
| { |
| // Do Implicit Cast to the Most General Character Set |
| // |
| Int32 iTranslateFromTo = find_translate_type( ctI->getCharSet(), |
| savedMostGeneral_CS ); |
| if ( iTranslateFromTo == Translate::UNKNOWN_TRANSLATION ) |
| continue; // Just skip this entry! |
| |
| ValueId vidi = getSource(i); |
| ItemExpr * newTranslateChild = NULL; |
| ItemExpr * ieChild = vidi.getItemExpr(); |
| |
| if ( (ieChild->getOperatorType() == ITM_CONSTANT) && |
| ((ConstValue *)ieChild)->isNull() ) // If NULL, create new child with needed charset |
| { |
| newTranslateChild = new(bindWA->wHeap()) ConstValue(); |
| newTranslateChild->bindNode(bindWA); |
| (newTranslateChild->getValueId()).changeType(MostGeneralType); |
| } |
| else // Must insert a Translate node |
| { |
| newTranslateChild = |
| new (bindWA->wHeap()) Translate(vidi.getItemExpr(), iTranslateFromTo ); |
| |
| newTranslateChild = newTranslateChild->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| |
| changeSource(i, ValueDesc::create(newTranslateChild, |
| MostGeneralType, bindWA->wHeap()) ); |
| } |
| } |
| } |
| |
| return this; |
| } |
| |
| // This method returns the length of the padding string (the third argument to |
| // an LPAD or RPAD function, for e.g. the '00' in LPAD(<colname>, max-len, '00')) |
| // if the padding string is not a varchar. The method returns -2 is there is an error |
| // and -1 if the length is a varchar or for some other reason the length cannot |
| // be determined. |
| Int32 ZZZBinderFunction::getPadStringLength (ItemExpr* padExpr, BindWA* bindWA) |
| { |
| Int32 padStringLength = -1; |
| if (padExpr) |
| { |
| ItemExpr * tempPadString = |
| padExpr->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return -2; |
| |
| const NAType &typ1 = tempPadString->getValueId().getType(); |
| if ((typ1.getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (!typ1.isVaryingLen())) { |
| const CharType &ctyp1 = (CharType &) typ1; |
| padStringLength = typ1.getNominalSize()/ctyp1.getBytesPerChar() ; |
| } |
| } |
| return padStringLength ; |
| } |
| |
| // This method returns the maximum length (the second argument to |
| // an LPAD or RPAD function, for e.g. the <max-len> in LPAD(<colname>, <max-len>, '00')) |
| // if the second argument is a constant that is not null. |
| // The method returns -2 is there is an error |
| // and -1 if max-len is an expression or null. |
| Int32 ZZZBinderFunction::getPadLength (ItemExpr* padLengthExpr, BindWA* bindWA) |
| { |
| Int32 padLengthMax = -1; |
| if (padLengthExpr) |
| { |
| ItemExpr * tempPadLength = padLengthExpr->bindNode(bindWA); |
| if (bindWA->errStatus()) return -2; |
| |
| NumericType &tempType = (NumericType&)tempPadLength->getValueId().getType(); |
| if(! (tempType.isExact() && tempType.getScale() <=0 )) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4047); |
| bindWA->setErrStatus(); |
| return -2; |
| } |
| |
| NABoolean negate; |
| if ((tempPadLength->getOperatorType() == ITM_CONSTANT) && |
| (tempPadLength->castToConstValue(negate))) |
| { |
| ConstValue * cv = tempPadLength->castToConstValue(negate); |
| if (! cv->isNull()) |
| { |
| if (cv->canGetExactNumericValue()) |
| { |
| padLengthMax = (Int32) cv->getExactNumericValue(); |
| if ( padLengthMax > CmpCommon::getDefaultNumeric(TRAF_MAX_CHARACTER_COL_LENGTH)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4129) |
| << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return -2; |
| } |
| } |
| } |
| } // padLength is a constant |
| } |
| return padLengthMax ; |
| } |
| |
| NABoolean ZZZBinderFunction::isPadWithSpace (ExprValueId& padExpr, CharInfo::CharSet cs) |
| { |
| |
| if ((cs != CharInfo::UNICODE)&&(cs != CharInfo::ISO88591)) |
| { |
| return FALSE; // the optimization to use CAST for RPAD is done |
| // only for these two charsets. |
| } |
| |
| if (padExpr == NULL) // if this expression is null then we pad with blank space |
| return TRUE ; |
| |
| CharInfo::CharSet padExprCS = CharInfo::UnknownCharSet; |
| if(!padExpr->castToItemExpr()->nodeIsBound()) |
| { |
| CCMPASSERT(FALSE); |
| return FALSE; |
| } |
| const NAType& padExprType = padExpr->castToItemExpr()->getValueId().getType(); |
| if ( padExprType.getTypeQualifier() == NA_CHARACTER_TYPE ) |
| padExprCS = ((CharType&) padExprType).getCharSet(); |
| if (padExprCS != cs) |
| { |
| if ( CmpCommon::getDefault(ALLOW_IMPLICIT_CHAR_CASTING) == DF_OFF ) |
| return FALSE; // if charset of arg1 and arg3 are different then do not |
| // the RPAD to CAST optimization. We let existing code handle the error path. |
| } |
| |
| if ((padExpr->castToItemExpr()->getOperatorType() == ITM_CONSTANT) && |
| (!((ConstValue *)padExpr->castToItemExpr())->getText().isNull())) |
| { |
| NAString padString( |
| ((ConstValue *)padExpr->castToItemExpr())->getConstStr(FALSE)); |
| Int32 i = 0; |
| NABoolean foundSingleQuote = FALSE; |
| for (const char *s = padString.data(); *s; s++) |
| { |
| i++; |
| if ((!foundSingleQuote)&&(*s != '\'')) // loop through |
| continue; // the prefix _UCS2 or _ISO88591 |
| else if ((!foundSingleQuote)) |
| { |
| foundSingleQuote = TRUE; // found the leading single quote. |
| continue; |
| } |
| if ((i == (padString.length())) && (*s == '\'')) // trailing single quote |
| continue; |
| if (*s != ' ') |
| return FALSE; |
| } |
| return foundSingleQuote; |
| } |
| return FALSE ; |
| |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ZZZBinderFunction |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *ZZZBinderFunction::bindNode(BindWA *bindWA) |
| { |
| Parser parser(bindWA->currentCmpContext()); |
| char buf[2500]; |
| buf[0] = 0; |
| NABoolean resetRealBigNum = FALSE; |
| |
| ItemExpr *parseTree = NULL, *boundTree = NULL; |
| |
| // Need to check to see if we have any parameters that are MVFs or |
| // subqueries of degree > 1. |
| // |
| // Some functions like LPAD and RPAD can take 2 or 3 parameters. |
| // Here we can choose to if we are given the 2 parameter version, but |
| // one of those parameters is a subquer of degree 2 or an MVF that returns |
| // 2 outputs. |
| |
| Lng32 childDegree = 0; |
| Lng32 origArity = getArity(); |
| |
| for (Lng32 idx = 0; idx < origArity; idx++) |
| { |
| // Break when we have no more parameters |
| // This can happen since we have some function with optional params. |
| // getArity() may sometime return the maximum allowed, instead of actual |
| // This code will also only allow that expansion to happen if the |
| // subquery or MVF is the last given parameter. |
| if (child(idx) == NULL) break; |
| |
| ItemExpr *chldExpr = child(idx)->castToItemExpr(); |
| switch (chldExpr->getOperatorType()) |
| { |
| case ITM_ROW_SUBQUERY: |
| case ITM_USER_DEF_FUNCTION: |
| { |
| // Need to bind the subquery to find its degree |
| child(idx) = child(idx)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| Subquery *subq = (Subquery *) child(idx)->castToItemExpr(); |
| UDFunction * udf = (UDFunction *) child(idx)->castToItemExpr(); |
| Lng32 myDegree = (chldExpr->getOperatorType() == ITM_ROW_SUBQUERY) ? |
| subq->getSubquery()->getDegree() : |
| udf->getRoutineDesc()->getOutputColumnList().entries(); |
| |
| childDegree += myDegree; |
| |
| // Only allow expansion for LPAD/RPAD |
| // for the two parameter case |
| if ( myDegree > 1 && |
| ((getOperatorType() == ITM_LPAD) || |
| (getOperatorType() == ITM_RPAD)) && |
| origArity == 2) |
| { |
| // Expand the child() array |
| // First shift any existing ones over |
| for (Lng32 shiftIdx = origArity-1; shiftIdx > idx; shiftIdx--) |
| { |
| child(shiftIdx+myDegree) = child(shiftIdx); |
| } |
| |
| ItemExprList *mDegreeList = (ItemExprList *) new(bindWA->wHeap()) |
| ItemExprList(child(idx)->castToItemExpr() |
| ,bindWA->wHeap()); |
| |
| ItemExpr * ie = mDegreeList->convertToItemExpr(); |
| ie->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| ValueIdList mDegreeVlist; |
| ie->convertToValueIdList(mDegreeVlist, bindWA, ITM_ITEM_LIST); |
| |
| for (Lng32 expIdx = 0; expIdx < myDegree; expIdx++) |
| { |
| |
| child(idx + expIdx) = mDegreeVlist[expIdx].getItemExpr(); |
| } |
| |
| // bump the index to point to the next non processed param |
| idx += myDegree-1; |
| // We don't need to anything special to increase the arity |
| // since the assignment inside the for loop above does it for |
| // us. The BuiltinFunction::getArity() method looks at the number |
| // of children in its child array. |
| |
| } |
| } |
| break; |
| default: |
| childDegree += 1; |
| break; |
| } |
| } |
| |
| if ((childDegree > origArity ) && |
| (! ( childDegree == 3 && origArity == 2 && |
| ((getOperatorType() == ITM_LPAD) || |
| (getOperatorType() == ITM_RPAD)))) |
| ) |
| { |
| // Error |
| NAString upperFunc(getText(), bindWA->wHeap()); |
| |
| upperFunc.toUpper(); |
| *CmpCommon::diags() << DgSqlCode(-4479) << DgString0(upperFunc) |
| << DgInt1(origArity) << DgInt2(childDegree); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // fix 10-040621-7164. Make sure that the child expression is not dynamic param |
| // before verifying the type of the child. Otherwise, the verification may fail |
| // prematually. |
| // |
| // This is because bindNode() is called before ::synthesizeType() and the |
| // dynamic parameter's type will not be set until ::synthesizType() is called. |
| // If we emit error here, then we miss the chance to assign a solid type to the |
| // dynamic parameter child. |
| |
| // |
| // We do not have to apply the "is dynamic param" check if only the nullness |
| // We do not have to apply the "is dynamic param" check if only the nullness |
| // attribute of the actual type is checked, since a dynamic paramete is |
| // always nullable. |
| // |
| // This fix applies the "is dynamic param" check to the implementation of SQL function |
| // LEFT() and RIGHT(). We do not do this for QUARTER() because a cast is needed |
| // if a dynamic param is involved (see R2.0 Ref. Manual on MXCI, section Type |
| // Assignment for Parameters). |
| switch (getOperatorType()) |
| { |
| case ITM_DATE_TRUNC_YEAR: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| |
| //Cast to DATETIME YEAR first to pick up only the year. |
| strcpy(buf, "CAST(CAST(@A1 AS DATETIME YEAR) AS TIMESTAMP) ;"); |
| } |
| break; |
| |
| case ITM_DATE_TRUNC_MONTH: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| |
| //Get first day of year and then add in the months. |
| strcpy(buf, "CAST(CAST(@A1 AS DATETIME YEAR) AS TIMESTAMP) + " |
| "CAST(MONTH(@A1)-1 AS INTERVAL MONTH);"); |
| } |
| break; |
| |
| case ITM_DATE_TRUNC_DAY: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| |
| //Note: Cast to DATE first to zero out all time fields |
| strcpy(buf, "CAST(CAST(@A1 AS DATE) AS TIMESTAMP);"); |
| } |
| break; |
| |
| case ITM_DATE_TRUNC_HOUR: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| |
| //Note: Cast to DATE to zero out all time fields. Cast to TIMESTAMP in case DATE was supplied. |
| strcpy(buf, |
| "CAST( CAST(@A1 AS DATE) AS TIMESTAMP) + " |
| "CAST( HOUR( CAST(@A1 AS TIMESTAMP) ) AS INTERVAL HOUR);"); |
| } |
| break; |
| |
| case ITM_DATE_TRUNC_MINUTE: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| |
| strcpy(buf, "DATE_TRUNC('HOUR',@A1) + " |
| "CAST(MINUTE(CAST(@A1 AS TIMESTAMP)) AS INTERVAL MINUTE);"); |
| } |
| break; |
| |
| case ITM_DATE_TRUNC_SECOND: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| |
| strcpy(buf, "DATE_TRUNC('MINUTE',@A1) + " |
| "CAST( CAST( SECOND(CAST(@A1 AS TIMESTAMP)) AS SMALLINT) " |
| "AS INTERVAL SECOND);"); |
| } |
| break; |
| |
| case ITM_DATE_TRUNC_CENTURY: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| |
| strcpy(buf, "CAST( CAST(@A1 AS DATETIME YEAR) AS TIMESTAMP ) - " |
| "CAST( MOD(YEAR(@A1),100) AS INTERVAL YEAR(4) );"); |
| } |
| break; |
| |
| case ITM_DATE_TRUNC_DECADE: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| |
| strcpy(buf, "CAST( CAST(@A1 AS DATETIME YEAR) AS TIMESTAMP ) - " |
| "CAST( MOD(YEAR(@A1),10) AS INTERVAL YEAR(4) );"); |
| } |
| break; |
| |
| case ITM_DATEDIFF_YEAR: |
| case ITM_TSI_YEAR: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| if (enforceDateOrTimestampDatatype(bindWA,1,3)) |
| return this; |
| strcpy(buf, "CAST( YEAR(@A2) - YEAR(@A1) AS INT) ;"); |
| } |
| break; |
| |
| case ITM_DATEDIFF_MONTH: |
| case ITM_TSI_MONTH: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| if (enforceDateOrTimestampDatatype(bindWA,1,3)) |
| return this; |
| strcpy(buf, "CAST( (YEAR(@A2)*12 + MONTH(@A2)) - " |
| "(YEAR(@A1)*12 + MONTH(@A1)) AS INT) ;"); |
| } |
| break; |
| |
| case ITM_MONTHS_BETWEEN: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,1)) |
| return this; |
| if (enforceDateOrTimestampDatatype(bindWA,1,2)) |
| return this; |
| strcpy(buf, "CASE WHEN DAY (@A1) = DAY (@A2) THEN (YEAR(@A1)*12 + MONTH(@A1) - (YEAR(@A2)*12 + MONTH(@A2))) ELSE CAST((CAST(@A1 AS DATE) - CAST(@A2 AS DATE)) AS NUMERIC(18,6))/31 END"); |
| } |
| break; |
| |
| case ITM_DATEDIFF_DAY: |
| case ITM_TSI_DAY: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| if (enforceDateOrTimestampDatatype(bindWA,1,3)) |
| return this; |
| strcpy(buf, "CAST( CAST(@A2 AS DATE) - CAST(@A1 AS DATE) AS INT );"); |
| } |
| break; |
| |
| case ITM_DATEDIFF_HOUR: |
| case ITM_TSI_HOUR: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| if (enforceDateOrTimestampDatatype(bindWA,1,3)) |
| return this; |
| strcpy(buf, |
| "CAST( (JULIANTIMESTAMP(DATE_TRUNC('HOUR',@A2)) - " |
| " JULIANTIMESTAMP(DATE_TRUNC('HOUR',@A1))) / (1000000*3600) " |
| " AS INT);"); |
| } |
| break; |
| |
| case ITM_DATEDIFF_MINUTE: |
| case ITM_TSI_MINUTE: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| if (enforceDateOrTimestampDatatype(bindWA,1,3)) |
| return this; |
| strcpy(buf, |
| "CAST( (JULIANTIMESTAMP(DATE_TRUNC('MINUTE',@A2)) - " |
| " JULIANTIMESTAMP(DATE_TRUNC('MINUTE',@A1))) / (1000000*60)" |
| " AS INT);"); |
| } |
| break; |
| |
| case ITM_DATEDIFF_SECOND: |
| case ITM_TSI_SECOND: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| if (enforceDateOrTimestampDatatype(bindWA,1,3)) |
| return this; |
| strcpy(buf, "CAST( " |
| "(JULIANTIMESTAMP(DATE_TRUNC('SECOND',@A2)) - " |
| " JULIANTIMESTAMP(DATE_TRUNC('SECOND',@A1))) / 1000000" |
| " AS INT);"); |
| } |
| break; |
| |
| case ITM_DATEDIFF_QUARTER: |
| case ITM_TSI_QUARTER: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| if (enforceDateOrTimestampDatatype(bindWA,1,3)) |
| return this; |
| strcpy(buf, "CAST( (" |
| "((YEAR(@A2)*12) + ((QUARTER(@A2)-1)*3)) - " |
| "((YEAR(@A1)*12) + ((QUARTER(@A1)-1)*3)) ) / 3 AS INT);"); |
| } |
| break; |
| |
| case ITM_DATEDIFF_WEEK: |
| case ITM_TSI_WEEK: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,2)) |
| return this; |
| if (enforceDateOrTimestampDatatype(bindWA,1,3)) |
| return this; |
| strcpy(buf, "CAST((" |
| "(CAST(@A2 AS DATE) - CAST(DAYOFWEEK(@A2)-1 AS INTERVAL DAY)) - " |
| "(CAST(@A1 AS DATE) - CAST(DAYOFWEEK(@A1)-1 AS INTERVAL DAY))" |
| ") / 7 AS INT);"); |
| } |
| break; |
| |
| case ITM_LAST_DAY: |
| { |
| if (enforceDateOrTimestampDatatype(bindWA,0,1)) |
| return this; |
| |
| strcpy(buf, "@A1 - CAST( DAY(@A1) -1 AS INTERVAL DAY) + INTERVAL '1' MONTH - INTERVAL '1' DAY;"); |
| } |
| break; |
| |
| case ITM_NEXT_DAY: |
| { |
| // Make sure that child(0) is of date or timestamp datatype. |
| if (enforceDateOrTimestampDatatype(bindWA,0,1)) |
| return this; |
| |
| // make sure child(1) is of string type |
| ItemExpr * tempBoundTree = |
| child(1)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (tempBoundTree->getValueId().getType().getTypeQualifier() != |
| NA_CHARACTER_TYPE) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4116) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| strcpy(buf, "@A1 + CAST( MOD( CASE WHEN UPPER(@A2) = 'MONDAY' THEN -5 WHEN UPPER(@A2) = 'TUESDAY' THEN -4 WHEN UPPER(@A2) = 'WEDNESDAY' THEN -3 WHEN UPPER(@A2) = 'THURSDAY' THEN -2 WHEN UPPER(@A2) = 'FRIDAY' THEN -1 WHEN UPPER(@A2) = 'SATURDAY' THEN 0 WHEN UPPER(@A2) = 'SUNDAY' THEN 1 ELSE NULL END - DAYOFWEEK(@A1), 7) + 7 AS INTERVAL DAY);"); |
| } |
| break; |
| |
| case ITM_YEARWEEK: |
| { |
| // human-readable week format, 100*year + week |
| strcpy(buf, "CAST( (YEAR(@A1) * 100 + WEEK(@A1)) AS NUMERIC(6,0));"); |
| } |
| break; |
| |
| case ITM_YEARWEEKD: |
| { |
| // dense week format, 54*year + 0-based week |
| // (see week function documentation, week value can range from 1 to 54) |
| strcpy(buf, "CAST( (YEAR(@A1) * 54 + (WEEK(@A1) - 1)) AS NUMERIC(6,0));"); |
| } |
| break; |
| |
| case ITM_COALESCE: |
| { |
| bindChildren(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| ExprValueId eVid(child(0)->castToItemExpr()); |
| ItemExprTreeAsList coalesceList(&eVid, ITM_ITEM_LIST); |
| |
| IfThenElse * firstITE = NULL; |
| IfThenElse * currITE = NULL; |
| for (CollIndex i = 0; i < (CollIndex)coalesceList.entries(); i++) |
| { |
| // Specifically prohibit the last operand from being explicit NULL |
| if ( (i+1) == (CollIndex)coalesceList.entries() ) { |
| if ( (coalesceList[i])->getOperatorType() == ITM_CONSTANT ) { |
| if ( ((ConstValue *)coalesceList[i])->isNull() ) { |
| *CmpCommon::diags() << DgSqlCode(-3416) << DgString0("COALESCE"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } |
| ItemExpr * v = |
| new(bindWA->wHeap()) UnLogic(ITM_IS_NOT_NULL, coalesceList[i]); |
| IfThenElse * ite = new(bindWA->wHeap()) IfThenElse(v, coalesceList[i], NULL); |
| if (firstITE == NULL) |
| firstITE = ite; |
| if (currITE != NULL) |
| { |
| currITE->setElse(ite); |
| } |
| currITE = ite; |
| } |
| currITE->setElse(new(bindWA->wHeap()) SystemLiteral()); |
| parseTree = new(bindWA->wHeap()) Case(NULL, firstITE); |
| } |
| break; |
| |
| case ITM_DAYNAME: |
| { |
| // find the nullability of child |
| //coverity[returned_pointer] |
| ItemExpr * tempBoundTree = |
| child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| NABoolean childIsNullable = FALSE; |
| if (tempBoundTree->getValueId().getType().supportsSQLnull()) |
| { |
| childIsNullable = TRUE; |
| } |
| |
| if (childIsNullable) |
| strcpy(buf, "CASE WHEN @A1 IS NULL THEN NULL ELSE "); |
| else |
| strcpy(buf, ""); |
| |
| strcat (buf, "CASE DAYOFWEEK(@A1) WHEN 1 THEN 'Sunday' WHEN 2 THEN 'Monday' WHEN 3 THEN 'Tuesday' WHEN 4 THEN 'Wednesday' WHEN 5 THEN 'Thursday' WHEN 6 THEN 'Friday' WHEN 7 THEN 'Saturday' ELSE 'ERROR' END"); |
| |
| if (childIsNullable) |
| strcat(buf, " END;"); |
| else |
| strcat(buf, ";"); |
| } |
| break; |
| |
| case ITM_DECODE: |
| { |
| Int32 savedCurrChildNo = currChildNo(); |
| |
| for (Int32 i = 0; i < getArity(); i++, currChildNo()++) |
| { |
| ItemExpr *boundExpr = child(i)->bindNode(bindWA); |
| child(i) = boundExpr; |
| } |
| |
| currChildNo() = savedCurrChildNo; |
| |
| if (bindWA->errStatus()) |
| return this; |
| |
| ExprValueId eVid(child(0)->castToItemExpr()); |
| ItemExprTreeAsList decodeList(&eVid, ITM_ITEM_LIST); |
| |
| const NAType &typ1 = eVid->getValueId().getType(); |
| NABoolean noNulls = FALSE; |
| |
| if ((getArity() == 1) && (NOT typ1.supportsSQLnull())) { |
| noNulls = true; // main expression cannot be null so a simplier expression can be built |
| } |
| |
| IfThenElse * firstITE = NULL; |
| IfThenElse * currITE = NULL; |
| |
| Int32 numExprs = decodeList.entries(); |
| |
| if (numExprs < 3) { |
| *CmpCommon::diags() << DgSqlCode(-15001) << DgString0(")"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| BiRelat *v; |
| for (CollIndex i = 1; i < (CollIndex)numExprs-1; i++) |
| { |
| v = new(bindWA->wHeap())BiRelat(ITM_EQUAL,decodeList[0] ,decodeList[i]); |
| v->setSpecialNulls(TRUE); |
| IfThenElse * ite = new(bindWA->wHeap()) IfThenElse(v, decodeList[i+1], NULL); |
| if (firstITE == NULL) |
| firstITE = ite; |
| |
| if (currITE != NULL) |
| { |
| currITE->setElse(ite); |
| } |
| |
| currITE = ite; |
| i++; // must increment two each loop |
| } |
| |
| if (numExprs % 2 == 0 ) // even number. final argument is an else |
| currITE->setElse( decodeList[numExprs-1]); |
| else |
| currITE->setElse(new(bindWA->wHeap()) SystemLiteral()); |
| |
| parseTree = new(bindWA->wHeap()) Case(NULL, firstITE); |
| parseTree->setOrigOpType(getOperatorType()); |
| } |
| break; |
| |
| case ITM_DAYOFYEAR: |
| { |
| strcpy(buf, "CAST(CAST(@A1 AS DATE) - FIRSTDAYOFYEAR(@A1) AS INT) + 1;"); |
| } |
| break; |
| |
| case ITM_FIRSTDAYOFYEAR: |
| { |
| strcpy(buf, "CAST(CAST(@A1 AS DATETIME YEAR) AS DATE);"); |
| } |
| break; |
| |
| case ITM_DAYOFMONTH: |
| { |
| // Make sure that the child is of date datatype. |
| ItemExpr * tempBoundTree = |
| child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (tempBoundTree->getValueId().getType().getTypeQualifier() != |
| NA_DATETIME_TYPE) |
| { |
| // 4071 The operand of a DAYOFMONTH function must be a datetime. |
| *CmpCommon::diags() << DgSqlCode(-4071) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| parseTree = new(bindWA->wHeap()) |
| ExtractOdbc(REC_DATE_DAY, child(0), TRUE); |
| } |
| break; |
| |
| case ITM_GREATEST: |
| { |
| strcpy(buf, "CASE WHEN @A1 is NULL or @A2 is null then NULL WHEN @A1 > @A2 then @A1 else @A2 END;"); |
| } |
| break; |
| |
| case ITM_LEAST: |
| { |
| strcpy(buf, "CASE WHEN @A1 is NULL or @A2 is null then NULL WHEN @A1 < @A2 then @A1 else @A2 END;"); |
| } |
| break; |
| |
| case ITM_INSERT_STR: |
| { |
| // Make sure that the third child(length) is of unsigned numeric with |
| // scale of zero. |
| ItemExpr * tempBoundTree = |
| child(2)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // fix case 10-031103-2610, soln 10-031103-0997: make sure length arg |
| // in function "INSERT(s1, start, length, s2)" is not null. |
| if (tempBoundTree->getOperatorType() == ITM_CONSTANT && |
| ((ConstValue *)tempBoundTree)->isNull()) { |
| *CmpCommon::diags() << DgSqlCode(-4097) << DgString0("INSERT"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // |
| // Type cast any param. |
| // |
| SQLInt nType(FALSE); |
| ValueId vid = child(2)->getValueId(); |
| vid.coerceType(nType, NA_NUMERIC_TYPE); |
| |
| NABoolean errorLength = FALSE; |
| if (tempBoundTree->getValueId().getType().getTypeQualifier() != |
| NA_NUMERIC_TYPE) |
| { |
| errorLength = TRUE; |
| } |
| |
| const NumericType &ntyp = |
| (NumericType &) tempBoundTree->getValueId().getType(); |
| |
| if ((NOT ntyp.isExact()) || (ntyp.getScale() != 0)) |
| errorLength = TRUE; |
| |
| if (errorLength) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4053) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // INSERT (str1(@A1), start(@A2), length(@A3), str2(@A4)) |
| // Returns a string where <length> characters have been deleted from |
| // <str1> beginning at <start> and where <str2> has been inserted |
| // into <str1>, beginning at <start> |
| |
| // make sure replacement expression handles any null args correctly |
| strcpy(buf, "CASE WHEN @A1 IS NULL THEN NULL" |
| " WHEN @A2 IS NULL THEN NULL" |
| " WHEN @A3 IS NULL THEN NULL" |
| " WHEN @A4 IS NULL THEN NULL ELSE "); |
| |
| // Get the characters before <start> |
| strcat(buf, "(LEFT(@A1, CAST(@A2 AS INT UNSIGNED) - 1)"); |
| |
| // append <str2> |
| strcat(buf, "|| @A4 "); |
| |
| // and finally append the 'rightmost' characters from str1 after |
| // skipping (start + length) characters. |
| // If the number of 'rightmost' characters is a negative, |
| // then insert '0' characters. |
| strcat(buf, "|| CASE WHEN (CHAR_LENGTH(@A1) - (@A2 + CAST(@A3 AS INT UNSIGNED)) + 1) > 0 THEN RIGHT(@A1, CHAR_LENGTH(@A1) - (@A2 + CAST(@A3 AS INT UNSIGNED)) + 1) ELSE RIGHT(@A1, 0) END) END;"); |
| } |
| break; |
| |
| case ITM_LPAD: |
| { |
| Int32 padStringLength = 0; |
| if (child(2)) |
| { |
| padStringLength = getPadStringLength(child(2)->castToItemExpr(), bindWA); |
| if (bindWA->errStatus()) return this; |
| } |
| |
| if (child(2) == NULL) |
| { |
| // pad with spaces |
| // NOTE: The outer SUBSTRING operation would seem to be unnecessary |
| // in some cases, but it ensures the SQL compiler always knows that |
| // the maximum length of the return string is the user-specified value. |
| |
| strcpy(buf, |
| "SUBSTRING( " |
| "CASE WHEN @A1 IS NULL THEN NULL " |
| "WHEN CHAR_LENGTH(@A1) >= @A2 THEN @A1 " |
| "ELSE SUBSTRING( SPACE(@A2, _UNKNOWN_), 1, " |
| " @A2 - CHAR_LENGTH(@A1) ) || @A1 " |
| "END, 1, @A2 ) ;"); |
| } |
| else if (padStringLength == 1) |
| { |
| // when the third param has a length = 1 then the we do not need to |
| // use the complicated case expression in the else branch a few lines |
| // below. When the padding string length is known to be 1 we can use |
| // an case expression very similar to the situation when child(2) is null. |
| |
| strcpy(buf, |
| "SUBSTRING( " |
| "CASE WHEN @A1 IS NULL THEN NULL " |
| "WHEN CHAR_LENGTH(@A1) >= @A2 THEN @A1 " |
| "ELSE SUBSTRING( " |
| "REPEAT(@A3, @A2), 1, @A2 - CHAR_LENGTH(@A1)) || @A1 " |
| "END, 1, @A2 ) ;"); |
| } |
| else |
| { |
| // pad with the third param. |
| Int32 lpadLength = getPadLength(child(1)->castToItemExpr(), bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // If result size needed is less than some threshold, use fast path |
| if ((lpadLength > 0) && (lpadLength <= 1024)) { |
| strcpy(buf, |
| "SUBSTRING( " |
| "CASE WHEN @A1 IS NULL OR @A3 IS NULL THEN NULL " |
| "WHEN CHAR_LENGTH(@A1) >= @A2 THEN @A1 " |
| "ELSE SUBSTRING( REPEAT(@A3, @A2), 1, " |
| "@A2 - CHAR_LENGTH(@A1)) || @A1 " |
| "END, 1, @A2 );"); |
| } |
| else |
| { |
| // NOTES: |
| // (1) The outer SUBSTRING operation would seem to be unnecessary |
| // in some cases, but it ensures the SQL compiler always knows |
| // that the maximum length of the return string is the |
| // user-specified value (if the user specified a constant.) |
| // (2) The 2nd WHEN prevents a negative REPEAT count and is |
| // also required by definition of this padding function. |
| // (3) The 3rd WHEN prevents division by 0 later on and also |
| // ensures we catch overly large count (@A2) values at compile |
| // time if possible ... by sending @A2 directly to REPEAT |
| // without doing any CASTing or arithmetic on it (because |
| // doing those causes the value of @A2 to be resolved at |
| // runtime instead of compile time.) |
| |
| strcpy(buf, |
| "SUBSTRING( " |
| "CASE WHEN @A1 IS NULL OR @A3 IS NULL THEN NULL " |
| "WHEN CHAR_LENGTH(@A1) >= @A2 THEN @A1 " |
| "WHEN CHAR_LENGTH(@A3) <= 1 THEN " |
| "SUBSTRING( REPEAT(@A3, @A2), 1, " |
| "@A2 - CHAR_LENGTH(@A1)) || @A1 " |
| "ELSE SUBSTRING( " |
| "REPEAT(@A3," |
| "CAST((@A2 - CHAR_LENGTH(@A1))/CHAR_LENGTH(@A3) " |
| "+ 1 AS INT)" |
| "), " |
| " 1, " |
| " @A2 - CHAR_LENGTH(@A1) ) || @A1 " |
| "END, 1, @A2 );"); |
| |
| if (lpadLength > -1) |
| { |
| parseTree = parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 3, child(0), child(1), child(2)); |
| buf[0] = 0; |
| |
| // The max length of repeat is "length of lpad" * "padding string |
| // length" * "bytes per char". The "length of lpad" is stored, |
| // and when the type is synthesized, the length will be multiplied |
| // by the nominal size of the pattern string. Also note, both the |
| // pattern string and the column must be the same type - an error |
| // would occur otherwise at compile-time. |
| |
| Int32 maxRepeatLength = lpadLength; |
| |
| if (parseTree && parseTree->getOperatorType() == ITM_SUBSTR) |
| { |
| NAList<Lng32> childNumList(CmpCommon::statementHeap(),7); |
| NAList<OperatorTypeEnum> |
| opTypeList(CmpCommon::statementHeap(),7); |
| |
| childNumList.insertAt(0,0); |
| opTypeList.insertAt(0,ITM_CASE); |
| |
| childNumList.insertAt(1,0); |
| opTypeList.insertAt(1,ITM_IF_THEN_ELSE); |
| |
| childNumList.insertAt(2,2); |
| opTypeList.insertAt(2,ITM_IF_THEN_ELSE); |
| |
| childNumList.insertAt(3,2); |
| opTypeList.insertAt(3,ITM_IF_THEN_ELSE); |
| |
| childNumList.insertAt(4,2); opTypeList.insertAt(4,ITM_CONCAT); |
| childNumList.insertAt(5,0); opTypeList.insertAt(5,ITM_SUBSTR); |
| childNumList.insertAt(6,0); opTypeList.insertAt(6,ITM_REPEAT); |
| |
| Repeat* repNode = |
| (Repeat *) parseTree->getParticularItemExprFromTree( |
| childNumList, opTypeList) ; |
| if (repNode && (maxRepeatLength > 1)) |
| repNode->setMaxLength(maxRepeatLength); |
| |
| } // end of block that is looking for the problematic Repeat node |
| else |
| { |
| bindWA->setErrStatus(); // couldn't parse lpad replacement str |
| return this; |
| } |
| } // lpad has a fixed max length |
| } |
| } // third param is has more than one character or is a varchar |
| } // end of LPAD case |
| break; |
| |
| case ITM_MONTHNAME: |
| { |
| // find the nullability of child |
| ItemExpr * tempBoundTree = |
| child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| NABoolean childIsNullable = FALSE; |
| if (tempBoundTree->getValueId().getType().supportsSQLnull()) |
| { |
| childIsNullable = TRUE; |
| } |
| |
| if (childIsNullable) |
| strcpy(buf, "CASE WHEN @A1 IS NULL THEN NULL ELSE "); |
| else |
| strcpy(buf, ""); |
| |
| strcat (buf, "CASE MONTH(@A1) WHEN 1 THEN 'January' WHEN 2 THEN 'February' WHEN 3 THEN 'March' WHEN 4 THEN 'April' WHEN 5 THEN 'May' WHEN 6 THEN 'June' WHEN 7 THEN 'July' WHEN 8 THEN 'August' WHEN 9 THEN 'September' WHEN 10 THEN 'October' WHEN 11 THEN 'November' WHEN 12 THEN 'December' ELSE 'Error' END"); |
| |
| if (childIsNullable) |
| strcat(buf, " END;"); |
| else |
| strcat(buf, ";"); |
| } |
| break; |
| |
| case ITM_ODBC_LENGTH: |
| { |
| strcpy(buf, "CHAR_LENGTH(RTRIM(@A1));"); |
| } |
| break; |
| |
| case ITM_QUARTER: |
| { |
| // Make sure that the child is of datetime datatype. |
| ItemExpr * tempBoundTree = |
| child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| if (tempBoundTree->getValueId().getType().getTypeQualifier() != |
| NA_DATETIME_TYPE) |
| { |
| // 4071 The operand of a QUARTER function must be a datetime. |
| *CmpCommon::diags() << DgSqlCode(-4071) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| NABoolean childIsNullable = FALSE; |
| if (tempBoundTree->getValueId().getType().supportsSQLnull()) |
| { |
| childIsNullable = TRUE; |
| } |
| |
| if (childIsNullable) |
| strcpy(buf, "CASE WHEN @A1 IS NULL THEN NULL ELSE "); |
| else |
| strcpy(buf, ""); |
| |
| strcat (buf, "CASE WHEN MONTH(@A1) >= 1 AND MONTH(@A1) <= 3 THEN 1 WHEN MONTH(@A1) >= 4 AND MONTH(@A1) <= 6 THEN 2 WHEN MONTH(@A1) >= 7 AND MONTH(@A1) <= 9 THEN 3 WHEN MONTH(@A1) >= 10 AND MONTH(@A1) <= 12 THEN 4 ELSE 0 END"); |
| |
| if (childIsNullable) |
| strcat(buf, " END;"); |
| else |
| strcat(buf, ";"); |
| |
| } |
| break; |
| |
| case ITM_RIGHT: |
| case ITM_LEFT: |
| |
| { |
| // LEFT(<str>(@A1), <count>(A2)) |
| // RIGHT(<str>(@A1), <count>(A2)) |
| |
| // verify that the COUNT is specified as an INT with no scale. |
| ItemExpr * tempBoundTree = |
| child(1)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| const NAType &typ1 = tempBoundTree->getValueId().getType(); |
| |
| if ( tempBoundTree->getOperatorType() != ITM_DYN_PARAM ) { |
| const NumericType &ntyp1 = (NumericType &) typ1; |
| if ((typ1.getTypeQualifier() != NA_NUMERIC_TYPE) || |
| (NOT ntyp1.isExact())) |
| { |
| // 4046 Count must be exact numeric. |
| *CmpCommon::diags() << DgSqlCode(-4046) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (ntyp1.getScale() != 0) |
| { |
| // 4047 Count must have a scale of 0. |
| *CmpCommon::diags() << DgSqlCode(-4047) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| if ( getOperatorType() == ITM_RIGHT ) |
| // The case expression is needed for cases where the length supplied |
| // exceeds the length of the string; in this case we want to return |
| // the whole string. SUBSTR of a 0 or negative value doesn't do that. |
| strcpy(buf, "SUBSTRING(@A1 FROM " |
| "CASE WHEN(CHAR_LENGTH(@A1) - CAST(@A2 AS INT UNSIGNED) + 1) > 1 " |
| "THEN (CHAR_LENGTH(@A1) - CAST(@A2 AS INT UNSIGNED) + 1) ELSE 1 END);"); |
| else |
| strcpy(buf, "SUBSTRING(@A1 FROM 1 FOR @A2);"); // LEFT() |
| |
| } |
| break; |
| |
| case ITM_RPAD: |
| { |
| Int32 padStringLength = 0; |
| Int32 rpadLength = -3; // -2, -1 and 0 have a distinct meaning, see |
| // comment near function definition. |
| |
| if (child(2)) |
| { |
| child(2) = child(2)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| padStringLength = getPadStringLength(child(2)->castToItemExpr(), bindWA); |
| if (bindWA->errStatus()) return this; |
| } |
| |
| if (child(1)) |
| { |
| rpadLength = getPadLength(child(1)->castToItemExpr(), bindWA); |
| if (bindWA->errStatus()) return this; |
| } |
| |
| ItemExpr * tempBoundTree = |
| child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| CharInfo::CharSet cs = CharInfo::UnknownCharSet; |
| const NAType& type = tempBoundTree->getValueId().getType(); |
| if ( type.getTypeQualifier() == NA_CHARACTER_TYPE ) |
| cs = ((CharType&)type).getCharSet(); |
| |
| NABoolean padWithSpace = FALSE; |
| |
| // If the character set is ISO88591, ISO mapping is checked |
| // to get the correct character set in the current configuration |
| if (cs == CharInfo::ISO88591) |
| { |
| CharInfo::CharSet isoMappingCS = (CharInfo::CharSet)SqlParser_ISO_MAPPING; |
| padWithSpace = isPadWithSpace(child(2),isoMappingCS); |
| } |
| else |
| padWithSpace = isPadWithSpace(child(2),cs); |
| |
| if ((rpadLength > 0) && padWithSpace) |
| { |
| // use CAST to implement RPAD of blank space |
| // remember to turn off string trunc. warnings. |
| if (cs == CharInfo::ISO88591) |
| sprintf(buf, "CAST(@A1 AS CHAR(%d) CHARACTER SET ISO88591) ;", rpadLength); |
| else if (cs == CharInfo::UTF8) |
| sprintf(buf, "CAST(@A1 AS CHAR(%d BYTES) CHARACTER SET UTF8) ;", rpadLength); |
| else if (cs == CharInfo::SJIS /* && encodingCharSet == CharInfo::SJIS */) |
| sprintf(buf, "CAST(@A1 AS CHAR(%d BYTES) CHARACTER SET SJIS) ;", rpadLength); |
| else |
| { |
| sprintf(buf, "CAST(@A1 AS CHAR(%d) CHARACTER SET UCS2) ;", rpadLength); |
| } |
| |
| if ( rpadLength > CmpCommon::getDefaultNumeric(TRAF_MAX_CHARACTER_COL_LENGTH)) |
| { |
| //Note: We claim error occurred in "REPEAT" here just so we get a consistent |
| //error message regardless of whether or not the CAST optimization is used. |
| *CmpCommon::diags() << DgSqlCode(-4129) << DgString0("REPEAT"); |
| *CmpCommon::diags() << DgSqlCode(-4062) << DgString0("RPAD"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| parseTree = parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 1, child(0)); |
| buf[0] = 0; |
| if (parseTree && parseTree->getOperatorType() == ITM_CAST) |
| { |
| ((Cast*) parseTree)->setNoStringTruncationWarnings(TRUE); |
| } |
| } |
| else if (child(2) == NULL) |
| { |
| // pad with spaces |
| // NOTE: The outer SUBSTRING operation would seem to be unnecessary |
| // in some cases, but it ensures the SQL compiler always knows that |
| // the max length of the return string is the user-specified value. |
| |
| strcpy(buf, |
| "SUBSTRING( " |
| "CASE WHEN @A1 IS NULL THEN NULL " |
| "WHEN CHAR_LENGTH(@A1) >= @A2 THEN @A1 " |
| "ELSE @A1 || SUBSTRING( SPACE(@A2, _UNKNOWN_), 1, " |
| " @A2 - CHAR_LENGTH(@A1) ) " |
| "END, 1, @A2 ) ;"); |
| } |
| else if (padStringLength == 1) |
| { |
| // when the third param has a length = 1 then the we do not need to |
| // use the complicated case expression in the else branch a few lines |
| // below. When the padding string length is known to be 1 we can use |
| // a case expression very similar to that used when child(2) is null. |
| strcpy(buf, |
| "SUBSTRING( " |
| "CASE WHEN @A1 IS NULL THEN NULL " |
| "WHEN CHAR_LENGTH(@A1) >= @A2 THEN @A1 " |
| "ELSE @A1 || REPEAT(@A3, @A2) " |
| "END, 1, @A2 ) ;"); |
| |
| } |
| else |
| { |
| // pad with the third param. |
| |
| Int32 rpadLength = getPadLength(child(1)->castToItemExpr(), bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // If result size needed is less than some threshold, use fast path |
| if ((rpadLength > 0) && (rpadLength <= 1024)) { |
| strcpy(buf, |
| "SUBSTRING( " |
| "CASE WHEN @A1 IS NULL OR @A3 IS NULL THEN NULL " |
| "WHEN CHAR_LENGTH(@A1) >= @A2 THEN @A1 " |
| "ELSE @A1 || REPEAT(@A3, @A2) " |
| "END, 1, @A2 );"); |
| } |
| else |
| { |
| // NOTES: |
| // (1) The outer SUBSTRING operation would seem to be unnecessary |
| // in some cases, but it ensures the SQL compiler always knows |
| // that the maximum length of the return string is the |
| // user-specified value (if the user specified a constant.) |
| // (2) The 2nd WHEN prevents division by 0 later on and also |
| // ensures we catch overly large count (@A2) values at compile |
| // time if possible ... by sending @A2 directly to REPEAT |
| // without doing any CASTing or arithmetic on it (because |
| // doing those causes the value of @A2 to be resolved at |
| // runtime instead of compile time.) |
| // (3) The 3rd WHEN prevents a negative REPEAT count and is |
| // also required by definition of this padding function. |
| |
| strcpy(buf, |
| "SUBSTRING( " |
| "CASE WHEN @A1 IS NULL OR @A3 IS NULL THEN NULL " |
| "WHEN CHAR_LENGTH(@A3) <= 1 THEN @A1 || REPEAT(@A3, @A2) " |
| "WHEN CHAR_LENGTH(@A1) >= @A2 THEN @A1 " |
| "ELSE @A1 || REPEAT(@A3, CAST(( " |
| "@A2 - CHAR_LENGTH(@A1))/CHAR_LENGTH(@A3) + 1 AS INT)) " |
| "END, 1, @A2 );"); |
| |
| if (rpadLength > -1) |
| { |
| parseTree = parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 3, child(0), child(1), child(2)); |
| buf[0] = 0; |
| |
| // The max length of repeat is "length of rpad" * "padding string |
| // length" * "bytes per char". The "length of rpad" is stored, |
| // and when the type is synthesized, the length will be multiplied |
| // by the nominal size of the pattern string. Also note, both the |
| // pattern string and the column must be the same type - an error |
| // would occur otherwise at compile-time. |
| |
| Int32 maxRepeatLength = rpadLength; |
| |
| if (parseTree && parseTree->getOperatorType() == ITM_SUBSTR) |
| { |
| NAList<Lng32> childNumList(CmpCommon::statementHeap(),6); |
| NAList<OperatorTypeEnum> |
| opTypeList(CmpCommon::statementHeap(),6); |
| |
| childNumList.insertAt(0,0); |
| opTypeList.insertAt(0,ITM_CASE); |
| |
| childNumList.insertAt(1,0); |
| opTypeList.insertAt(1,ITM_IF_THEN_ELSE); |
| |
| childNumList.insertAt(2,2); |
| opTypeList.insertAt(2,ITM_IF_THEN_ELSE); |
| |
| childNumList.insertAt(3,2); |
| opTypeList.insertAt(3,ITM_IF_THEN_ELSE); |
| |
| childNumList.insertAt(4,2); opTypeList.insertAt(4,ITM_CONCAT); |
| childNumList.insertAt(5,1); opTypeList.insertAt(5,ITM_REPEAT); |
| |
| Repeat* repNode = |
| (Repeat *) parseTree->getParticularItemExprFromTree( |
| childNumList, opTypeList) ; |
| if (repNode && (maxRepeatLength > 1)) |
| repNode->setMaxLength(maxRepeatLength); |
| |
| } // end of block that is looking for the problematic Repeat node |
| else |
| { |
| bindWA->setErrStatus(); // couldn't parse rpad replacement str |
| return this; |
| } |
| } // rpad has a fixed max length |
| } // padding string of rpad has more than 1 character |
| } |
| } |
| break; |
| |
| case ITM_SIGN: |
| { |
| ItemExpr * tempBoundTree = |
| child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // |
| // Type cast any param. |
| // |
| SQLInt nType(FALSE); |
| ValueId vid = tempBoundTree->castToItemExpr()->getValueId(); |
| vid.coerceType(nType, NA_NUMERIC_TYPE); |
| |
| const NAType &typ1 = vid.getType(); |
| if (typ1.getTypeQualifier() != NA_NUMERIC_TYPE) |
| { |
| // 4045 Operand must be numeric. |
| *CmpCommon::diags() << DgSqlCode(-4045) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // find the nullability of child |
| NABoolean childIsNullable = FALSE; |
| if (tempBoundTree->getValueId().getType().supportsSQLnull()) |
| { |
| childIsNullable = TRUE; |
| } |
| |
| if (childIsNullable) |
| strcpy(buf, "CASE WHEN @A1 IS NULL THEN NULL ELSE "); |
| else |
| strcpy(buf, ""); |
| |
| strcat(buf, "CASE WHEN @A1 < 0 THEN -1 WHEN @A1 = 0 THEN 0 ELSE 1 END"); |
| |
| if (childIsNullable) |
| strcat(buf, " END;"); |
| else |
| strcat(buf, ";"); |
| } |
| break; |
| |
| case ITM_SPACE: |
| { |
| strcpy(buf, "REPEAT ( @A1 , @A2 );"); |
| } |
| break; |
| |
| case ITM_USER: |
| case ITM_AUTHNAME: |
| case ITM_AUTHTYPE: |
| { |
| ItemExpr * tempBoundTree = |
| child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (tempBoundTree->getValueId().getType().getTypeQualifier() != |
| NA_NUMERIC_TYPE) |
| { |
| strcpy(buf, |
| "cast(substring(@A1, 1, position(',' in @A1)-1) as smallint) * 256 + cast(substring(@A1, position(',' in @A1)+1, char_length(@A1) - position(',' in @A1)) as smallint)"); |
| } |
| else |
| { |
| buf[0] = 0; |
| parseTree = child(0); |
| } |
| |
| } |
| break; |
| |
| case ITM_WEEK: |
| { |
| // Essentially we need to process the following case statement to |
| // achieve WEEK functionality. However, the case statement is split |
| // into pieces to access the divide operator node. |
| // "CAST((DAYOFYEAR(@A1) - 1 + DAYOFWEEK(FIRSTDAYOFYEAR(@A1)) - 1)/7 |
| // AS INT) + 1;" |
| strcpy(buf, "(DAYOFYEAR(@A1) - 1 + DAYOFWEEK(FIRSTDAYOFYEAR(@A1)) - 1)/7"); |
| |
| ItemExpr *tempExpr = NULL; |
| if (strlen(buf) > 0) |
| { |
| tempExpr = parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 1, child(0)); |
| if (! tempExpr) |
| { |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Disable rounding for DATE TIME type of operations. |
| if (tempExpr->getOperatorType() == ITM_DIVIDE) |
| { |
| ((BiArith*)tempExpr)->setIgnoreSpecialRounding(); |
| } |
| |
| strcpy(buf, "CAST(@A1 AS INT) + 1;"); |
| |
| parseTree = parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 1, tempExpr); |
| |
| parseTree->setOrigOpType(getOperatorType()); |
| |
| buf[0] = 0; |
| } |
| } |
| break; |
| |
| case ITM_NULLIF: |
| { |
| strcpy(buf, "CASE WHEN @A1 = @A2 THEN NULL ELSE @A1 END;"); |
| } |
| break; |
| |
| case ITM_ZEROIFNULL: |
| { |
| // find the nullability of child |
| ItemExpr * tempBoundTree = |
| child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| // |
| // Type cast any param. |
| // |
| SQLInt nType(FALSE); |
| ValueId vid = tempBoundTree->castToItemExpr()->getValueId(); |
| vid.coerceType(nType, NA_NUMERIC_TYPE); |
| |
| if (tempBoundTree->getValueId().getType().getTypeQualifier() != NA_NUMERIC_TYPE) |
| { |
| // 4045 must be numeric. |
| *CmpCommon::diags() << DgSqlCode(-4045) << DgString0(getTextUpper()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if ((tempBoundTree->getValueId().getType().supportsSQLnull()) || |
| (tempBoundTree->isASubquery())) |
| { |
| strcpy(buf, "CASE WHEN @A1 IS NULL then 0 ELSE @A1 END;"); |
| } |
| else |
| { |
| // if child is not nullable, then no need to do zeroifnull. |
| // Point to child. |
| parseTree = child(0)->castToItemExpr(); |
| } |
| } |
| break; |
| case ITM_SUBSTR: |
| { |
| // |
| // Perhaps someone, someday, will figure out how to make |
| // sqlparser.y invoke the Substring() constructor and pass |
| // it a constant of 1 for the start argument...without |
| // causing crashes of mxcmp. Until then, we will |
| // translate it to old SUBSTRING syntax here. |
| // |
| strcpy(buf, "SUBSTRING(@A1 FROM 1 FOR @A2);"); |
| } |
| break; |
| |
| case ITM_ROUND: |
| { |
| //Do initial argument validations. |
| // First operand is the value to be rounded. Cannot be float or |
| // bignums. |
| // |
| // Second operand is the number of digits after decimal point to be |
| // rounded to. |
| // Third operand is the rounding mode. Must be a constant. |
| // |
| |
| NABoolean useOptimizedRound = TRUE; |
| |
| //Processing of first operand. |
| ItemExpr * tempBoundTree = child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) return this; |
| |
| if (tempBoundTree->getValueId().getType().getTypeQualifier() == NA_UNKNOWN_TYPE) |
| child(0)->getValueId().coerceType(NA_NUMERIC_TYPE); |
| |
| if (tempBoundTree->getValueId().getType().getTypeQualifier() != NA_NUMERIC_TYPE) |
| { |
| // 4059 The first operand must be numeric. |
| *CmpCommon::diags() << DgSqlCode(-4059) << DgString0("ROUND"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| NumericType &type_op1 = |
| (NumericType&)tempBoundTree->getValueId().getType(); |
| if ((NOT type_op1.isExact()) || |
| (type_op1.isComplexType())) |
| { |
| useOptimizedRound = FALSE; |
| // Only supporting ROUND of exact numerics with max precision |
| // of MAX_NUMERIC_PRECISION (no floats or bignums). |
| // |
| // 4059 The first operand must be numeric. |
| // *CmpCommon::diags() << DgSqlCode(-4070) << DgString0("ROUND"); |
| //bindWA->setErrStatus(); |
| //return this; |
| } |
| |
| //second operand. |
| Lng32 roundTo = -1; |
| if (child(1) == NULL) |
| { |
| // round all digits after the decimal point |
| roundTo = 0; |
| } |
| else |
| { |
| //argument validation for second argument. |
| ItemExpr *secondOpExpr = child(1)->castToItemExpr(); |
| secondOpExpr->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (secondOpExpr->getValueId().getType().getTypeQualifier() != NA_NUMERIC_TYPE) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4052) << DgString0("ROUND"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| NumericType &tempType = (NumericType&)secondOpExpr->getValueId().getType(); |
| if(! (tempType.isExact() && tempType.getScale() <=0 )) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4047) << DgString0("ROUND"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (secondOpExpr->getOperatorType() == ITM_CONSTANT) |
| { |
| roundTo = |
| (Lng32)((ConstValue*)secondOpExpr)->getExactNumericValue(); |
| |
| if (roundTo > MAX_NUMERIC_PRECISION) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4052) << DgString0("ROUND"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| else |
| { |
| useOptimizedRound = FALSE; |
| // *CmpCommon::diags() << DgSqlCode(-4052) << DgString0("ROUND"); |
| // bindWA->setErrStatus(); |
| // return this; |
| } |
| } |
| |
| short roundingMode = 1; |
| if( child(2) != NULL ) |
| { |
| //argument validation for second argument. |
| ItemExpr *thirdOpExpr = child(2)->castToItemExpr(); |
| thirdOpExpr->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if ((thirdOpExpr->getOperatorType() != ITM_CONSTANT) || |
| (thirdOpExpr->getValueId().getType().getTypeQualifier() != NA_NUMERIC_TYPE) || |
| (useOptimizedRound == FALSE)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4053) << DgString0("ROUND"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| NumericType &tempType = (NumericType&)thirdOpExpr->getValueId().getType(); |
| if(! (tempType.isExact() && tempType.getScale() <=0 )) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4047) << DgString0("ROUND"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| roundingMode = |
| (short)((ConstValue*)thirdOpExpr)->getExactNumericValue(); |
| if ((roundingMode < 0) || |
| (roundingMode > 2)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4053) << DgString0("ROUND"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| if (useOptimizedRound) |
| { |
| // divide the value by 10 raised to the power of the number of |
| // digits to be rounded off. |
| // If roundTo(second) param is a const, create a literal instead |
| // of an expression. |
| ItemExpr * divExpr = NULL; |
| Int64 denom = 0; |
| buf[0] = 0; |
| if ((roundTo >= 0) && |
| (type_op1.getScale() > roundTo)) |
| { |
| denom = 1; |
| Int32 i = (type_op1.getScale() - roundTo); |
| while (i > 0) |
| { |
| denom = denom * 10; |
| i--; |
| } |
| |
| str_sprintf(buf, "@A1 / %ld", denom); |
| } |
| |
| if (strlen(buf) > 0) |
| { |
| divExpr = parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 2, child(0), child(1)); |
| if (! divExpr) |
| { |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // indicate that this division need to do rounding. |
| if (divExpr->getOperatorType() == ITM_DIVIDE) |
| { |
| ((BiArith*)divExpr)->setRoundingMode(roundingMode); |
| ((BiArith*)divExpr)->setDivToDownscale(TRUE); |
| } |
| |
| // multiply by 10 ** (number of digits rounded off) to |
| // get back to the original scale. |
| str_sprintf(buf, "cast(@A1 * %ld as numeric(%d,%d))", |
| denom, MAX_NUMERIC_PRECISION, MAXOF(type_op1.getScale(), roundTo)); |
| parseTree = parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 2, divExpr, child(1)); |
| if (! parseTree) |
| { |
| bindWA->setErrStatus(); |
| return this; |
| } |
| buf[0] = 0; |
| } |
| else |
| parseTree = child(0)->castToItemExpr(); |
| } |
| else |
| { |
| //The round function is implemented using other existing math functions. |
| //Typically ROUND(expr,num) will involve the following:Ex: ROUND(34.58,1) |
| //1. Move the decimal point num+1 places by multiplying by num+1. |
| // Example: 34.58 * power(10,num+1) = 3458.00 |
| //2. MOD(result,10)=x determines if round-up is required. In the case of |
| // inexact numbers, round-down or round-even is also performed. |
| // If x < 5 then no change. if x > 5 then round up. |
| // if x = 5 then round even. |
| // Example:MOD(3458.00, 10) = 8 means round up. |
| //3. Based on roundup,round down, round even, round the number at numth |
| // position. |
| // Example: floor(34.58 * power(10, num)) = 345.00 + 1 = 346.00 |
| //4. Then move the decimal point by multiplying or dividing by |
| // multiples of 10. |
| // Example: 346.00 * power(10, -1) = 34.60. |
| //5. In the case of inexact numbers, round even is performed if required. |
| |
| //ROUND processing: |
| if( type_op1.isExact() )//MOD does not support inexact expressions. |
| { |
| if(child(1) == NULL)//In ROUND(expr,num), num is optional, defaults to 0. |
| { |
| if(type_op1.getScale() <=0)//no need to perform round in this case. |
| parseTree = child(0)->castToItemExpr(); |
| else |
| { |
| if(type_op1.isBigNum()) |
| strcpy(buf,"case when SIGN(@A1) < 0 then case when (cast(@A1 * 10 as numeric(128,0))-(10 * cast(cast((@A1 * 10)as numeric(128,0))/10 as numeric(128,0)))) > -5 then cast(@A1 as numeric(128,0)) when (cast(@A1 * 10 as numeric(128,0))-(10 * cast(cast((@A1 * 10)as numeric(128,0))/10 as numeric(128,0)))) <=-5 then cast(@A1 as numeric(128,0)) - 1 END else case when (cast(@A1 * 10 as numeric(128,0))-(10 * cast(cast((@A1 * 10)as numeric(128,0))/10 as numeric(128,0)))) < 5 then cast(@A1 as numeric(128,0)) when (cast(@A1 * 10 as numeric(128,0))-(10 * cast(cast((@A1 * 10)as numeric(128,0))/10 as numeric(128,0)))) >=5 then cast(@A1 as numeric(128,0)) +SIGN(@A1) END END"); |
| else |
| strcpy(buf, "case when MOD(cast((abs(@A1) * 10) as largeint), 10) < 5 then cast(@A1 as largeint) when MOD(cast((abs(@A1) * 10) as largeint), 10) >=5 then cast(@A1 as largeint) +SIGN(@A1) END"); |
| } |
| } |
| else{ |
| if(type_op1.getScale() <= 0){ |
| if(type_op1.isBigNum()) |
| strcpy(buf, "case SIGN(@A1) when -1 then case SIGN(@A2+1)when 1 then @A1 ELSE case when (cast(@A1/cast(power(10, abs(@A2+1)) as numeric(128,0)) as numeric(128,0))-(10 * cast(cast(@A1/cast(power(10, abs(@A2+1)) as numeric(128,0)) as numeric(128,0))/10 as numeric(128,0)))) > -5 then cast((@A1/cast(power(10,abs(@A2)) as numeric(128,0))) as numeric(128,0))* cast(power(10,abs(@A2)) as numeric(128,0)) ELSE (cast((@A1/cast(power(10,abs(@A2)) as numeric(128,0))) as numeric(128,0)) + SIGN(@A1)) * cast(power(10,abs(@A2)) as numeric(128,0)) END END ELSE case SIGN(@A2+1)when 1 then @A1 ELSE case when (cast(@A1/cast(power(10, abs(@A2+1)) as numeric(128,0)) as numeric(128,0))-(10 * cast(cast(@A1/cast(power(10, abs(@A2+1)) as numeric(128,0)) as numeric(128,0))/10 as numeric(128,0)))) < 5 then cast((@A1/cast(power(10,abs(@A2)) as numeric(128,0))) as numeric(128,0))* cast(power(10,abs(@A2)) as numeric(128,0)) ELSE (cast((@A1/cast(power(10,abs(@A2)) as numeric(128,0))) as numeric(128,0)) + SIGN(@A1)) * cast(power(10,abs(@A2)) as numeric(128,0)) END END END"); |
| else |
| strcpy(buf, "case SIGN(@A2+1)when 1 then case when MOD(cast(abs(@A1) * cast(power(10, @A2+1) as largeint) as largeint), 10) < 5 then cast((@A1 * cast(power(10,@A2) as largeint)) as largeint) / cast(power(10,abs(@A2)) as largeint) when MOD(cast(abs(@A1) * cast(power(10, @A2+1) as largeint) as largeint), 10) >=5 then (cast((@A1 * cast(power(10,@A2) as largeint)) as largeint) +SIGN(@A1)) / cast(power(10,@A2) as largeint) END ELSE case when MOD(cast(abs(@A1) /cast(power(10, abs(@A2+1)) as largeint) as largeint), 10) < 5 then cast((@A1/cast(power(10,abs(@A2)) as largeint)) as largeint)* cast(power(10,abs(@A2)) as largeint) when MOD(cast(abs(@A1) /cast(power(10, abs(@A2+1)) as largeint) as largeint), 10) >=5 then (cast((@A1/cast(power(10,abs(@A2)) as largeint)) as largeint)+ SIGN(@A1)) * cast(power(10,abs(@A2)) as largeint) END END"); |
| } |
| else{ |
| NumericType *typeTemp; |
| if(type_op1.isBigNum()) |
| { |
| // In the below case statement, cast the result to |
| // resultPrecision + 1. This is done for cases that |
| // result in consuming additional precision. |
| // For example, round(99.00, -2) = 100.00 |
| const Int16 DisAmbiguate = 0; |
| typeTemp = new(bindWA->wHeap()) SQLNumeric(bindWA->wHeap(), type_op1.isSigned(), |
| MINOF(type_op1.getPrecision()+1,128), |
| type_op1.getScale(), |
| DisAmbiguate // added for 64bit proj. |
| ); |
| |
| NAString nstr = ""; |
| typeTemp->getMyTypeAsText(&nstr, FALSE); |
| char *text = convertNAString(nstr, bindWA->wHeap()); |
| sprintf(buf, "cast(case SIGN(@A1) when -1 then case SIGN(@A2+1) when 1 then case when (cast(@A1 * cast(power(10, @A2+1) as numeric(128,0)) as numeric(128,0))-(10 * cast(cast(@A1 * cast(power(10, @A2+1) as numeric(128,0))as numeric(128,0))/10 as numeric(128,0)))) > -5 then cast((@A1 * cast(power(10,@A2) as numeric(128,0))) as numeric(128,0)) / cast(power(10,abs(@A2)) as numeric(128,0)) ELSE (cast((@A1 * cast(power(10,@A2) as numeric(128,0))) as numeric(128,0)) + SIGN(@A1) ) / cast(power(10,@A2) as numeric(128,0)) END ELSE case when (cast(@A1/cast(power(10, abs(@A2+1)) as numeric(128,0)) as numeric(128,0))-(10 * cast(cast(@A1/cast(power(10, abs(@A2+1)) as numeric(128,0)) as numeric(128,0))/10 as numeric(128,0)))) > -5 then cast((@A1/cast(power(10,abs(@A2)) as numeric(128,0))) as numeric(128,0))* cast(power(10,abs(@A2)) as numeric(128,0)) ELSE (cast((@A1/cast(power(10,abs(@A2)) as numeric(128,0))) as numeric(128,0))+ SIGN(@A1)) * cast(power(10,abs(@A2)) as numeric(128,0)) END END ELSE case SIGN(@A2+1) when 1 then case when (cast(@A1 * cast(power(10, @A2+1) as numeric(128,0)) as numeric(128,0))-(10 * cast(cast(@A1 * cast(power(10, @A2+1) as numeric(128,0))as numeric(128,0))/10 as numeric(128,0)))) < 5 then cast((@A1 * cast(power(10,@A2) as numeric(128,0))) as numeric(128,0)) / cast(power(10,abs(@A2)) as numeric(128,0)) ELSE (cast((@A1 * cast(power(10,@A2) as numeric(128,0))) as numeric(128,0)) + SIGN(@A1) ) / cast(power(10,@A2) as numeric(128,0)) END ELSE case when (cast(@A1/cast(power(10, abs(@A2+1)) as numeric(128,0)) as numeric(128,0))-(10 * cast(cast(@A1/cast(power(10, abs(@A2+1)) as numeric(128,0)) as numeric(128,0))/10 as numeric(128,0)))) < 5 then cast((@A1/cast(power(10,abs(@A2)) as numeric(128,0))) as numeric(128,0))* cast(power(10,abs(@A2)) as numeric(128,0)) ELSE (cast((@A1/cast(power(10,abs(@A2)) as numeric(128,0))) as numeric(128,0))+ SIGN(@A1)) * cast(power(10,abs(@A2)) as numeric(128,0)) END END END as %s)", text); |
| } |
| else |
| { |
| if(type_op1.getScale() <=18) |
| { |
| // In the below case statement, cast the result to |
| // resultPrecision + 1. This is done for cases that |
| // result in consuming additional precision. |
| // For example, round(99.00, -2) = 100.00 |
| typeTemp = (NumericType*)type_op1.newCopy(bindWA->wHeap()); |
| typeTemp->setPrecision(MINOF(type_op1.getPrecision()+1,18)); |
| typeTemp->setScale(type_op1.getScale()); |
| |
| //precision is 10 or bigger, then UNSIGNED cannot be used for NUMERIC type. |
| if((typeTemp->getPrecision() >= 10 ) && typeTemp->isUnsigned()) |
| typeTemp->makeSigned(); |
| |
| NAString nstr = ""; |
| typeTemp->getMyTypeAsText(&nstr, FALSE); |
| char *text = convertNAString(nstr, bindWA->wHeap()); |
| sprintf(buf, "cast(case SIGN(@A2+1)when 1 then case when MOD(cast(abs(@A1) * cast(power(10, @A2+1) as largeint) as largeint), 10) < 5 then cast((@A1 * cast(power(10,@A2) as largeint)) as largeint) / cast(power(10,abs(@A2)) as largeint) when MOD(cast(abs(@A1) * cast(power(10, @A2+1) as largeint) as largeint), 10) >=5 then (cast((@A1 * cast(power(10,@A2) as largeint)) as largeint) +SIGN(@A1)) / cast(power(10,@A2) as largeint) END ELSE case when MOD(cast(abs(@A1) /cast(power(10, abs(@A2+1)) as largeint) as largeint), 10) < 5 then cast((@A1/cast(power(10,abs(@A2)) as largeint)) as largeint)* cast(power(10,abs(@A2)) as largeint) when MOD(cast(abs(@A1) /cast(power(10, abs(@A2+1)) as largeint) as largeint), 10) >=5 then (cast((@A1/cast(power(10,abs(@A2)) as largeint)) as largeint)+ SIGN(@A1)) * cast(power(10,abs(@A2)) as largeint) END END as %s)", text); |
| } |
| else |
| { |
| //If the scale of type_op1 is greater than 18, |
| //skip the casting of final result expression. |
| sprintf(buf, "case SIGN(@A2+1)when 1 then case when MOD(cast(abs(@A1) * cast(power(10, @A2+1) as largeint) as largeint), 10) < 5 then cast((@A1 * cast(power(10,@A2) as largeint)) as largeint) / cast(power(10,abs(@A2)) as largeint) when MOD(cast(abs(@A1) * cast(power(10, @A2+1) as largeint) as largeint), 10) >=5 then (cast((@A1 * cast(power(10,@A2) as largeint)) as largeint) +SIGN(@A1)) / cast(power(10,@A2) as largeint) END ELSE case when MOD(cast(abs(@A1) /cast(power(10, abs(@A2+1)) as largeint) as largeint), 10) < 5 then cast((@A1/cast(power(10,abs(@A2)) as largeint)) as largeint)* cast(power(10,abs(@A2)) as largeint) when MOD(cast(abs(@A1) /cast(power(10, abs(@A2+1)) as largeint) as largeint), 10) >=5 then (cast((@A1/cast(power(10,abs(@A2)) as largeint)) as largeint)+ SIGN(@A1)) * cast(power(10,abs(@A2)) as largeint) END END"); |
| } |
| } |
| } |
| } |
| // Once we are done bignum round processing above, we need to |
| // make sure not to introduce realbignum if the type_op1 is not |
| // real BigNum. Using CAST(x as numeric(128,0)) will automatically |
| // make the operand realbignum. |
| if(type_op1.isBigNum() && |
| (!((SQLBigNum &)type_op1).isARealBigNum())) |
| { |
| resetRealBigNum = TRUE; |
| } |
| } |
| else { |
| ItemExpr * v = |
| new(bindWA->wHeap()) MathFunc(ITM_ROUND, child(0), child(1)); |
| |
| boundTree = v->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| } |
| } |
| } |
| |
| break; |
| case ITM_CURRNT_USER: |
| { |
| strcpy(buf, "TRANSLATE(CURRNT_USR_INTN USING ISO88591ToUCS2);"); |
| } |
| break; |
| case ITM_SESSN_USER: |
| { |
| strcpy(buf, "TRANSLATE(SESSN_USR_INTN USING ISO88591ToUCS2);"); |
| } |
| break; |
| case ITM_CONVERTTOHX: |
| { |
| strcpy(buf, "TRANSLATE(CONVERTTOHX_INTN(@A1) USING ISO88591ToUCS2);"); |
| } |
| break; |
| |
| case ITM_SCALE_TRUNC: |
| { |
| ItemExpr *tempBoundTree = child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (tempBoundTree->getValueId().getType().getTypeQualifier() == NA_NUMERIC_TYPE) |
| { |
| NumericType &type_op1 = |
| (NumericType&)tempBoundTree->getValueId().getType(); |
| |
| if ((NOT type_op1.isExact()) || |
| (type_op1.isComplexType())) |
| { |
| // 4059 The first operand must be numeric. |
| *CmpCommon::diags() << DgSqlCode(-4070) << DgString0("ROUND"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| Lng32 truncVal = |
| (Lng32)((ConstValue*)child(1)->castToItemExpr())->getExactNumericValue(); |
| |
| str_sprintf(buf, "CAST(@A1 as NUMERIC(%d, %d));", |
| type_op1.getPrecision(), truncVal); |
| } |
| else if (tempBoundTree->getValueId().getType().getTypeQualifier() == NA_DATETIME_TYPE) |
| { |
| // for now, just return the child. |
| boundTree = tempBoundTree; |
| } |
| else |
| { |
| // 4059 The first operand must be numeric. |
| *CmpCommon::diags() << DgSqlCode(-4059) << DgString0("TRUNC"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| break; |
| |
| case ITM_TO_NUMBER: |
| { |
| ItemExpr *tempBoundTree = child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| if (CmpCommon::getDefault(MODE_SPECIAL_4) == DF_OFF) |
| { |
| if (tempBoundTree->getValueId().getType().getTypeQualifier() != NA_CHARACTER_TYPE) |
| { |
| // 4043 The first operand must be numeric. |
| *CmpCommon::diags() << DgSqlCode(-4043) << DgString0("TO_NUMBER"); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| NAType &type_op1 = |
| (NAType&)tempBoundTree->getValueId().getType(); |
| |
| str_sprintf(buf, "CAST(@A1 as NUMERIC(%d));", |
| type_op1.getNominalSize()); |
| } |
| break; |
| |
| case ITM_TO_TIMESTAMP: |
| { |
| ItemExpr *tempBoundTree = child(0)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| str_sprintf(buf, "CAST(@A1 as TIMESTAMP(6));"); |
| } |
| break; |
| |
| case ITM_CURRENT_TIMESTAMP_UTC: |
| { |
| if (bindWA->currentCmpContext()->gmtDiff() > 0) |
| str_sprintf(buf, "CURRENT_TIMESTAMP + INTERVAL '%4d' MINUTE(4);", |
| bindWA->currentCmpContext()->gmtDiff()); |
| else if (bindWA->currentCmpContext()->gmtDiff() < 0) |
| str_sprintf(buf, "CURRENT_TIMESTAMP - INTERVAL '%4d' MINUTE(4);", |
| -bindWA->currentCmpContext()->gmtDiff()); |
| else |
| strcpy(buf, "CURRENT_TIMESTAMP"); |
| } |
| break; |
| |
| case ITM_CURRENT_TIME_UTC: |
| { |
| str_sprintf(buf, "CAST(CURRENT_TIMESTAMP_UTC AS TIME);"); |
| } |
| break; |
| |
| case ITM_GROUPING_ID: |
| { |
| *CmpCommon::diags() << DgSqlCode(-3242) |
| << DgString0("GROUPING_ID function must be specified in the select list of a GROUP BY ROLLUP statement."); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| break; |
| |
| default: |
| { |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| if (CURRENTSTMT->getItemExprOrigOpTypeCounter() == 0) |
| CURRENTSTMT->setItemExprOrigOpTypeBeingBound(getOperatorType()); |
| |
| (CURRENTSTMT->getItemExprOrigOpTypeCounter())++; |
| |
| if (strlen(buf) > 0) |
| { |
| parseTree = parser.getItemExprTree(buf, strlen(buf), BINDITEMEXPR_STMTCHARSET, 5, child(0), child(1), child(2), child(3), child(4)); |
| } |
| |
| if (parseTree) { |
| |
| switch (getOperatorType()) |
| { |
| case ITM_UNICODE_CODE_VALUE: |
| case ITM_NCHAR_MP_CODE_VALUE: |
| { |
| parseTree = new(bindWA->wHeap()) CodeVal(getOperatorType(), parseTree); |
| } |
| break; |
| |
| case ITM_AUTHNAME: |
| case ITM_AUTHTYPE: |
| case ITM_USER: |
| { |
| parseTree = new(bindWA->wHeap()) MonadicUSERFunction(parseTree,getOperatorType()); |
| } |
| break; |
| case ITM_ROUND: |
| { |
| if(resetRealBigNum) |
| resetRealBigNumFlag(parseTree); |
| } |
| break; |
| |
| } // switch |
| |
| boundTree = parseTree->bindNode(bindWA); |
| if (bindWA->errStatus()) boundTree = NULL; |
| } |
| |
| //origOpTypeCounter()--; |
| (CURRENTSTMT->getItemExprOrigOpTypeCounter())--; |
| |
| // once out of the scope of any operator set the type to default |
| //if (origOpTypeCounter() == 0) |
| // origOpTypeBeingBound() = NO_OPERATOR_TYPE; |
| |
| if (CURRENTSTMT->getItemExprOrigOpTypeCounter() == 0) |
| CURRENTSTMT->setItemExprOrigOpTypeBeingBound(NO_OPERATOR_TYPE); |
| |
| // NOTE: Don't call unparse() below if we don't need to. It may blow up |
| // since getSubquery() could return NULL if the subquery was discarded. |
| // |
| if ((boundTree == NULL) && (CURRENTSTMT->getItemExprOrigOpTypeCounter() == 0) ) { |
| NAString orig(bindWA->wHeap()); |
| |
| // For functions whose arity is more than 1, am removing |
| // the use of unparse since it does not do the right thing. |
| //if (parseTree) parseTree->unparse(orig,DEFAULT_PHASE,USER_FORMAT_DELUXE); |
| if (getArity() > 1) |
| orig = getTextUpper(); |
| else |
| unparse(orig, DEFAULT_PHASE, USER_FORMAT_DELUXE); |
| |
| // 4062 The preceding error actually occurred in function $0~String0. |
| *CmpCommon::diags() << DgSqlCode(-4062) << DgString0(orig); |
| bindWA->setErrStatus(); |
| } |
| |
| return boundTree; |
| } |
| |
| ItemExpr *ZZZBinderFunction::tryToUndoBindTransformation(ItemExpr *expr) |
| { |
| // Given as input an expression produced by ZZZBinderFunction::bindNode(), |
| // return an equivalent ZZZBinderFunction ItemExpr or NULL for cases |
| // that are not yet supported. This is used for a) unparsing such |
| // functions, and b) validating some item expressions. |
| |
| ItemExpr *result = NULL; |
| ItemExpr *op1 = expr; |
| ItemExpr *op2 = NULL; |
| |
| switch (expr->origOpType()) |
| { |
| case ITM_DATE_TRUNC_SECOND: |
| { |
| // Form is date_trunc(date_trunc('hour', <arg>) + ...) + ... |
| // ==> Remove the top + and fall through to next case |
| if (op1 && |
| op1->getOperatorType() == ITM_PLUS) |
| op1 = op1->child(0); |
| else |
| op1 = NULL; |
| } |
| // fall through to next case |
| |
| case ITM_DATE_TRUNC_MINUTE: |
| { |
| // Form is date_trunc('hour', <arg>) + ... |
| // ==> Remove the top + and fall through to next case |
| if (op1 && |
| op1->getOperatorType() == ITM_PLUS) |
| op1 = op1->child(0); |
| else |
| op1 = NULL; |
| } |
| // fall through to next case |
| |
| case ITM_DATE_TRUNC_MONTH: |
| case ITM_DATE_TRUNC_HOUR: |
| case ITM_DATE_TRUNC_CENTURY: |
| case ITM_DATE_TRUNC_DECADE: |
| { |
| // form is cast(cast(<arg>)) +/- ... |
| // ==> Remove the top + or - and fall through to next case |
| if (op1 && |
| (op1->getOperatorType() == ITM_PLUS || |
| op1->getOperatorType() == ITM_MINUS)) |
| op1 = op1->child(0); |
| else |
| op1 = NULL; |
| } |
| // fall through to next case |
| |
| case ITM_DATE_TRUNC_YEAR: |
| case ITM_DATE_TRUNC_DAY: |
| { |
| // form is cast(cast(<arg>)) |
| if (op1 && |
| op1->getOperatorType() == ITM_CAST) |
| { |
| op1 = op1->child(0); |
| if (op1->getOperatorType() == ITM_CAST) |
| op1 = op1->child(0); |
| } |
| else |
| op1 = NULL; |
| |
| if (op1) |
| result = new(CmpCommon::statementHeap()) |
| ZZZBinderFunction(expr->origOpType(),op1); |
| } |
| break; |
| |
| case ITM_DATEDIFF_YEAR: |
| case ITM_DATEDIFF_QUARTER: |
| case ITM_DATEDIFF_MONTH: |
| { |
| // different forms (in prefix notation) - we find <arg1> and <arg2> |
| // and ignore the parts shown as ellipsis |
| // year: cast(-(extract(<arg2>), |
| // extract(<arg1>))) |
| // quarter: cast(/(-(+(*(extract(<arg2>), |
| // ...), |
| // ...), |
| // +(*(extract(<arg1>), |
| // ...), |
| // ...)))) |
| // month: cast(-(+(*(extract(<arg2>), |
| // ...), |
| // ...), |
| // +(*(extract(<arg1>), |
| // ...), |
| // ...))) |
| if (op1->getOperatorType() == ITM_CAST) |
| { |
| if (NULL != expr && expr->origOpType() == ITM_DATEDIFF_QUARTER && |
| op1->child(0)->getOperatorType() == ITM_DIVIDE) |
| op1 = op1->child(0)->child(0); // the minus operator |
| // cast / - |
| else if (op1->child(0)->getOperatorType() == ITM_MINUS) |
| op1 = op1->child(0); // the minus operator |
| // cast - |
| |
| if (NULL != expr && expr->origOpType() == ITM_DATEDIFF_YEAR) |
| { |
| if (op1->child(0)->getOperatorType() == ITM_EXTRACT || |
| op1->child(0)->getOperatorType() == ITM_EXTRACT_ODBC) |
| { |
| op2 = op1->child(0)->child(0); |
| // - extract <arg2> |
| op1 = op1->child(1)->child(0); |
| // - extract <arg1> |
| } |
| } |
| else if (op1->child(0)->getOperatorType() == ITM_PLUS && |
| op1->child(0)->child(0)->getOperatorType() == ITM_TIMES && |
| (op1->child(0)->child(0)->child(0)->getOperatorType() == ITM_EXTRACT || |
| op1->child(0)->child(0)->child(0)->getOperatorType() == ITM_EXTRACT_ODBC) && |
| op1->child(1)->getOperatorType() == ITM_PLUS && |
| op1->child(1)->child(0)->getOperatorType() == ITM_TIMES && |
| (op1->child(1)->child(0)->child(0)->getOperatorType() == ITM_EXTRACT || |
| op1->child(1)->child(0)->child(0)->getOperatorType() == ITM_EXTRACT_ODBC)) |
| { |
| op2 = op1->child(0)->child(0)->child(0)->child(0); |
| // - + * extract <arg2> |
| op1 = op1->child(1)->child(0)->child(0)->child(0); |
| // - + * extract <arg1> |
| } |
| else |
| op1 = NULL; |
| } |
| if (op1 && op2) |
| result = new(CmpCommon::statementHeap()) |
| ZZZBinderFunction(expr->origOpType(),op1,op2); |
| } |
| break; |
| |
| case ITM_DATEDIFF_WEEK: |
| { |
| // form: cast(/(-(-(cast(<arg2>),...), |
| // -(cast(<arg1>),...))))) |
| if (op1->getOperatorType() == ITM_CAST && |
| op1->child(0)->getOperatorType() == ITM_DIVIDE && |
| op1->child(0)->child(0)->getOperatorType() == ITM_MINUS) |
| { |
| op1 = op1->child(0)->child(0); |
| // cast / - |
| |
| if (op1->child(0)->getOperatorType() == ITM_MINUS && |
| op1->child(0)->child(0)->getOperatorType() == ITM_CAST && |
| op1->child(1)->getOperatorType() == ITM_MINUS && |
| op1->child(1)->child(0)->getOperatorType() == ITM_CAST) |
| { |
| op2 = op1->child(0)->child(0)->child(0); |
| // - - cast <arg2> |
| op1 = op1->child(1)->child(0)->child(0); |
| // - - cast <arg1> |
| } |
| } |
| else |
| op1 = NULL; |
| if (op1 && op2) |
| result = new(CmpCommon::statementHeap()) |
| ZZZBinderFunction(expr->origOpType(),op1,op2); |
| } |
| break; |
| |
| case ITM_YEARWEEK: |
| case ITM_YEARWEEKD: |
| { |
| // form: cast(+(*(extract_odbc(<arg1>),...),...)) |
| if (op1->getOperatorType() == ITM_CAST && |
| op1->child(0)->getOperatorType() == ITM_PLUS && |
| op1->child(0)->child(0)->getOperatorType() == ITM_TIMES && |
| op1->child(0)->child(0)->child(0)->getOperatorType() == ITM_EXTRACT_ODBC) |
| { |
| op1 = op1->child(0)->child(0)->child(0)->child(0); |
| // cast + * extract <arg1> |
| result = new(CmpCommon::statementHeap()) |
| ZZZBinderFunction(expr->origOpType(), op1); |
| } |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| return result; |
| } |
| |
| // returns true if there is an error |
| bool ZZZBinderFunction::enforceDateOrTimestampDatatype(BindWA * bindWA, CollIndex childIndex, int operand) |
| { |
| // Make sure that the child is of date or timestamp datatype. |
| ItemExpr * tempBoundTree = |
| child(childIndex)->castToItemExpr()->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return true; |
| |
| bool error = (tempBoundTree->getValueId().getType().getTypeQualifier() != |
| NA_DATETIME_TYPE); |
| if (!error) |
| { |
| DatetimeType *dtOper = |
| &(DatetimeType&)tempBoundTree->getValueId().getType(); |
| error = ((dtOper->getPrecision() != SQLDTCODE_TIMESTAMP) && |
| (dtOper->getPrecision() != SQLDTCODE_DATE)); |
| } |
| |
| if (error) |
| { |
| // 4182 Function $0~String0 operand $0~Int0 must be of type $1~String1. |
| *CmpCommon::diags() << DgSqlCode(-4182) |
| << DgString0(getTextUpper()) |
| << DgInt0(operand) |
| << DgString1("DATE or TIMESTAMP"); |
| bindWA->setErrStatus(); |
| return true; |
| } |
| |
| setChild(childIndex, tempBoundTree); |
| return false; // no error |
| } |
| |
| //------------------------------------------------------------------------- |
| // |
| // member functions for class ItmSequenceFunction |
| // |
| //------------------------------------------------------------------------- |
| ItemExpr *ItmSequenceFunction::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| if (bindWA->getCurrentScope()->getAllSequenceFunctions().contains(getValueId())) |
| bindWA->getCurrentScope()->getUnresolvedSequenceFunctions() += getValueId(); |
| return getValueId().getItemExpr(); |
| } |
| |
| if (isOlapFunction()) |
| { |
| ItmSeqOlapFunction * olap = (ItmSeqOlapFunction * )this; |
| if ((olap->isFrameStartUnboundedPreceding() && //olap->getframeStart() == -INT_MAX && |
| olap->isFrameEndUnboundedPreceding() ) || //olap->getframeEnd() == -INT_MAX) || |
| (olap->isFrameStartUnboundedFollowing() && //olap->getframeStart() == INT_MAX && |
| olap->isFrameEndUnboundedFollowing()) || //olap->getframeEnd() == INT_MAX) || |
| (olap->getframeStart() > olap->getframeEnd())) |
| { |
| //The specified window frame clause is not valid. |
| *CmpCommon::diags() << DgSqlCode(-4342); |
| bindWA->setErrStatus(); |
| } |
| |
| if (!olap->isFrameStartUnboundedPreceding() && //olap->getframeStart() != -INT_MAX && |
| !olap->isFrameEndUnboundedFollowing() && //olap->getframeEnd() != INT_MAX && |
| olap->getframeEnd() - olap->getframeStart() > |
| (Lng32)CmpCommon::getDefaultNumeric(OLAP_MAX_FIXED_WINDOW_FRAME)) |
| { |
| //Maximum Window frame size exceeded. |
| *CmpCommon::diags() << DgSqlCode(-4347) |
| << DgInt0((Lng32)CmpCommon::getDefaultNumeric(OLAP_MAX_FIXED_WINDOW_FRAME)); |
| bindWA->setErrStatus(); |
| } |
| |
| if ( olap->getframeEnd() > 0 && |
| bindWA->getCurrentScope()->context()->inPrecOlapFunction()) |
| { |
| //Nesting Window functions with FOLLOWING clause is not supported. |
| *CmpCommon::diags() << DgSqlCode(-4348); |
| bindWA->setErrStatus(); |
| } |
| |
| if (olap->getframeStart() <= 0) |
| { |
| bindWA->getCurrentScope()->context()->inPrecOlapFunction() = TRUE; |
| } |
| |
| } |
| |
| RelSequence * seqNode = |
| (RelSequence *)bindWA->getCurrentScope()->getSequenceNode(); |
| |
| bindWA->getCurrentScope()->context()->inSequenceFunction() = TRUE; |
| |
| if (seqNode && !isOLAP()) |
| { |
| if (isTDFunction() && |
| ((const RelSequence *)seqNode)->requiredOrder().entries() != 0) |
| { |
| setIsTDFunction(FALSE); |
| } |
| |
| if(isTDFunction() ) |
| { |
| if (bindWA->getCurrentScope()->context()->inTDFunction() && |
| getOperatorType() == ITM_RUNNING_RANK) |
| {//Nesting rank functions is not supported. |
| *CmpCommon::diags() << DgSqlCode(-4368); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (!bindWA->getCurrentScope()->context()->inSelectList() && |
| !bindWA->getCurrentScope()->context()->inQualifyClause()) |
| { //Rank can be placed only in the select list or the qualify clause |
| *CmpCommon::diags() << DgSqlCode(-4364); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (getOperatorType() == ITM_RUNNING_RANK) |
| { |
| bindWA->getCurrentScope()->context()->inTDFunction() = TRUE; |
| } |
| |
| ValueIdList change = seqNode->getPartitionChange(); |
| |
| if (change.entries() != 0 && getOperatorType() == ITM_RUNNING_RANK) |
| { |
| setOlapPartitionBy(change.rebuildExprTree(ITM_ITEM_LIST)); |
| |
| CMPASSERT(getOlapPartitionBy()); |
| |
| ItemExpr * tdSeq = transformTDFunction(bindWA); |
| if (! tdSeq) |
| return NULL; |
| |
| return tdSeq->bindNode(bindWA); |
| } |
| } |
| else |
| { |
| if (bindWA->getCurrentScope()->context()->inTDFunction()) |
| { //Using rank function and sequence functions together in the same query scope is not supported |
| *CmpCommon::diags() << DgSqlCode(-4367); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } |
| |
| BindScope::HasOlapFunctionsEnum olap = isOLAP() ? BindScope::OLAP_: BindScope::NONOLAP_; |
| |
| if ( bindWA->getCurrentScope()->getHasOlapSeqFunctions() == BindScope::OLAPUNKNOWN_ ) |
| { |
| bindWA->getCurrentScope()->setHasOlapSeqFunctions(olap); |
| } |
| else |
| { |
| if (bindWA->getCurrentScope()->getHasOlapSeqFunctions() != olap) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4345); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } |
| |
| if (bindWA->getCurrentScope()->context()->inHavingClause() OR ( |
| bindWA->getCurrentScope()->context()->inSelectList() AND |
| bindWA->getCurrentScope()->getRETDesc()->isGrouped() AND |
| ! isOLAP() |
| )) { |
| // |
| // 4109: We are in a SELECT or HAVING, and the sequence function is not |
| // inside an aggregate (fixes Genesis case 10-990823-0045) |
| // |
| if (NOT bindWA->getCurrentScope()->context()->inAggregate()) { |
| NAString unparsed(bindWA->wHeap()); |
| unparse(unparsed, DEFAULT_PHASE, USER_FORMAT_DELUXE); |
| // 4109: sequence function placed incorrectly |
| *CmpCommon::diags() << DgSqlCode(-4109) << DgString0(unparsed); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // Capture the current set of sequence functions in this scope. |
| // Do not add those sequence functions which are below this node. |
| // |
| ValueIdSet seqFuncs = |
| bindWA->getCurrentScope()->getUnresolvedSequenceFunctions(); |
| |
| // Check for invalid nesting of the THIS sequence function |
| // inside ROWS SINCE and some other sequence function. |
| // All other nestings are allowed. |
| // |
| NABoolean savedRowsSince = bindWA->getCurrentScope()->context()->inRowsSince(); |
| NABoolean savedOtherSequenceFunction = bindWA->getCurrentScope()->context()->inOtherSequenceFunction(); |
| switch (getOperatorType()) |
| { |
| case ITM_ROWS_SINCE: |
| bindWA->getCurrentScope()->context()->inRowsSince() = TRUE; |
| // The next line fixes Genesis case 10-990301-0576. |
| bindWA->getCurrentScope()->context()->inOtherSequenceFunction() = FALSE; |
| break; |
| |
| case ITM_THIS: |
| if (bindWA->getCurrentScope()->context()->inRowsSince() && |
| bindWA->getCurrentScope()->context()->inOtherSequenceFunction()) |
| { |
| // Inside a ROWS SINCE, the xxx function contained an invalid reference |
| // to the THIS function. |
| *CmpCommon::diags() << DgSqlCode(-4108); |
| bindWA->setErrStatus(); |
| } |
| if (NOT bindWA->getCurrentScope()->context()->inRowsSince()) |
| { |
| // THIS can be used only Inside a ROWS SINCE |
| *CmpCommon::diags() << DgSqlCode(-4220); |
| bindWA->setErrStatus(); |
| } |
| break; |
| |
| default: |
| bindWA->getCurrentScope()->context()->inOtherSequenceFunction() = TRUE; |
| break; |
| } |
| |
| // The sequence functions are bound in the environment (RETDesc) of |
| // the child of the Sequence |
| // |
| |
| RelExpr *sequenceNode = bindWA->getCurrentScope()->getSequenceNode(); |
| RETDesc *currentRETDesc = NULL; |
| |
| if (sequenceNode) { |
| |
| currentRETDesc = bindWA->getCurrentScope()->getRETDesc(); |
| |
| bindWA->getCurrentScope()->setRETDesc(sequenceNode->child(0)->getRETDesc()); |
| } |
| |
| |
| // ItmSequencefunction is directly derived from BuiltinFunction; |
| // safe to invoke this |
| // |
| BuiltinFunction::bindNode(bindWA); |
| |
| if (sequenceNode) { |
| |
| bindWA->getCurrentScope()->setRETDesc(currentRETDesc); |
| |
| } |
| |
| if (bindWA->errStatus()) return NULL; |
| |
| bindWA->getCurrentScope()->context()->inTDFunction() = FALSE; |
| |
| bindWA->getCurrentScope()->context()->inPrecOlapFunction() = FALSE; |
| |
| ValueId equivId, finalVid; |
| if (CmpCommon::getDefault(COMP_BOOL_203) == DF_ON) { |
| equivId = bindWA->getCurrentScope()->getEquivalentItmSequenceFunction(getValueId()); |
| } |
| |
| // Add value id to list of sequence functions in this scope. |
| // Ignore those sequence functions which are below this node. |
| // We only want the root sequence functions. |
| // |
| if (CmpCommon::getDefault(COMP_BOOL_203) == DF_ON) |
| finalVid = equivId; |
| else |
| finalVid = getValueId(); |
| |
| seqFuncs += finalVid ; |
| bindWA->getCurrentScope()->getUnresolvedSequenceFunctions() = seqFuncs; |
| bindWA->getCurrentScope()->getAllSequenceFunctions() += finalVid; |
| |
| |
| // |
| // Reset the nesting flags. |
| // |
| bindWA->getCurrentScope()->context()->inOtherSequenceFunction() = savedOtherSequenceFunction; |
| bindWA->getCurrentScope()->context()->inRowsSince() = savedRowsSince; |
| |
| // Reset other context flags. |
| bindWA->getCurrentScope()->context()->inSequenceFunction() = FALSE; |
| |
| if (CmpCommon::getDefault(COMP_BOOL_203) == DF_ON) |
| return equivId.getItemExpr(); |
| else |
| return getValueId().getItemExpr(); |
| } |
| |
| //--------------------------------------------------------------------------- |
| // |
| // member functions for class HbaseColumnLookup |
| // |
| //--------------------------------------------------------------------------- |
| ItemExpr *HbaseColumnLookup::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * boundExpr = NULL; |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // Binds self; Binds children; ColumnLookup::synthesize(); |
| boundExpr = Function::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| // Add this hbase column to column usage info of this hbase table. |
| ItemExpr * ie = child(0)->castToItemExpr(); |
| NAColumn * nacol = NULL; |
| if (ie->getOperatorType() == ITM_REFERENCE) |
| { |
| ColReference * colR = (ColReference*)ie; |
| nacol = colR->getValueId().getNAColumn(TRUE/*okIfNotColumn*/); |
| } |
| else if (ie->getOperatorType() == ITM_BASECOLUMN) |
| { |
| BaseColumn * baseC = (BaseColumn*)ie; |
| nacol = baseC->getNAColumn(); |
| } |
| |
| const NATable * naTable = nacol->getNATable(); |
| bindWA->hbaseColUsageInfo()->insert |
| ((QualifiedName*)&naTable->getTableName(), &hbaseCol_); |
| |
| return boundExpr; |
| } |
| |
| //--------------------------------------------------------------------------- |
| // |
| // member functions for class HbaseColumnsDisplay |
| // |
| //--------------------------------------------------------------------------- |
| ItemExpr *HbaseColumnsDisplay::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * boundExpr = NULL; |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // Binds self; Binds children; ColumnDisplay::synthesize(); |
| boundExpr = Function::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| // Add this hbase column to column usage info of this hbase table. |
| ItemExpr * ie = child(0)->castToItemExpr(); |
| NAColumn * nacol = NULL; |
| if (ie->getOperatorType() == ITM_REFERENCE) |
| { |
| ColReference * colR = (ColReference*)ie; |
| nacol = colR->getValueId().getNAColumn(TRUE/*okIfNotColumn*/); |
| } |
| else if (ie->getOperatorType() == ITM_BASECOLUMN) |
| { |
| BaseColumn * baseC = (BaseColumn*)ie; |
| nacol = baseC->getNAColumn(); |
| } |
| |
| const NATable * naTable = nacol->getNATable(); |
| if (csl() == NULL) |
| { |
| NAString nas("*"); |
| bindWA->hbaseColUsageInfo()->insert |
| ((QualifiedName*)&naTable->getTableName(), &nas); |
| } |
| else |
| { |
| for (Lng32 i = 0; i < csl()->entries(); i++) |
| { |
| NAString * nas = (NAString*)(*csl())[i]; |
| |
| bindWA->hbaseColUsageInfo()->insert |
| ((QualifiedName*)&naTable->getTableName(), nas); |
| } |
| } |
| |
| return boundExpr; |
| } |
| |
| //--------------------------------------------------------------------------- |
| // |
| // member functions for class HbaseColumnCreate |
| // |
| //--------------------------------------------------------------------------- |
| ItemExpr *HbaseColumnCreate::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| ItemExpr * boundExpr = NULL; |
| short numEntries = hccol_->entries(); |
| colValMaxLen_ = 0; |
| NAType * firstType = NULL; |
| NAType * resultType = NULL; |
| NABoolean resultNull = FALSE; |
| colNameMaxLen_ = 0; |
| for (short i = 0; i < numEntries; i++) |
| { |
| HbaseColumnCreateOptions * hcco = (*hccol_)[i]; |
| HbaseColumnCreate::HbaseColumnCreateOptions::ConvType co = HbaseColumnCreate::HbaseColumnCreateOptions::NONE; |
| |
| ItemExpr * colName = hcco->colName(); |
| colName = colName->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| // type cast any params |
| ValueId vid1 = colName->getValueId(); |
| SQLVarChar c1(NULL, CmpCommon::getDefaultNumeric(HBASE_MAX_COLUMN_NAME_LENGTH)); |
| vid1.coerceType(c1, NA_CHARACTER_TYPE); |
| |
| hcco->setColName(colName); |
| |
| ItemExpr * colValue = hcco->colVal(); |
| colValue = colValue->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| // type cast any params |
| ValueId vid2 = colValue->getValueId(); |
| SQLVarChar c2(NULL, CmpCommon::getDefaultNumeric(HBASE_MAX_COLUMN_VAL_LENGTH)); |
| vid2.coerceType(c2, NA_CHARACTER_TYPE); |
| |
| hcco->setColVal(colValue); |
| |
| const NAType &typeColName = |
| colName->castToItemExpr()->getValueId().getType(); |
| |
| const NAType &typeColVal = |
| colValue->castToItemExpr()->getValueId().getType(); |
| |
| if (colNameMaxLen_ < typeColName.getNominalSize()) |
| colNameMaxLen_ = typeColName.getNominalSize(); |
| |
| if (i == 0) // first entry |
| { |
| co = hcco->convType(); |
| firstType = (NAType*)hcco->naType(); |
| if (firstType) |
| resultType = firstType; |
| else |
| resultType = &(NAType&)typeColVal; |
| if (resultType->getTypeQualifier() != NA_CHARACTER_TYPE) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4221) |
| << DgString0("COLUMN_CREATE(list format)") |
| << DgString1("character type"); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| colValMaxLen_ = resultType->getNominalSize(); |
| resultNull = resultType->supportsSQLnull(); |
| } // if first entry |
| else |
| { |
| if ((co != hcco->convType()) || |
| (! firstType && hcco->naType()) || |
| (firstType && ! hcco->naType()) || |
| (firstType && hcco->naType() && (NOT (*firstType == *hcco->naType())))) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4221) |
| << DgString0("COLUMN_CREATE(list format)") |
| << DgString1("compatible"); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| } // else |
| |
| if (co == HbaseColumnCreate::HbaseColumnCreateOptions::NONE) |
| { |
| if (colValMaxLen_ < typeColVal.getNominalSize()) |
| colValMaxLen_ = typeColVal.getNominalSize(); |
| |
| if (typeColVal.supportsSQLnull()) |
| resultNull = TRUE; |
| } |
| } // for |
| |
| resultNull = TRUE; |
| NAType * childResultType = new(bindWA->wHeap()) SQLVarChar(bindWA->wHeap(), colValMaxLen_, |
| resultNull); |
| |
| Lng32 totalLen = 0; |
| totalLen += sizeof(numEntries) + sizeof(colNameMaxLen_) |
| + sizeof(short)/*VCLenIndicatorSize*/ + sizeof(colValMaxLen_); |
| |
| for (Lng32 i = 0; i < numEntries; i++) |
| { |
| HbaseColumnCreateOptions * hcco = (*hccol_)[i]; |
| |
| NAType * cnType = new(bindWA->wHeap()) SQLVarChar(bindWA->wHeap(), colNameMaxLen_, FALSE); |
| ItemExpr * cnChild = |
| new (bindWA->wHeap()) Cast(hcco->colName(), cnType); |
| cnChild = cnChild->bindNode(bindWA); |
| hcco->setColName(cnChild); |
| totalLen += cnChild->getValueId().getType().getTotalSize(); |
| |
| ItemExpr * newChild = |
| new (bindWA->wHeap()) Cast(hcco->colVal(), childResultType); |
| newChild = newChild->bindNode(bindWA); |
| hcco->setColVal(newChild); |
| totalLen += newChild->getValueId().getType().getTotalSize(); |
| } |
| |
| resultType_ = new(bindWA->wHeap()) SQLVarChar(bindWA->wHeap(), totalLen, FALSE); |
| |
| // Binds self; Binds children; ColumnCreate::synthesize(); |
| boundExpr = Function::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| return boundExpr; |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Loboper, LOBinsert, LOBselect, LOBdelete |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr *LOBinsert::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * boundExpr = NULL; |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // Binds self; Binds children; LOBoper::synthesize(); |
| boundExpr = LOBoper::bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| |
| return boundExpr; |
| } // LOBinsert::bindNode() |
| |
| ItemExpr *LOBselect::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * boundExpr = NULL; |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| |
| // For now LOBselect is allowed only in the top most select list |
| // check that first, or else give an error |
| |
| |
| // Binds self; Binds children; LOBoper::synthesize(); |
| boundExpr = LOBoper::bindNode(bindWA); |
| if (bindWA->errStatus()) return NULL; |
| |
| return boundExpr; |
| } // LOBselect::bindNode() |
| |
| |
| |
| ItemExpr *SequenceValue::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * boundExpr = NULL; |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // Binds self; Binds children; SequenceValue::synthesize(); |
| boundExpr = Function::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| ULng32 savedParserFlags = Get_SqlParser_Flags (0xFFFFFFFF); |
| Set_SqlParser_Flags(ALLOW_VOLATILE_SCHEMA_IN_TABLE_NAME); |
| |
| // Obtain the NATable for the seq object. |
| naTable_ = bindWA->getNATable(seqCorrName_); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // BindWA keeps list of sequence generators used, so privileges can be checked. |
| bindWA->insertSeqVal(this); |
| |
| Assign_SqlParser_Flags (savedParserFlags); |
| |
| return boundExpr; |
| } |
| |
| ItemExpr *HbaseTimestamp::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * boundExpr = NULL; |
| |
| CMPASSERT(col_); |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| col_ = col_->bindNode(bindWA); |
| if (! col_ || bindWA->errStatus()) |
| return NULL; |
| |
| CMPASSERT(col_->getOperatorType() == ITM_BASECOLUMN); |
| |
| NAColumn * nac = ((BaseColumn*)col_)->getNAColumn(); |
| if (! nac) |
| return NULL; |
| |
| colName_ = nac->getColName(); |
| |
| NAType * tsValsType = |
| new (bindWA->wHeap()) SQLVarChar(bindWA->wHeap(), sizeof(Int64), FALSE); |
| tsVals_ = |
| new (bindWA->wHeap()) NATypeToItem(tsValsType); |
| |
| tsVals_ = tsVals_->bindNode(bindWA); |
| if (! tsVals_ || bindWA->errStatus()) |
| return NULL; |
| |
| // Binds self; Binds children; HbaseTimestamp::synthesize(); |
| boundExpr = Function::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| return boundExpr; |
| } |
| |
| ItemExpr *HbaseTimestampRef::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * boundExpr = NULL; |
| |
| CMPASSERT(col_); |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| col_ = col_->bindNode(bindWA); |
| if (! col_ || bindWA->errStatus()) |
| return NULL; |
| |
| CMPASSERT(col_->getOperatorType() == ITM_BASECOLUMN); |
| |
| BaseColumn * bc = (BaseColumn*)col_; |
| |
| if ((bc->getTableDesc()->getNATable()->isHiveTable()) || |
| (bc->getTableDesc()->getNATable()->isSQLMXAlignedTable())) |
| { |
| if (bc->getTableDesc()->getNATable()->isHiveTable()) |
| *CmpCommon::diags() << DgSqlCode(-3242) |
| << DgString0("hbase_timestamp or hbase_version cannot be used on a Hive table."); |
| else |
| *CmpCommon::diags() << DgSqlCode(-3242) |
| << DgString0("hbase_timestamp or hbase_version cannot be used on an aligned format table."); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (bc->getTableDesc()->hbaseTSList().entries() == 0) |
| { |
| for (CollIndex i = 0; i < bc->getTableDesc()->getColumnList().entries(); i++) |
| { |
| ItemExpr *baseCol = bc->getTableDesc()->getColumnList()[i].getItemExpr(); |
| HbaseTimestamp * hbtCol = |
| new (bindWA->wHeap()) HbaseTimestamp(baseCol); |
| hbtCol->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| bc->getTableDesc()->hbaseTSList().insert(hbtCol->getValueId()); |
| } |
| } |
| |
| ValueId valId = bc->getTableDesc()->hbaseTSList()[bc->getColNumber()]; |
| setValueId(valId); |
| |
| bindSelf(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| return valId.getItemExpr(); |
| } |
| |
| ItemExpr *HbaseVersion::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * boundExpr = NULL; |
| |
| CMPASSERT(col_); |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| col_ = col_->bindNode(bindWA); |
| if (! col_ || bindWA->errStatus()) |
| return NULL; |
| |
| CMPASSERT(col_->getOperatorType() == ITM_BASECOLUMN); |
| |
| NAColumn * nac = ((BaseColumn*)col_)->getNAColumn(); |
| if (! nac) |
| return NULL; |
| |
| colName_ = nac->getColName(); |
| |
| NAType * tsValsType = |
| new (bindWA->wHeap()) SQLVarChar(bindWA->wHeap(), sizeof(Int64), FALSE); |
| tsVals_ = |
| new (bindWA->wHeap()) NATypeToItem(tsValsType); |
| |
| tsVals_ = tsVals_->bindNode(bindWA); |
| if (! tsVals_ || bindWA->errStatus()) |
| return NULL; |
| |
| // Binds self; Binds children; HbaseVersion::synthesize(); |
| boundExpr = Function::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| return boundExpr; |
| } |
| |
| ItemExpr *HbaseVersionRef::bindNode(BindWA *bindWA) |
| { |
| ItemExpr * boundExpr = NULL; |
| |
| CMPASSERT(col_); |
| |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| col_ = col_->bindNode(bindWA); |
| if (! col_ || bindWA->errStatus()) |
| return NULL; |
| |
| CMPASSERT(col_->getOperatorType() == ITM_BASECOLUMN); |
| |
| BaseColumn * bc = (BaseColumn*)col_; |
| |
| if ((bc->getTableDesc()->getNATable()->isHiveTable()) || |
| (bc->getTableDesc()->getNATable()->isSQLMXAlignedTable())) |
| { |
| if (bc->getTableDesc()->getNATable()->isHiveTable()) |
| *CmpCommon::diags() << DgSqlCode(-3242) |
| << DgString0("hbase_timestamp or hbase_version cannot be used on a Hive table."); |
| else |
| *CmpCommon::diags() << DgSqlCode(-3242) |
| << DgString0("hbase_timestamp or hbase_version cannot be used on an aligned format table."); |
| |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| if (bc->getTableDesc()->hbaseVersionList().entries() == 0) |
| { |
| for (CollIndex i = 0; i < bc->getTableDesc()->getColumnList().entries(); i++) |
| { |
| ItemExpr *baseCol = bc->getTableDesc()->getColumnList()[i].getItemExpr(); |
| HbaseVersion * hbtCol = |
| new (bindWA->wHeap()) HbaseVersion(baseCol); |
| hbtCol->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| bc->getTableDesc()->hbaseVersionList().insert(hbtCol->getValueId()); |
| } |
| } |
| |
| ValueId valId = bc->getTableDesc()->hbaseVersionList()[bc->getColNumber()]; |
| setValueId(valId); |
| |
| bindSelf(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| return valId.getItemExpr(); |
| } |
| |
| ItemExpr *RowNumFunc::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| return getValueId().getItemExpr(); |
| |
| // For now user(x) is allowed only in the top most select list |
| // check that first, or else give an error |
| |
| BindScope * currScope = bindWA->getCurrentScope(); |
| BindContext *context = currScope->context(); |
| |
| if (!(context->inSelectList())) |
| // (! context->inWhereClause())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4311) |
| << DgString0("ROWNUM"); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| // Check for case like select (select user(1) ...). |
| // or select * from t1, (select user(x) from t1) t3 etc. |
| // Here the user function is in the select list of a |
| // sub-query and in join, hence is not allowed. |
| // Also it is not allowed at any other place example orderBy |
| BindScope *prevScope = NULL; |
| |
| while (currScope) |
| { |
| BindContext *currContext = currScope->context(); |
| if (currContext->inSubquery() || |
| currContext->inOrderBy() || |
| currContext->inExistsPredicate() || |
| currContext->inGroupByClause() || |
| currContext->inGroupByOrdinal() || |
| currContext->inWhereClause() || |
| currContext->inHavingClause() || |
| currContext->inUnion() || |
| currContext->inJoin() ) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4311) |
| << DgString0("ROWNUM"); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| prevScope = currScope; |
| currScope = bindWA->getPreviousScope(prevScope); |
| } |
| |
| return BuiltinFunction::bindNode(bindWA); |
| } // RowNumFunc::bindNode |
| |
| NABoolean ItemExpr::canBeUsedInGBorOB(NABoolean setErr) |
| { |
| Int32 arity = getArity(); |
| for (Int32 i=0; i<arity; i++) |
| { |
| ItemExpr *ieChild = child(i); |
| if (NOT ieChild->canBeUsedInGBorOB(setErr)) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| NABoolean RowNumFunc::canBeUsedInGBorOB(NABoolean setErr) |
| { |
| // cannot be used in a group by or order by clause. |
| if (setErr) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4311) |
| << DgString0("ROWNUM"); |
| } |
| |
| return FALSE; |
| } |