blob: e8acea464d0d57d2048b56900831d378bafe2557 [file] [log] [blame]
/**********************************************************************
// @@@ START COPYRIGHT @@@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
// @@@ END COPYRIGHT @@@
**********************************************************************/
#include <limits>
#include <float.h>
#include "nawstring.h"
#include "QRDescGenerator.h"
#include "NumericType.h"
#include "DatetimeType.h"
#include "QRLogger.h"
#include "OptRange.h"
#include "ItemLog.h"
#include "ComCextdecs.h"
double getDoubleValue(ConstValue* val, logLevel level);
/**
* Returns the Int64 value corresponding to the type of the ConstValue val.
* val can be of any numeric, datetime, or interval type. The returned value
* is used in the representation of a range of values implied by the predicates
* of a query for an exact numeric, datetime, or interval type.
*
* @param val ConstValue that wraps the value to be represented as an Int64.
* @param rangeColType The type of the column or expr the range is for.
* @param [out] truncated TRUE returned if the value returned was the result of
* truncating the input value. This can happen for floating
* point input, or exact numeric input that has greater
* scale than rangeColType.
* @param [out] valWasNegative TRUE returned if the input value was negative.
* Adjustment of truncated values is only done for
* positive values (because the truncation of a negative
* value adjusts it correctly). The caller can't just
* look at the returned value, because if it is 0, it
* may have been truncated from a small negative (-1 < n < 0)
* or a small positive (0 < n < 1) value.
* @param level Logging level to use in event of failure.
* @return The rangespec internal representation of the input constant value.
*/
static Int64 getInt64Value(ConstValue* val, const NAType* rangeColType,
NABoolean& truncated, NABoolean& valWasNegative,
logLevel level);
OptRangeSpec::OptRangeSpec(QRDescGenerator* descGenerator, CollHeap* heap, logLevel ll)
: RangeSpec(heap, ll),
descGenerator_(descGenerator),
rangeExpr_(NULL),
vid_(NULL_VALUE_ID),
isIntersection_(FALSE)
{
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
descGenerator, QRLogicException,
"OptRangeSpec constructed for null QRDescGenerator");
if (descGenerator->isDumpMvMode())
setDumpMvMode();
}
ValueId OptRangeSpec::getBaseCol(const ValueIdSet& vegMembers)
{
for (ValueId id=vegMembers.init(); vegMembers.next(id); vegMembers.advance(id))
{
if (id.getItemExpr()->getOperatorType() == ITM_BASECOLUMN)
return id;
}
return NULL_VALUE_ID;
}
ValueId OptRangeSpec::getBaseCol(const ValueId vegRefVid)
{
ItemExpr* itemExpr = vegRefVid.getItemExpr();
OperatorTypeEnum opType = itemExpr->getOperatorType();
// See if the vid is for a basecol instead of a vegref.
if (opType == ITM_BASECOLUMN)
return vegRefVid;
// Get the value id of the first member that is a base column.
assertLogAndThrow1(CAT_SQL_COMP_RANGE, logLevel_,
opType == ITM_VEG_REFERENCE, QRDescriptorException,
"OptRangeSpec::getBaseCol() expected value id of a "
"vegref, not of op type -- %d", opType);
ValueId baseColVid = getBaseCol(static_cast<VEGReference*>(itemExpr)
->getVEG()->getAllValues());
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
baseColVid != NULL_VALUE_ID, QRDescriptorException,
"Vegref contains no base columns");
return baseColVid;
}
OptRangeSpec* OptRangeSpec::createRangeSpec(QRDescGenerator* descGenerator,
ItemExpr* predExpr,
CollHeap* heap,
NABoolean createForNormalizer)
{
QRTRACER("createRangeSpec");
OptRangeSpec* range = NULL;
if (predExpr->getOperatorType() == ITM_RANGE_SPEC_FUNC)
{
assertLogAndThrow(CAT_SQL_COMP_RANGE, LL_ERROR,
!createForNormalizer, QRDescriptorException,
"RangeSpecRef should not be present if creating for Normalizer");
RangeSpecRef* rangeIE = static_cast<RangeSpecRef*>(predExpr);
range = new(heap) OptRangeSpec(*rangeIE->getRangeObject(), heap);
// RangeSpecRefs are produced by the Normalizer. The rangespec they contain
// may have a vegref vid as the rangecolvalueid instead of using the first
// basecol vid as we do when a range is created in mvqr. Also, the vid may
// be that of a joinpred, but will still be stored as the rangecolvalueid.
// Below, we sort out these issues for the new rangespec we have created
// using the copy ctor on the one in the RangeSpecRef.
ValueId rcvid = range->getRangeColValueId();
if (rcvid != NULL_VALUE_ID)
{
ItemExpr* ie = rcvid.getItemExpr();
if (ie->getOperatorType() == ITM_VEG_REFERENCE)
{
if (descGenerator->isJoinPredId(rcvid))
{
range->setRangeColValueId(NULL_VALUE_ID);
range->setRangeJoinPredId(rcvid);
}
else
{
rcvid = range->getBaseCol(((VEGReference*)ie)->getVEG()->getAllValues());
if (rcvid != NULL_VALUE_ID)
range->setRangeColValueId(rcvid);
}
}
}
}
else
{
if (createForNormalizer)
{
range = new (heap) OptNormRangeSpec(descGenerator, heap);
(static_cast<OptNormRangeSpec*>(range))
->setOriginalItemExpr(predExpr);
}
else
range = new (heap) OptRangeSpec(descGenerator, heap);
if (!range->buildRange(predExpr))
{
delete range;
return NULL;
}
}
range->setID(predExpr->getValueId());
range->log();
return range;
}
// Protected copy ctor, used by clone().
OptRangeSpec::OptRangeSpec(const OptRangeSpec& other, CollHeap* heap)
: RangeSpec(other, heap),
descGenerator_(other.descGenerator_),
rangeExpr_(NULL),
vid_(other.vid_),
isIntersection_(other.isIntersection_)
{
// At this point the inherited heap ptr mvqrHeap_ has been initialized
// by the superclass ctor.
if (other.rangeExpr_)
rangeExpr_ = other.rangeExpr_->copyTree(mvqrHeap_);
}
QRRangePredPtr OptRangeSpec::createRangeElem()
{
QRTRACER("createRangeElem");
QRRangePredPtr rangePredElem =
new (mvqrHeap_) QRRangePred(ADD_MEMCHECK_ARGS(mvqrHeap_));
rangePredElem->setRangeItem(genRangeItem());
NABoolean rangeIsOnCol = (rangeJoinPredId_ != NULL_VALUE_ID ||
rangeColValueId_ != NULL_VALUE_ID);
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
getID()>0, QRDescriptorException,
"No id for range element in OptRangeSpec::createRangeElem().");
// The id of this rangespec is the value id of the original predicate on the
// corresponding range column/expr. If other preds on the range col/expr were
// found and had to be intersected, we need to use the value id of the itemexpr
// that is the result of the intersection (so the right predicat will be used
// for the rewrite).
if (isIntersection_)
rangePredElem->setID(getRangeItemExpr()->getValueId());
else
rangePredElem->setID(getID());
const NAType* typePtr = getType();
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
typePtr, QRDescriptorException,
"Call to getType() returned NULL in OptRangeSpec::createRangeElem().");
const NAType& type = *typePtr;
rangePredElem->setSqlType(type.getTypeSQLname());
QROpInequalityPtr ineqOp;
QROpEQPtr eqOp = NULL;
QROpBTPtr betweenOp;
CollIndex numSubranges = subranges_.entries();
for (CollIndex i=0; i<numSubranges; i++)
{
SubrangeBase& subrange = *subranges_[i];
if (subrange.startIsMin() ||
(i==0 && rangeIsOnCol && subrange.isMinForType(type)))
{
assertLogAndThrow1(CAT_SQL_COMP_RANGE, logLevel_,
i==0, QRDescriptorException,
"Subrange other than 1st is unbounded on low side, "
"subrange index %d", i);
if (subrange.endIsMax() ||
(i==numSubranges-1 && rangeIsOnCol && subrange.isMaxForType(type)))
{
// Range spans all values of the type. If NULL is included as
// well, return NULL to indicate no range restriction. If NULL is
// not included in the range, an empty <RangePred> is used to
// indicate IS NOT NULL.
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
numSubranges==1, QRDescriptorException,
"Range of all values must have a single Subrange.");
if (nullIncluded_)
{
QRLogger::log(CAT_SQL_COMP_RANGE, LL_INFO,
"Range predicate ignored because it spans entire range + NULL.");
deletePtr(rangePredElem);
return NULL;
}
else
{
// An IS NOT NULL predicate on a NOT NULL column is removed in
// the early compilation stages. If we generate the usual empty
// range element to represent a predicate that spans all values,
// it will not match this missing predicate. So we detect and
// remove it.
QRElementPtr rangeItemElem = rangePredElem->getRangeItem()
->getReferencedElement();
if (rangeItemElem->getIDFirstChar() == 'C')
{
ValueId vid = rangeItemElem->getIDNum();
if ((static_cast<BaseColumn*>(vid.getItemExpr()))
->getNAColumn()->getNotNullNondroppable())
{
QRLogger::log(CAT_SQL_COMP_RANGE, LL_INFO,
"Range predicate ignored because it spans entire "
"range and has NOT NULL constraint.");
deletePtr(rangePredElem);
return NULL;
}
}
}
return rangePredElem; // leave it empty
}
if (subrange.endInclusive())
ineqOp = new (mvqrHeap_) QROpLE(ADD_MEMCHECK_ARGS(mvqrHeap_));
else
ineqOp = new (mvqrHeap_) QROpLS(ADD_MEMCHECK_ARGS(mvqrHeap_));
ineqOp->setValue(subrange.getEndScalarValElem(mvqrHeap_, type));
// If start is not min, we are here because lower bound for the type
// was start of first subrange.
ineqOp->setNormalized(!subrange.startIsMin());
rangePredElem->addOperator(ineqOp);
}
else if (subrange.endIsMax() ||
(i==numSubranges-1 && rangeIsOnCol
&& subrange.isMaxForType(type)))
{
assertLogAndThrow1(CAT_SQL_COMP_RANGE, logLevel_,
i==numSubranges-1, QRDescriptorException,
"Subrange other than last is unbounded on high side, "
"subrange index %d",
i);
if (eqOp) // wrap this up if one in progress
{
rangePredElem->addOperator(eqOp);
eqOp = NULL;
}
if (subrange.startInclusive())
ineqOp = new (mvqrHeap_) QROpGE(ADD_MEMCHECK_ARGS(mvqrHeap_));
else
ineqOp = new (mvqrHeap_) QROpGT(ADD_MEMCHECK_ARGS(mvqrHeap_));
ineqOp->setValue(subrange.getStartScalarValElem(mvqrHeap_, type));
// If end is not max, we are here because upper bound for the type
// was end of last subrange.
ineqOp->setNormalized(!subrange.endIsMax());
rangePredElem->addOperator(ineqOp);
}
else if (subrange.isSingleValue())
{
// Add the value to a new OpEQ or the one we are already working on,
// but don't finish it until we hit something besides a single-value
// subrange.
if (!eqOp)
eqOp = new (mvqrHeap_) QROpEQ(ADD_MEMCHECK_ARGS(mvqrHeap_));
eqOp->addValue(subrange.getStartScalarValElem(mvqrHeap_, type));
}
else
{
// If values have been accumulated in an OpEQ, add it before doing
// the between op.
if (eqOp)
{
rangePredElem->addOperator(eqOp);
eqOp = NULL;
}
betweenOp = new (mvqrHeap_) QROpBT(ADD_MEMCHECK_ARGS(mvqrHeap_));
betweenOp->setStartValue(subrange.getStartScalarValElem(mvqrHeap_,
type));
betweenOp->setStartIncluded(subrange.startInclusive());
betweenOp->setEndValue(subrange.getEndScalarValElem(mvqrHeap_,
type));
betweenOp->setEndIncluded(subrange.endInclusive());
rangePredElem->addOperator(betweenOp);
}
}
// If IS NULL is part of the range spec, it should come last. If an OpEQ
// element is in progress, add it there, else create one for it. In either
// of these cases, add it to the range pred element.
if (eqOp)
{
if (nullIncluded_)
eqOp->setNullVal(new(mvqrHeap_) QRNullVal(ADD_MEMCHECK_ARGS(mvqrHeap_)));
rangePredElem->addOperator(eqOp);
}
else if (nullIncluded_)
{
eqOp = new (mvqrHeap_) QROpEQ(ADD_MEMCHECK_ARGS(mvqrHeap_));
eqOp->setNullVal(new(mvqrHeap_) QRNullVal(ADD_MEMCHECK_ARGS(mvqrHeap_)));
rangePredElem->addOperator(eqOp);
}
return rangePredElem;
} // createRangeElem()
QRElementPtr OptRangeSpec::genRangeItem()
{
QRElementPtr elem;
if (rangeColValueId_ != NULL_VALUE_ID)
elem = descGenerator_->genQRColumn(rangeColValueId_,
rangeJoinPredId_);
else if (rangeExpr_)
elem = descGenerator_->genQRExpr(rangeExpr_, rangeJoinPredId_);
else
{
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
rangeJoinPredId_ != NULL_VALUE_ID, QRDescriptorException,
"All range value ids are null");
ValueId colVid = getBaseCol(rangeJoinPredId_);
elem = descGenerator_->genQRColumn(colVid,
rangeJoinPredId_);
}
return elem;
}
ItemExpr* OptRangeSpec::getRangeExpr() const
{
if (rangeExpr_)
return rangeExpr_;
else if (rangeJoinPredId_ != NULL_VALUE_ID)
return ((ValueId)rangeJoinPredId_).getItemExpr();
else
return ((ValueId)rangeColValueId_).getItemExpr();
}
// Exprs have been converted to a canonical form in which const is the 2nd operand.
// NULL is returned if there is not a constant operand, or if the other operand
// is not the one the range is being built for.
ConstValue* OptRangeSpec::getConstOperand(ItemExpr* predExpr, Lng32 constInx)
{
QRTRACER("getConstOperand");
ItemExpr* left = predExpr->child(0);
ItemExpr* right = predExpr->child(constInx);
// Bail out if we don't have a constant. If a vegref, see if the veg includes
// a constant, and substitute that if so.
if (right->getOperatorType() != ITM_CONSTANT)
{
if (right->getOperatorType() == ITM_VEG_REFERENCE)
{
ValueId constVid = (static_cast<VEGReference*>(right))->getVEG()->getAConstant(TRUE);
if (constVid == NULL_VALUE_ID)
return NULL;
else
right = constVid.getItemExpr();
}
else
return NULL;
}
ValueId colVid;
switch (left->getOperatorType())
{
case ITM_VEG_REFERENCE:
if (forNormalizer())
colVid = left->getValueId();
else
colVid = getBaseCol(((VEGReference*)left)->getVEG()->getAllValues());
break;
case ITM_BASECOLUMN: // Should only happen for a check constraint
colVid = left->getValueId();
break;
default: // must be an expression
colVid = NULL_VALUE_ID;
break;
}
if (colVid != NULL_VALUE_ID)
{
// If this range is for an expression, the pred is not on it.
if (rangeExpr_)
return NULL;
if (rangeColValueId_ == NULL_VALUE_ID)
{
rangeColValueId_ = colVid;
EqualitySet* eqSet =
descGenerator_->getEqualitySet(&rangeColValueId_);
if (eqSet)
{
rangeJoinPredId_ = (QRValueId)eqSet->getJoinPredId();
setType(eqSet->getType());
}
else
{
rangeJoinPredId_ = (QRValueId)NULL_VALUE_ID;
setType(&((ValueId)rangeColValueId_).getType());
}
}
else if (rangeColValueId_ != colVid)
return NULL;
}
else
{
// The left side of the pred is an expression. If this range is for a
// simple column, it doesn't match.
if (rangeColValueId_ != NULL_VALUE_ID)
return NULL;
if (!rangeExpr_)
{
// If more than one node is involved in an expression, it is a
// residual pred instead of a range pred.
if (descGenerator_->getExprNode(left) == NULL_CA_ID)
return NULL;
setRangeExpr(left); // sets rangeExpr_, rangeExprText_
EqualitySet* eqSet =
descGenerator_->getEqualitySet(&rangeExprText_);
if (eqSet)
{
rangeJoinPredId_ = (QRValueId)eqSet->getJoinPredId();
setType(eqSet->getType());
}
else
{
rangeJoinPredId_ = (QRValueId)NULL_VALUE_ID;
setType(&rangeExpr_->getValueId().getType());
}
}
else
{
// The ItemExpr ptrs will be different, so we compare the expression
// text to see if they are the same. At some point this text will be
// in a canonical form that ignores syntactic variances.
NAString exprText;
left->unparse(exprText, OPTIMIZER_PHASE, MVINFO_FORMAT);
if (rangeExprText_ != exprText)
return NULL;
}
}
if (!(QRDescGenerator::typeSupported(getType())))
return NULL;
// If we reach this point, the predicate has been confirmed to apply to the
// same col/expr of this range, and the right operand has been confirmed to
// be a constant. Before returning the ConstValue, make sure it is a type we
// currently support. Predicates involving types not yet supported will be
// treated as residual predicates.
if (QRDescGenerator::typeSupported(static_cast<ConstValue*>(right)->getType()))
return static_cast<ConstValue*>(right);
else
return NULL;
} // getConstOperand()
void OptRangeSpec::addSubrange(ConstValue* start, ConstValue* end,
NABoolean startInclusive, NABoolean endInclusive)
{
QRTRACER("addSubrange");
const NAType* type = getType();
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
type, QRDescriptorException,
"Call to getType() returned NULL in OptRangeSpec::addSubrange().");
NAString unparsedStart(""), unparsedEnd("");
if (isDumpMvMode())
{
// Add the "official" unparsed text of the expression as a sub-element.
if (start)
start->unparse(unparsedStart, OPTIMIZER_PHASE, QUERY_FORMAT);
if (end)
end->unparse(unparsedEnd, OPTIMIZER_PHASE, QUERY_FORMAT);
}
NABuiltInTypeEnum typeQual = type->getTypeQualifier();
switch (typeQual)
{
case NA_NUMERIC_TYPE:
case NA_DATETIME_TYPE:
case NA_INTERVAL_TYPE:
case NA_BOOLEAN_TYPE:
//if (((const NumericType*)type)->isExact())
if (typeQual == NA_DATETIME_TYPE ||
typeQual == NA_INTERVAL_TYPE ||
(typeQual == NA_NUMERIC_TYPE &&
static_cast<const NumericType*>(type)->isExact()) ||
(typeQual == NA_BOOLEAN_TYPE))
{
// Fixed-point numeric subranges are normalized to be inclusive, to
// simplify equivalence and subsumption checks.
Subrange<Int64>* sub = new (mvqrHeap_) Subrange<Int64>(logLevel_);
sub->setUnparsedStart(unparsedStart);
sub->setUnparsedEnd(unparsedEnd);
NABoolean valTruncated;
NABoolean valNegative;
NABoolean startOverflowed = FALSE;
NABoolean endOverflowed = FALSE;
if (start)
{
// If the constant is truncated because it has higher scale than
// the type of the range col/expr, the truncated value is not the
// start of the range. 1 is added to the truncated value to get
// the next value that is possible for the type.
sub->start = getInt64Value(start, type, valTruncated, valNegative, logLevel_);
if ((!startInclusive || valTruncated) &&
(!valTruncated || !valNegative))
sub->makeStartInclusive(type, startOverflowed);
}
else
sub->setStartIsMin(TRUE);
if (end)
{
// If the constant is truncated because it has higher scale than
// the type of the range col/expr, the truncated value must be
// included in the range even if the end is not inclusive (<).
sub->end = getInt64Value(end, type, valTruncated, valNegative, logLevel_);
if ((!endInclusive && !valTruncated) ||
(valTruncated && valNegative))
sub->makeEndInclusive(type, endOverflowed);
}
else
sub->setEndIsMax(TRUE);
// If not originally inclusive, has been adjusted above.
// Need this in case makeXXXInclusive was not called, but leave as
// is if made noninclusive because of positive (for start) or
// negative (for end) overflow.
if (!startOverflowed)
sub->setStartInclusive(TRUE);
if (!endOverflowed)
sub->setEndInclusive(TRUE);
// Handling a constant with scale that exceeds that of the range
// column could result in an empty (i.e., start>end) range. For example,
// if n is a numeric(4,2), the predicate n = 12.341 will result in
// the range 12.35..12.34, which is appropriate since the predicate
// is guaranteed to be false by the type constraint of the column.
if (sub->startIsMin() || sub->endIsMax() || sub->start <= sub->end)
placeSubrange(sub);
else
delete sub;
}
else
{
Subrange<double>* sub = new (mvqrHeap_) Subrange<double>(logLevel_);
sub->setUnparsedStart(unparsedStart);
sub->setUnparsedEnd(unparsedEnd);
if (start)
sub->start = getDoubleValue(start, logLevel_);
else
sub->setStartIsMin(TRUE);
if (end)
sub->end = getDoubleValue(end, logLevel_);
else
sub->setEndIsMax(TRUE);
sub->setStartInclusive(startInclusive);
sub->setEndInclusive(endInclusive);
placeSubrange(sub);
}
break;
// In some cases, constant folding of char expressions produces a varchar
// constant, so we have to take a possible length field into account.
case NA_CHARACTER_TYPE:
{
Lng32 headerBytes;
const NAType* startType = (start ? start->getType() : NULL);
const NAType* endType = (end ? end->getType() : NULL);
// Alignment is 2 for UCS2, 1 for single-byte char set.
if (type->getDataAlignment() == 2)
{
// Unicode string.
Subrange<RangeWString>* sub = new (mvqrHeap_) Subrange<RangeWString>(logLevel_);
sub->setUnparsedStart(unparsedStart);
sub->setUnparsedEnd(unparsedEnd);
if (start)
{
headerBytes = startType->getVarLenHdrSize() +
startType->getSQLnullHdrSize();
sub->start.remove(0)
.append((const NAWchar*)start->getConstValue()
+ (headerBytes / sizeof(NAWchar)),
(start->getStorageSize() - headerBytes)
/ sizeof(NAWchar));
}
else
sub->setStartIsMin(TRUE);
if (end)
{
headerBytes = endType->getVarLenHdrSize() +
endType->getSQLnullHdrSize();
sub->end.remove(0)
.append((const NAWchar*)end->getConstValue()
+ (headerBytes / sizeof(NAWchar)),
(end->getStorageSize() - headerBytes)
/ sizeof(NAWchar));
}
else
sub->setEndIsMax(TRUE);
sub->setStartInclusive(startInclusive);
sub->setEndInclusive(endInclusive);
placeSubrange(sub);
}
else
{
// Latin1 string.
Subrange<RangeString>* sub = new (mvqrHeap_) Subrange<RangeString>(logLevel_);
sub->setUnparsedStart(unparsedStart);
sub->setUnparsedEnd(unparsedEnd);
if (start)
{
headerBytes = startType->getVarLenHdrSize() +
startType->getSQLnullHdrSize();
sub->start.remove(0)
.append((const char*)start->getConstValue() + headerBytes,
start->getStorageSize() - headerBytes);
}
else
sub->setStartIsMin(TRUE);
if (end)
{
headerBytes = endType->getVarLenHdrSize() +
endType->getSQLnullHdrSize();
sub->end.remove(0)
.append((const char*)end->getConstValue() + headerBytes,
end->getStorageSize() - headerBytes);
}
else
sub->setEndIsMax(TRUE);
sub->setStartInclusive(startInclusive);
sub->setEndInclusive(endInclusive);
placeSubrange(sub);
}
}
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_RANGE, logLevel_,
FALSE, QRDescriptorException,
"Unhandled data type: %d", typeQual);
break;
}
} // addSubrange(ConstValue*...
ItemExpr* OptRangeSpec::getCheckConstraintPred(ItemExpr* checkConstraint)
{
if (checkConstraint->getOperatorType() != ITM_CASE)
{
QRLogger::log(CAT_SQL_COMP_RANGE, logLevel_,
"Expected ITM_CASE but found operator %d.",
checkConstraint->getOperatorType());
return NULL;
}
ItemExpr* itemExpr = checkConstraint->child(0);
if (itemExpr->getOperatorType() != ITM_IF_THEN_ELSE)
{
QRLogger::log(CAT_SQL_COMP_RANGE, logLevel_,
"Expected ITM_IF_THEN_ELSE but found operator %d.",
itemExpr->getOperatorType());
return NULL;
}
// Child of the if-then-else is either is_false, which is the parent of the
// predicate (for most check constraints), or the predicate itself (for
// isnotnull or the check option of a view).
itemExpr = itemExpr->child(0);
if (itemExpr->getOperatorType() == ITM_IS_FALSE)
return itemExpr->child(0);
else
return itemExpr;
}
void OptRangeSpec::intersectCheckConstraints(QRDescGenerator* descGen,
ValueId colValId)
{
QRTRACER("intersectCheckConstraints");
// Check and Not Null constraints.
//
ItemExpr* itemExpr = colValId.getItemExpr();
if (itemExpr->getOperatorType() == ITM_VEG_REFERENCE)
{
// For a vegref, intersect all constraints applied to any member.
const ValueIdSet& vidSet = static_cast<VEGReference*>(itemExpr)
->getVEG()->getAllValues();
for (ValueId vid=vidSet.init(); vidSet.next(vid); vidSet.advance(vid))
{
if (vid.getItemExpr()->getOperatorType() == ITM_BASECOLUMN)
intersectCheckConstraints(descGen, vid);
}
return;
}
else if (itemExpr->getOperatorType() != ITM_BASECOLUMN)
{
QRLogger::log(CAT_SQL_COMP_RANGE, logLevel_,
"Nonfatal unexpected result: range column operator type is "
"%d instead of ITM_BASECOLUMN.", itemExpr->getOperatorType());
return;
}
#ifdef _DEBUG
const NATable* tbl = colValId.getNAColumn()->getNATable();
const CheckConstraintList& checks = tbl->getCheckConstraints();
for (CollIndex c=0; c<checks.entries(); c++)
{
QRLogger::log(CAT_SQL_COMP_RANGE, LL_DEBUG,
"Check constraint on table %s: %s",
tbl->getTableName().getObjectName().data(),
checks[c]->getConstraintText().data());
}
#endif
OptRangeSpec* checkRange = NULL;
ItemExpr* checkPred;
const ValueIdList& checkConstraints =
(static_cast<BaseColumn*>(itemExpr))->getTableDesc()->getCheckConstraints();
for (CollIndex i=0; i<checkConstraints.entries(); i++)
{
checkPred = getCheckConstraintPred(checkConstraints[i].getItemExpr());
if (checkPred)
{
checkRange = new(mvqrHeap_) OptRangeSpec(descGen, mvqrHeap_);
checkRange->setRangeColValueId(colValId);
checkRange->setType(colValId.getType().newCopy(mvqrHeap_));
if (checkRange->buildRange(checkPred))
// Call the RangeSpec version of intersect; this avoids trying to
// modify the original ItemExpr with the check constraint pred.
RangeSpec::intersectRange(checkRange);
delete checkRange;
}
}
}
// Add type-implied constraint for numeric, datetime, and interval types.
void OptRangeSpec::intersectTypeConstraint(QRDescGenerator* descGen,
ValueId colValId)
{
QRTRACER("intersectTypeConstraint");
NABoolean isExact;
const NAType& colType = colValId.getType();
NABuiltInTypeEnum typeQual = colType.getTypeQualifier();
switch (typeQual)
{
case NA_NUMERIC_TYPE:
{
const NumericType& numType = static_cast<const NumericType&>(colType);
isExact = numType.isExact();
if (isExact && numType.getFSDatatype() == REC_BIN64_SIGNED)
return; // No type restriction for 64-bit integers
}
break;
case NA_DATETIME_TYPE:
case NA_INTERVAL_TYPE:
isExact = TRUE;
break;
default:
return; // No type constraint applied for other types
}
OptRangeSpec* typeRange = NULL;
if (isExact) // Exact numeric, datetime, interval
{
// Add the subrange implied by the type. If the type is largeint,
// nothing is needed here and no type range will be created. We don't
// need to set the type of the range specs created in this function,
// because addSubrange (which looks at the type) is bypassed and we call
// placeSubrange directly.
typeRange = new(mvqrHeap_) OptRangeSpec(descGen, mvqrHeap_);
Int64 start, end;
SubrangeBase::getExactNumericMinMax(colType, start, end, logLevel_);
Subrange<Int64>* numSubrange = new(mvqrHeap_) Subrange<Int64>(logLevel_);
numSubrange->setUnparsedStart("");
numSubrange->setUnparsedEnd("");
numSubrange->start = start;
numSubrange->end = end;
numSubrange->setStartIsMin(FALSE);
numSubrange->setEndIsMax(FALSE);
numSubrange->setStartInclusive(TRUE);
numSubrange->setEndInclusive(TRUE);
typeRange->placeSubrange(numSubrange);
}
else // approximate numeric
{
switch (colType.getFSDatatype())
{
case REC_IEEE_FLOAT32:
{
typeRange = new(mvqrHeap_) OptRangeSpec(descGen, mvqrHeap_);
Subrange<double>* dblSubrange = new(mvqrHeap_) Subrange<double>(logLevel_);
dblSubrange->end = static_cast<const NumericType&>(colType).getMaxValue();
dblSubrange->setUnparsedStart("");
dblSubrange->setUnparsedEnd("");
dblSubrange->start = -(dblSubrange->end);
dblSubrange->setStartIsMin(FALSE);
dblSubrange->setEndIsMax(FALSE);
dblSubrange->setStartInclusive(TRUE);
dblSubrange->setEndInclusive(TRUE);
typeRange->placeSubrange(dblSubrange);
}
break;
case REC_IEEE_FLOAT64:
// No range restriction needed.
break;
default:
QRLogger::log(CAT_SQL_COMP_RANGE, logLevel_,
"No case in intersectTypeConstraint() for "
"approximate numeric of type %d",
colType.getFSDatatype());
break;
}
}
if (typeRange)
{
typeRange->setNullIncluded(TRUE); // Null always part of a type range
// Call the RangeSpec version of intersect; this avoids trying to
// modify the original ItemExpr with the type constraint pred.
RangeSpec::intersectRange(typeRange);
delete typeRange;
}
} // intersectTypeConstraint()
void OptRangeSpec::addConstraints(QRDescGenerator* descGen)
{
QRTRACER("addConstraints");
ValueId colValId = getRangeColValueId();
if (colValId == NULL_VALUE_ID)
colValId = getRangeJoinPredId();
if (colValId == NULL_VALUE_ID)
return;
// Add constraint implied by the column's type.
intersectTypeConstraint(descGen, colValId);
// Check constraints can be added and dropped at will, and so can be in
// different states when the MV is created and when the query is matched.
// Therefore we utilize check constraints only for query descriptors. This
// may result in a NotProvided instead of a Provided match, but avoids the
// need to invalidate all the MV descriptors using a table when one of the
// table's check constraints is added/dropped.
if (descGen->getDescriptorType() == ET_QueryDescriptor)
intersectCheckConstraints(descGen, colValId);
// else
// assertLogAndThrow1(descGen->getDescriptorType() == ET_MVDescriptor,
// QRDescriptorException,
// "Invalid descriptor type -- %d",
// descGen->getDescriptorType());
}
void OptRangeSpec::addColumnsUsed(const QRDescGenerator* descGen)
{
if (descGen != descGenerator_)
descGenerator_->mergeDescGenerator(descGen);
}
#define AVR_STATE0 0
#define AVR_STATE1 1
#define AVR_STATE2 2
NABoolean OptRangeSpec::buildRange(ItemExpr* origPredExpr)
{
QRTRACER("buildRange");
ConstValue *startValue, *endValue;
OperatorTypeEnum leftOp, rightOp;
NABoolean isRange = TRUE;
NABoolean reprocessAND = FALSE;
//
// buildRange() can be called recursively for all the items in an IN-list
// at a point in time when we are already many levels deep in other
// recursion (e.g. Scan::applyAssociativityAndCommutativity() ). Consequently,
// we may not have much of our stack space available at the time, so
// we must eliminate the recursive calls to buildRange() 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 buildRange() code for the ItemExpr
// node that we are currently working on.
//
ARRAY( ItemExpr * ) IEarray(mvqrHeap_, 10) ; //Initially 10 elements (no particular reason to choose 10)
ARRAY( Int16 ) state(mvqrHeap_, 10) ; //These ARRAYs will grow automatically as needed.)
Int32 currIdx = 0 ;
IEarray.insertAt( currIdx, origPredExpr ) ; //Initialize 1st element in the ARRAYs
state.insertAt( currIdx, AVR_STATE0 ) ;
while( currIdx >= 0 && isRange )
{
ItemExpr * predExpr = IEarray[currIdx] ; //Get ptr to the current IE
OperatorTypeEnum op = predExpr->getOperatorType();
switch (op)
{
case ITM_AND:
// Check for a bounded subrange, from BETWEEN predicate, etc. If not of
// this form, the predicate was presumably too complex to convert to
// conjunctive normal form without a combinatorial explosion of clauses,
// and we handle it by creating separate range objects for each operand
// of the AND, intersecting them, and then unioning the result with the
// primary range.
leftOp = predExpr->child(0)->getOperatorType();
rightOp = predExpr->child(1)->getOperatorType();
if (leftOp == ITM_LESS || leftOp == ITM_LESS_EQ)
{
if (rightOp == ITM_GREATER || rightOp == ITM_GREATER_EQ)
{
endValue = getConstOperand(predExpr->child(0));
if (endValue)
{
startValue = getConstOperand(predExpr->child(1));
if (startValue)
addSubrange(startValue, endValue,
rightOp == ITM_GREATER_EQ,
leftOp == ITM_LESS_EQ);
else
isRange = FALSE;
}
else
isRange = FALSE;
}
else
reprocessAND = TRUE;
}
else if (leftOp == ITM_GREATER || leftOp == ITM_GREATER_EQ)
{
if (rightOp == ITM_LESS || rightOp == ITM_LESS_EQ)
{
startValue = getConstOperand(predExpr->child(0));
if (startValue)
{
endValue = getConstOperand(predExpr->child(1));
if (endValue)
addSubrange(startValue, endValue,
leftOp == ITM_GREATER_EQ,
rightOp == ITM_LESS_EQ);
else
isRange = FALSE;
}
else
isRange = FALSE;
}
else
reprocessAND = TRUE;
}
else
reprocessAND = TRUE;
// AND was used in a sense other than that of a BETWEEN predicate, so
// we must intersect the operand ranges before adding the result to the
// overall range.
if (reprocessAND)
{
OptRangeSpec *leftRange = NULL, *rightRange = NULL;
leftRange = createRangeSpec(descGenerator_, predExpr->child(0),
mvqrHeap_, forNormalizer());
if (!leftRange)
isRange = FALSE;
else if (!rangeSubjectIsSet())
setRangeSubject(leftRange);
else if (!sameRangeSubject(leftRange))
isRange = FALSE;
if (isRange)
{
rightRange = createRangeSpec(descGenerator_, predExpr->child(1),
mvqrHeap_, forNormalizer());
if (rightRange && rightRange->sameRangeSubject(leftRange))
{
leftRange->intersectRange(rightRange);
// Call only the superclass part of unionRange(); we want
// to avoid the part that modifies the originalItemExpr_;
RangeSpec::unionRange(leftRange);
}
else
isRange = FALSE;
}
delete leftRange;
delete rightRange;
}
break;
case ITM_OR:
if ( state[currIdx] == AVR_STATE0 )
{
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, predExpr->child(0) ) ;
continue ;
}
else if ( state[currIdx] == 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, predExpr->child(1) ) ;
continue ;
}
else
state.insertAt( currIdx, AVR_STATE0 ); // We are done processing predExpr
break ;
case ITM_EQUAL:
startValue = endValue = getConstOperand(predExpr);
if (startValue)
addSubrange(startValue, endValue, TRUE, TRUE);
else
isRange = FALSE;
break;
case ITM_LESS:
endValue = getConstOperand(predExpr);
if (endValue)
addSubrange(NULL, endValue, TRUE, FALSE);
else
isRange = FALSE;
break;
case ITM_LESS_EQ:
endValue = getConstOperand(predExpr);
if (endValue)
addSubrange(NULL, endValue, TRUE, TRUE);
else
isRange = FALSE;
break;
case ITM_GREATER:
startValue = getConstOperand(predExpr);
if (startValue)
addSubrange(startValue, NULL, FALSE, TRUE);
else
isRange = FALSE;
break;
case ITM_GREATER_EQ:
startValue = getConstOperand(predExpr);
if (startValue)
addSubrange(startValue, NULL, TRUE, TRUE);
else
isRange = FALSE;
break;
case ITM_NOT_EQUAL:
startValue = endValue = getConstOperand(predExpr);
if (startValue)
{
addSubrange(NULL, endValue, TRUE, FALSE);
addSubrange(startValue, NULL, FALSE, TRUE);
}
else
isRange = FALSE;
break;
case ITM_BETWEEN:
startValue = getConstOperand(predExpr);
if (startValue)
{
endValue = getConstOperand(predExpr, 2);
if (endValue)
//@ZX The Between class has private member variables
// leftBoundryIncluded_ and rightBoundryIncluded_ that have
// no accessor functions. If it is in fact possible for the
// boundaries to be noninclusive, we will need access to
// these properties.
addSubrange(startValue, endValue, TRUE, TRUE);
}
else
isRange = FALSE;
break;
case ITM_IS_NULL:
case ITM_IS_NOT_NULL:
{
ValueId colVid;
ItemExpr* child = predExpr->child(0);
if (child->getOperatorType() == ITM_VEG_REFERENCE)
{
// If this range is for an expression, the pred is not on it.
if (rangeExpr_)
isRange = FALSE;
else
{
VEG* veg = ((VEGReference*)child)->getVEG();
if (forNormalizer())
colVid = veg->getVEGReference()->getValueId();
else
colVid = getBaseCol(veg->getAllValues());
if (rangeColValueId_ == NULL_VALUE_ID)
{
rangeColValueId_ = colVid;
EqualitySet* eqSet =
descGenerator_->getEqualitySet(&rangeColValueId_);
if (eqSet)
{
rangeJoinPredId_ = (QRValueId)eqSet->getJoinPredId();
setType(eqSet->getType());
}
else
{
rangeJoinPredId_ = (QRValueId)NULL_VALUE_ID;
setType(&((ValueId)rangeColValueId_).getType());
}
}
else if (rangeColValueId_ != colVid)
isRange = FALSE;
}
}
else
{
// The left side of the pred is an expression. If this range is
// for a simple column, it doesn't match.
if (rangeColValueId_ != NULL_VALUE_ID)
isRange = FALSE;
else if (!rangeExpr_)
{
setRangeExpr(child); // sets rangeExpr_, rangeExprText_
EqualitySet* eqSet =
descGenerator_->getEqualitySet(&rangeExprText_);
if (eqSet)
{
rangeJoinPredId_ = (QRValueId)eqSet->getJoinPredId();
setType(eqSet->getType());
}
else
{
rangeJoinPredId_ = (QRValueId)NULL_VALUE_ID;
setType(&rangeExpr_->getValueId().getType());
}
}
else
{
NAString exprText;
child->unparse(exprText, OPTIMIZER_PHASE, MVINFO_FORMAT);
if (rangeExprText_ != exprText)
isRange = FALSE;
}
}
// Now that it has been validated as a range pred, take the appropriate
// action depending on whether the op is IS NULL or IS NOT NULL.
if (isRange)
{
if (op == ITM_IS_NULL)
nullIncluded_ = TRUE;
else
{
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_, op == ITM_IS_NOT_NULL,
QRLogicException,
"op must be ITM_IS_NOT_NULL here");
addSubrange(NULL, NULL, TRUE, TRUE);
}
}
}
break;
default:
isRange = FALSE;
break;
}
if ( state[currIdx] == AVR_STATE0 )
currIdx-- ; // Go back to the parent node & continue working on it.
}
return isRange;
} // buildRange()
// Local helper function to cast int64, which, as the widest integral type is
// used for rangespec processing, to the actual type of the column the rangespec
// applies to. numBuf is declared as Int64* to ensure proper alignment for all
// possible integral types.
static void downcastRangespecInt(Int64 val, Lng32 scale, NAType*& type,
Int64* numBuf, NAMemory* heap)
{
Lng32 precision = 0; // Only used for non-integral exact numeric
// For non-integer values with leading 0's, have to adjust precision to
// equal scale. For example, the raw value 23 with a scale of 4 (.0023)
// will initially be calculated to have precision 2 instead of 4.
if (scale) // non-integral value
{
precision = (Lng32)log10(::abs((double)val)) + 1;
if (scale > precision)
precision = scale;
}
if (val <= SHRT_MAX && val >= SHRT_MIN)
{
*((Int16*)numBuf) = static_cast<Int16>(val);
if (scale == 0)
type = new(heap) SQLSmall(heap, TRUE, FALSE);
else
type = new(heap) SQLNumeric(heap, sizeof(Int16), precision, scale,
TRUE, FALSE);
}
else if (val <= INT_MAX && val >= INT_MIN)
{
*((Int32*)numBuf) = static_cast<Int32>(val);
if (scale == 0)
type = new(heap) SQLInt(heap, TRUE, FALSE);
else
type = new(heap) SQLNumeric(heap, sizeof(Int32), precision, scale,
TRUE, FALSE);
}
else
{
*numBuf = val;
if(scale == 0)
type = new(heap) SQLLargeInt(heap, TRUE, FALSE);
else
type = new(heap) SQLNumeric(heap, sizeof(Int64), precision, scale,
TRUE, FALSE);
}
}
// Instantiate a ConstValue using the actual type of the passed Int64 value.
// The actual type is indicated by the type parameter, and could be any type
// represented as an integer in a rangespec.
ConstValue* OptRangeSpec::reconstituteInt64Value(NAType* type, Int64 val) const
{
// Use these for the textual representation of a constant value, which is
// passed to the ConstValue ctor.
char constValTextBuffer[50];
NAString constValTextStr;
NABuiltInTypeEnum typeQual = type->getTypeQualifier();
switch (typeQual)
{
case NA_NUMERIC_TYPE:
{
sprintf(constValTextBuffer, PF64, val);
constValTextStr = constValTextBuffer;
NumericType* numType = static_cast<NumericType*>(type);
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
numType->isExact(),
QRLogicException,
"Expecting exact numeric type in "
"reconstituteInt64Value");
Int64 numBuf;
NAType* constType = NULL;
downcastRangespecInt(val, numType->getScale(), constType, &numBuf, mvqrHeap_);
return new(mvqrHeap_) ConstValue(constType, &numBuf,
constType->getNominalSize(),
&constValTextStr, mvqrHeap_);
}
break;
case NA_DATETIME_TYPE:
{
DatetimeType* dtType = static_cast<DatetimeType*>(type);
ULng32 tsFieldValues[DatetimeValue::N_DATETIME_FIELDS];
DatetimeValue dtv("", 0);
switch (dtType->getSubtype())
{
case DatetimeType::SUBTYPE_SQLDate:
// Since Julian timestamp is calculated from noon on the base day,
// the timestamp corresponding to a given date (without time) is
// always x.5 days, where x+1 is the actual number of days passed.
// We truncate the fractional part when we store it, so it has to
// be added back here. .5 could be added, but to be safe we add a
// full day minus a microsecond, which is then truncated to the
// proper value.
DatetimeValue::decodeTimestamp
((val+1) * SubrangeBase::MICROSECONDS_IN_DAY - 1,
dtType->getFractionPrecision(), tsFieldValues);
dtv.setValue(REC_DATE_YEAR, REC_DATE_DAY,
dtType->getFractionPrecision(), tsFieldValues);
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
dtv.isValid(), QRLogicException,
"Invalid date value reconstructed from Julian timestamp");
constValTextStr = dtv.getValueAsString(*dtType);
return new(mvqrHeap_) ConstValue(new(mvqrHeap_)SQLDate(mvqrHeap_, FALSE),
(void*)dtv.getValue(), dtv.getValueLen(),
&constValTextStr, mvqrHeap_);
break;
case DatetimeType::SUBTYPE_SQLTime:
// The fractional seconds part is represented not by a number
// of microseconds but by the numerator of the fraction having
// denominator equal to 10^fractionPrecision.
tsFieldValues[DatetimeValue::FRACTION] =
(ULng32)((val % 1000000)
/ (Int64)pow(10, 6 - dtType->getFractionPrecision()));
val /= 1000000;
tsFieldValues[DatetimeValue::SECOND] = (ULng32)(val % 60);
val /= 60;
tsFieldValues[DatetimeValue::MINUTE] = (ULng32)(val % 60);
val /= 60;
tsFieldValues[DatetimeValue::HOUR] = (ULng32)val;
dtv.setValue(REC_DATE_HOUR, REC_DATE_SECOND,
dtType->getFractionPrecision(), tsFieldValues);
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
dtv.isValid(), QRLogicException,
"Invalid time value reconstructed from Int64 value");
constValTextStr = dtv.getValueAsString(*dtType);
return new(mvqrHeap_)
ConstValue(new(mvqrHeap_)SQLTime(mvqrHeap_, FALSE,
dtType->getFractionPrecision()),
(void*)dtv.getValue(), dtv.getValueLen(),
&constValTextStr, mvqrHeap_);
break;
case DatetimeType::SUBTYPE_SQLTimestamp:
// We represent these as a number of microseconds, so fractional
// precision does not have to be taken into account.
DatetimeValue::decodeTimestamp(val,
dtType->getFractionPrecision(),
tsFieldValues);
dtv.setValue(REC_DATE_YEAR, REC_DATE_SECOND,
dtType->getFractionPrecision(), tsFieldValues);
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
dtv.isValid(), QRLogicException,
"Invalid timestamp value reconstructed from Julian timestamp");
constValTextStr = dtv.getValueAsString(*dtType);
return new(mvqrHeap_) ConstValue(new(mvqrHeap_)
SQLTimestamp(mvqrHeap_, FALSE,
dtType->getFractionPrecision()),
(void*)dtv.getValue(),
dtv.getValueLen(),
&constValTextStr, mvqrHeap_);
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_RANGE, logLevel_,
FALSE, QRLogicException,
"Unknown datetime subtype -- %d",
dtType->getSubtype());
return NULL;
break;
}
}
break;
case NA_INTERVAL_TYPE:
{
Int64 origVal = val; // Use this later to check for precision loss.
Int64 scaledVal; // Compare to origVal to check precision loss.
Int64 scaleFactor;
IntervalType* intvlType = static_cast<IntervalType*>(type);
// For rangespec analysis, all values are converted to months or
// microseconds. Change it back to its normal internal representation
// in terms of units of the end field.
switch (intvlType->getEndField())
{
case REC_DATE_YEAR:
val /= 12;
scaledVal = val * 12;
break;
case REC_DATE_MONTH:
// No conversion necessary.
scaledVal = val;
break;
case REC_DATE_DAY:
val /= (24LL * 60 * 60000000);
scaledVal = val * (24LL * 60 * 60000000);
break;
case REC_DATE_HOUR:
val /= (60LL * 60000000);
scaledVal = val * (60LL * 60000000);
break;
case REC_DATE_MINUTE:
val /= 60000000LL;
scaledVal = val * 60000000;
break;
case REC_DATE_SECOND:
scaleFactor = (Int64)pow(10, 6 - intvlType->getFractionPrecision());
val /= scaleFactor;
scaledVal = val * scaleFactor;
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_RANGE, logLevel_,
FALSE, QRLogicException,
"Invalid end field for interval type -- %d",
intvlType->getEndField());
break;
}
// Calculate the leading field precision of the interval constant, when
// converted to units of its end field.
UInt32 leadingPrec = 0;
Int64 tempVal = (val >= 0 ? val : -val); // abs has no overload for Int64
while (tempVal > 0)
{
leadingPrec++;
tempVal /= 10;
}
// If the field is seconds, some of the digits we just counted may be
// fractional seconds, so we subtract them from what we counted.
if (intvlType->getEndField() == REC_DATE_SECOND)
{
UInt32 fracSecPrec = intvlType->getFractionPrecision();
if (leadingPrec <= fracSecPrec) // could be < for a fraction of a
leadingPrec = 2; // sec with leading zeroes
else
leadingPrec -= fracSecPrec;
}
// There are cases where we can come up with leading precision of 0,
// and this causes a problem. Make it min 2, which is the default.
if (leadingPrec < 2)
leadingPrec = 2;
// Any discrepancy in precision between the range column and the
// constant value should have been dealt with when the value was
// converted to months or microseconds in getInt64ValueFromInterval().
assertLogAndThrow2(CAT_SQL_COMP_RANGE, logLevel_,
origVal == scaledVal, QRLogicException,
"Precision lost in conversion to interval type: "
"value in microseconds (or months) = %Ld, "
"interval end field = %d",
origVal, intvlType->getEndField());
// IntervalValue::setValue() will cast the Int64 value to the appropriate
// type if you pass the length of that type, but negative values seem
// to be stored incorrectly unless they are 8 bytes (see bug 2773), so
// we just use 8 bytes always.
IntervalValue intvlVal(NULL, 0);
intvlVal.setValue(val, SQL_LARGE_SIZE);
constValTextStr = intvlVal.getValueAsString(*intvlType);
// The interval type for the constant is derived from that of the column
// the predicate is on, but is always a single field (the end field of
// the column type), and has the maximum possible leading field precision
// to prevent overflow in case the value is outside the range for the
// column's declared type (can happen when the rangespec is created for
// the Normalizer rather than MVQR, because type constraints are not
// incorporated in that case). See bug 2974.
return new(mvqrHeap_) ConstValue(new(mvqrHeap_)SQLInterval
(mvqrHeap_, FALSE,
intvlType->getEndField(),
leadingPrec,
intvlType->getEndField(),
intvlType->getFractionPrecision()),
(void*)intvlVal.getValue(),
intvlVal.getValueLen(),
&constValTextStr, mvqrHeap_);
}
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_RANGE, logLevel_, FALSE, QRLogicException,
"Type not handled by reconstituteInt64Value() -- %d",
typeQual);
return NULL;
break;
}
// make the compiler happy
return NULL;
} // reconstituteInt64Value()
// Instantiate a ConstValue using the passed double value.
ConstValue* OptRangeSpec::reconstituteDoubleValue(NAType* type, Float64 val) const
{
NABuiltInTypeEnum typeQual = type->getTypeQualifier();
if (typeQual != NA_NUMERIC_TYPE)
{
assertLogAndThrow1(CAT_SQL_COMP_RANGE, logLevel_, FALSE, QRLogicException,
"Type not handled by reconstituteDoubleValue() -- %d",
typeQual);
return NULL;
}
// Use these for the textual representation of a constant value, which is
// passed to the ConstValue ctor.
char constValTextBuffer[50];
NAString constValTextStr;
sprintf(constValTextBuffer, "%g", val);
constValTextStr = constValTextBuffer;
NumericType* numType = static_cast<NumericType*>(type);
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
!numType->isExact(),
QRLogicException,
"Expecting approximate numeric type in "
"reconstituteDoubleValue");
NAType* constType = new(mvqrHeap_) SQLDoublePrecision(mvqrHeap_, FALSE);
return new(mvqrHeap_) ConstValue(constType, &val, constType->getNominalSize(),
&constValTextStr, mvqrHeap_);
} // reconstituteDoubleValue()
// Generate a left-linear OR backbone of equality predicates corresponding
// to the values within the subrange.
ItemExpr* OptRangeSpec::makeSubrangeORBackbone(SubrangeBase* subrange,
ItemExpr* subrangeItem) const
{
QRTRACER("makeSubrangeOrBackbone");
NAType* type = getType()->newCopy(mvqrHeap_);
NAType* int64Type = new (mvqrHeap_) SQLLargeInt(mvqrHeap_, TRUE, FALSE );
type->resetSQLnullFlag();
type->resetSQLnullHdrSize();
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
type, QRDescriptorException,
"Call to getType() returned NULL in "
"OptRangeSpec::makeSubrangeOrBackbone().");
NABuiltInTypeEnum typeQual = type->getTypeQualifier();
assertLogAndThrow1(CAT_SQL_COMP_RANGE, logLevel_,
typeQual == NA_DATETIME_TYPE ||
typeQual == NA_INTERVAL_TYPE ||
(typeQual == NA_NUMERIC_TYPE &&
static_cast<const NumericType*>(type)->isExact()),
QRDescriptorException,
"Invalid type for makeSubrangeOrBackbone() -- %d", typeQual);
Subrange<Int64>* intSubrange = (Subrange<Int64>*)subrange;
Int64 startVal = intSubrange->start;
Int64 endVal = intSubrange->end;
ItemExpr* top = NULL; // current top of backbone; eventual return value
ItemExpr* eqExpr; // construct each rangeitem=value pred here...
ConstValue* cv; // ...using equality to this constant
for (Int64 val=startVal; val<=endVal; val++)
{
cv = reconstituteInt64Value(type, val);
eqExpr = new(mvqrHeap_) BiRelat(ITM_EQUAL, subrangeItem, cv);
eqExpr->synthTypeAndValueId();
if (top)
top = new(mvqrHeap_) BiLogic(ITM_OR, top, eqExpr);
else
top = eqExpr;
}
return top;
} // makeSubrangeORBackbone
ItemExpr* OptRangeSpec::makeSubrangeItemExpr(SubrangeBase* subrange,
ItemExpr* subrangeItem) const
{
QRTRACER("makeSubrangeItemExpr");
// ItemExpr that we will build and return, representing the subrange.
ItemExpr* itemExpr;
// Nodes that the start and end values of the subrange will be attached
// to (as the 2nd child) as necessary.
ItemExpr *parentOfStart = NULL, *parentOfEnd = NULL;
// If the subrange is derived from an IN list or a disjunction of equality
// predicates, return an OR backbone of ITM_EQUALs.
if ((subrange->getSpecifiedValueCount() > 0))
return makeSubrangeORBackbone(subrange, subrangeItem);
// Build the tree, except for the ConstValue subtrees that will be attached
// later, after the underlying type of the subrange is determined.
// parentOfStart and parentOfEnd mark the nodes to attach them to.
if (subrange->isSingleValue())
{
// A single-point subrange may have been the result of intersection of
// two ranges that left it with adjustment flags adopted from the another
// subranges. They aren't relevant when the subrange represents a single
// value, and will distort the value used if left in place.
subrange->setStartAdjustment(0);
subrange->setEndAdjustment(0);
itemExpr = parentOfStart
= new(mvqrHeap_) BiRelat(ITM_EQUAL, subrangeItem);
}
else if (subrange->endIsMax())
if (subrange->startInclusive() && subrange->getStartAdjustment() == 0)
itemExpr = parentOfStart
= new(mvqrHeap_) BiRelat(ITM_GREATER_EQ, subrangeItem);
else
itemExpr = parentOfStart
= new(mvqrHeap_) BiRelat(ITM_GREATER, subrangeItem);
else if (subrange->startIsMin())
if (subrange->endInclusive() && subrange->getEndAdjustment() == 0)
itemExpr = parentOfEnd
= new(mvqrHeap_) BiRelat(ITM_LESS_EQ, subrangeItem);
else
itemExpr = parentOfEnd
= new(mvqrHeap_) BiRelat(ITM_LESS, subrangeItem);
else
{
itemExpr = new(mvqrHeap_) BiLogic(ITM_AND);
if (subrange->startInclusive() && subrange->getStartAdjustment() == 0)
itemExpr->child(0) = parentOfStart
= new(mvqrHeap_) BiRelat(ITM_GREATER_EQ, subrangeItem);
else
itemExpr->child(0) = parentOfStart
= new(mvqrHeap_) BiRelat(ITM_GREATER, subrangeItem);
if (subrange->endInclusive() && subrange->getEndAdjustment() == 0)
itemExpr->child(1) = parentOfEnd
= new(mvqrHeap_) BiRelat(ITM_LESS_EQ, subrangeItem);
else
itemExpr->child(1) = parentOfEnd
= new(mvqrHeap_) BiRelat(ITM_LESS, subrangeItem);
}
// Now the item expression is complete except for filling in the constants
// at the appropriate locations, denoted by the parentOfStart and parentOfEnd
// ItemExpr ptrs, each of which is null if not applicable (e.g., an unbounded
// range will only use either the start or end value, not both).
NAType* type = getType()->newCopy(mvqrHeap_);
type->resetSQLnullFlag();
type->resetSQLnullHdrSize();
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
type, QRDescriptorException,
"Call to getType() returned NULL in "
"OptRangeSpec::makeSubrangeItemExpr().");
NABuiltInTypeEnum typeQual = type->getTypeQualifier();
switch (typeQual)
{
case NA_NUMERIC_TYPE:
case NA_DATETIME_TYPE:
case NA_INTERVAL_TYPE:
if (typeQual == NA_DATETIME_TYPE ||
typeQual == NA_INTERVAL_TYPE ||
(typeQual == NA_NUMERIC_TYPE &&
static_cast<const NumericType*>(type)->isExact()))
{
Subrange<Int64>* intSubrange = (Subrange<Int64>*)subrange;
Int64 startVal = intSubrange->start - intSubrange->getStartAdjustment();
Int64 endVal = intSubrange->end + intSubrange->getEndAdjustment();
if (parentOfStart)
parentOfStart->child(1) = reconstituteInt64Value(type, startVal);
if (parentOfEnd)
parentOfEnd->child(1) = reconstituteInt64Value(type, endVal);
}
else
{
Subrange<double>* dblSubrange = (Subrange<double>*)subrange;
double startVal = dblSubrange->start;
double endVal = dblSubrange->end;
if (parentOfStart)
parentOfStart->child(1) = reconstituteDoubleValue(type, startVal);
if (parentOfEnd)
parentOfEnd->child(1) = reconstituteDoubleValue(type, endVal);
}
break;
case NA_CHARACTER_TYPE:
{
// Alignment is 2 for UCS2, 1 for single-byte char set.
if (type->getDataAlignment() == 2)
{
// Unicode string.
Subrange<RangeWString>* wcharSubrange =
(Subrange<RangeWString>*)subrange;
if (parentOfStart)
parentOfStart->child(1) =
new(mvqrHeap_) ConstValue(wcharSubrange->start);
if (parentOfEnd)
parentOfEnd->child(1) =
new(mvqrHeap_) ConstValue(wcharSubrange->end);
}
else
{
// Latin1 string.
Subrange<RangeString>* charSubrange
= (Subrange<RangeString>*)subrange;
if (parentOfStart)
parentOfStart->child(1) =
new(mvqrHeap_) ConstValue(charSubrange->start, ((CharType *)type)->getCharSet() );
if (parentOfEnd)
parentOfEnd->child(1) =
new(mvqrHeap_) ConstValue(charSubrange->end, ((CharType *)type)->getCharSet() );
}
}
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_RANGE, logLevel_,
FALSE, QRDescriptorException,
"Unhandled data type in "
"OptRangeSpec::makeSubrangeItemExpr: %d",
typeQual);
break;
}
// This NAType object is never used in the creation of a ConstValue.
delete type;
return itemExpr;
} // makeSubrangeItemExpr()
void OptRangeSpec::intersectRange(OptRangeSpec* other)
{
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
sameRangeSubject(other) || !other->rangeSubjectIsSet(),
QRDescriptorException,
"Intersecting with range on a different col/expr, or range "
"subject is not set");
// Call the superclass version to intersect the ranges, and mark this range
// spec as being an intersection. The isIntersection_ flag will cause the
// itemexpr representing the overall range to be created, so its value id
// can be used as the id attribute of the RangePred element, instead of that
// of the initial predicate on the range column/expr. This is critical,
// because the predicate corresponding to that id is the one used to carry
// out a NotProvided rewrite instruction for a range pred.
RangeSpec::intersectRange(other);
isIntersection_ = TRUE;
}
void OptRangeSpec::unionRange(OptRangeSpec* other)
{
assertLogAndThrow(CAT_SQL_COMP_RANGE, logLevel_,
sameRangeSubject(other) || !other->rangeSubjectIsSet(),
QRDescriptorException,
"Unioning with range on a different col/expr, or range "
"subject is not set");
// Call the superclass version to union the ranges.
RangeSpec::unionRange(other);
}
// normWA is not used here. It is a parameter because the OptNormRangeSpec
// redefinition of this virtual function needs it.
ItemExpr* OptRangeSpec::getRangeItemExpr(NormWA* normWA) const
{
QRTRACER("OptRangeSpec::getRangeItemExpr");
// ItemExpr representing the application of the range condition to the
// subject column or expression.
ItemExpr* rangeItemExpr = NULL;
// If there are no values in the range, the predicate can't be satisfied;
// return a FALSE itemexpr.
if (!subranges_.entries() && !nullIncluded_)
rangeItemExpr = new(mvqrHeap_) BoolVal(ITM_RETURN_FALSE);
// If all values are covered by the range, the underlying predicate is
// necessarily true; return a TRUE itemexpr.
if (coversFullRange())
rangeItemExpr = new(mvqrHeap_) BoolVal(ITM_RETURN_TRUE);
// coversFullrange is false but we may have a full range [-inf..+inf] except
// for NULL values. In other words the predicate 'a > -1 OR a < 1' is converted
// to 'a IS NOT NULL'
if ((rangeItemExpr == NULL) && ((subranges_.entries() == 1) &&
subranges_[0]->coversFullRange()))
rangeItemExpr = new(mvqrHeap_) UnLogic(ITM_IS_NOT_NULL, getRangeExpr());
if (rangeItemExpr == NULL)
{
// ItemExpr representing the column or expression the range applies to.
ItemExpr* subjectItemExpr = getRangeExpr();
// Create a left-deep OR backbone joining the conditions representing the
// subranges of this range.
SubrangeBase* subrange = NULL;
for (CollIndex i=0; i < subranges_.entries(); i++)
{
subrange = subranges_[i];
if (!rangeItemExpr)
rangeItemExpr = makeSubrangeItemExpr(subrange, subjectItemExpr);
else
rangeItemExpr =
new(mvqrHeap_) BiLogic(ITM_OR, rangeItemExpr,
makeSubrangeItemExpr(subrange,
subjectItemExpr));
}
// If the range permits the null value, OR in an isNull predicate.
if (nullIncluded_)
{
ItemExpr* isNullItemExpr = new(mvqrHeap_) UnLogic(ITM_IS_NULL,
subjectItemExpr);
if (!rangeItemExpr)
rangeItemExpr = isNullItemExpr;
else
rangeItemExpr = new(mvqrHeap_) BiLogic(ITM_OR,
rangeItemExpr,
isNullItemExpr);
}
}
rangeItemExpr->synthTypeAndValueId();
rangeItemExpr->setRangespecItemExpr(TRUE);
return rangeItemExpr;
} // OptRangeSpec::getRangeItemExpr()
ItemExpr* OptNormRangeSpec::getRangeItemExpr(NormWA* normWA) const
{
QRTRACER("OptNormRangeSpec::getRangeItemExpr");
// Call the superclass version to do the primary work of producing an
// ItemExpr from the range specification.
ItemExpr* rangeItemExpr = OptRangeSpec::getRangeItemExpr();
// Copy LIKE pred info from original expr.
OperatorTypeEnum op = rangeItemExpr->getOperatorType();
if ((op == ITM_AND)||(op == ITM_OR))
{
op = rangeItemExpr->child(0)->getOperatorType();
if ( (op == ITM_GREATER_EQ) ||(op == ITM_GREATER) ||
(op == ITM_LESS) ||(op == ITM_LESS_EQ))
{
BiRelat *br = (BiRelat *) rangeItemExpr->child(0).getPtr();
OperatorTypeEnum op1 = getOriginalItemExpr()->child(0)->getOperatorType();
if( (op1 == ITM_GREATER_EQ) || (op1 == ITM_GREATER) ||
(op1 == ITM_LESS) ||(op1 == ITM_LESS_EQ))
{
BiRelat *br1 = (BiRelat *) getOriginalItemExpr()->child(0).getPtr();
br->setAddedForLikePred(br1->addedForLikePred());
br->setOriginalLikeExprId(br1->originalLikeExprId());
br->setLikeSelectivity(br1->getLikeSelectivity());
if(br1->isSelectivitySetUsingHint())
{
br->setSelectivitySetUsingHint();
br->setSelectivityFactor(br1->getSelectivityFactor());
}
}
}
op = rangeItemExpr->child(1)->getOperatorType();
if ( (op == ITM_GREATER_EQ) ||(op == ITM_GREATER) ||
(op == ITM_LESS) ||(op == ITM_LESS_EQ))
{
BiRelat *br = (BiRelat *) rangeItemExpr->child(1).getPtr();
OperatorTypeEnum op1 = getOriginalItemExpr()->child(1)->getOperatorType();
if( (op1 == ITM_GREATER_EQ) || (op1 == ITM_GREATER) ||
(op1 == ITM_LESS) ||(op1 == ITM_LESS_EQ))
{
BiRelat *br1 = (BiRelat *) getOriginalItemExpr()->child(1).getPtr();
br->setAddedForLikePred(br1->addedForLikePred());
br->setOriginalLikeExprId(br1->originalLikeExprId());
br->setLikeSelectivity(br1->getLikeSelectivity());
if(br1->isSelectivitySetUsingHint())
{
br->setSelectivitySetUsingHint();
br->setSelectivityFactor(br1->getSelectivityFactor());
}
}
}
}
else if ( (op == ITM_GREATER_EQ) ||(op == ITM_GREATER) ||
(op == ITM_LESS) ||(op == ITM_LESS_EQ))
{
BiRelat *br = (BiRelat *) rangeItemExpr;
OperatorTypeEnum op1 = getOriginalItemExpr()->getOperatorType();
if( (op1 == ITM_GREATER_EQ) || (op1 == ITM_GREATER) ||
(op1 == ITM_LESS) ||(op1 == ITM_LESS_EQ))
{
BiRelat *br1 = (BiRelat *) getOriginalItemExpr();
br->setAddedForLikePred(br1->addedForLikePred());
br->setOriginalLikeExprId(br1->originalLikeExprId());
br->setLikeSelectivity(br1->getLikeSelectivity());
if(br1->isSelectivitySetUsingHint())
{
br->setSelectivitySetUsingHint();
br->setSelectivityFactor(br1->getSelectivityFactor());
}
}
}
if(((ItemExpr *)getOriginalItemExpr())->isSelectivitySetUsingHint())
{
rangeItemExpr->setSelectivitySetUsingHint();
rangeItemExpr->setSelectivityFactor(getOriginalItemExpr()->getSelectivityFactor());
}
// Now associate the range spec's itemexpr with the value id of the
// original expression.
ValueId vid = getOriginalItemExpr()->getValueId();
vid.replaceItemExpr(rangeItemExpr);
if (normWA)
rangeItemExpr->normalizeNode(*normWA);
return rangeItemExpr;
}
void OptNormRangeSpec::intersectRange(OptNormRangeSpec* other)
{
// Build the item expression tree corresponding to the combined range.
ItemExpr* otherItemExpr = const_cast<ItemExpr*>(other->getOriginalItemExpr());
if (originalItemExpr_ && otherItemExpr)
{
originalItemExpr_ = new(mvqrHeap_) BiLogic(ITM_AND,
originalItemExpr_,
otherItemExpr);
originalItemExpr_->synthTypeAndValueId(TRUE);
}
else
{
if (!originalItemExpr_)
{
QRLogger::log(CAT_SQL_COMP_RANGE, logLevel_,
"OptNormRangeSpec::intersectRange -- originalItemExpr_ is NULL.");
}
if (!otherItemExpr)
{
QRLogger::log(CAT_SQL_COMP_RANGE, logLevel_,
"OptNormRangeSpec::intersectRange -- other op has NULL ItemExpr.");
}
}
// Now call the superclass version to intersect the ranges.
OptRangeSpec::intersectRange(other);
}
void OptNormRangeSpec::unionRange(OptNormRangeSpec* other)
{
// Build the item expression tree corresponding to the combined range.
ItemExpr* otherItemExpr = const_cast<ItemExpr*>(other->getOriginalItemExpr());
if (originalItemExpr_ && otherItemExpr)
{
originalItemExpr_ = new(mvqrHeap_) BiLogic(ITM_OR,
originalItemExpr_,
otherItemExpr);
originalItemExpr_->synthTypeAndValueId(TRUE);
}
else
{
if (!originalItemExpr_)
{
QRLogger::log(CAT_SQL_COMP_RANGE, logLevel_,
"OptNormRangeSpec::unionRange -- originalItemExpr_ is NULL.");
}
if (!otherItemExpr)
{
QRLogger::log(CAT_SQL_COMP_RANGE, logLevel_,
"OptNormRangeSpec::unionRange -- other op has NULL ItemExpr.");
}
}
// Now call the superclass version to union the ranges.
OptRangeSpec::unionRange(other);
}
// Convert a date, time, or timestamp value to an integral form for use with
// range specifications.
static Int64 getInt64ValueFromDateTime(ConstValue* val,
const DatetimeType* rangeColType,
const DatetimeType* constType,
NABoolean& truncated,
logLevel level)
{
char dateRep[11];
Int64 i64val;
char* valPtr = (char*)val->getConstValue() +
val->getType()->getSQLnullHdrSize();
Lng32 rangeColFracSecPrec = rangeColType->getFractionPrecision();
Lng32 constValueFracSecPrec = constType->getFractionPrecision();
Lng32 fracSec;
truncated = FALSE;
switch (constType->getSubtype())
{
case DatetimeType::SUBTYPE_SQLDate:
memcpy(dateRep, valPtr, 4);
memset(dateRep+4, 0, 7);
i64val = DatetimeType::julianTimestampValue(dateRep, 11, 0)
/ SubrangeBase::MICROSECONDS_IN_DAY;
break;
case DatetimeType::SUBTYPE_SQLTime:
i64val = *valPtr * 3600 + *(valPtr+1) * 60 + *(valPtr+2);
i64val *= 1000000; // in microseconds
if (constValueFracSecPrec)
{
memcpy(&fracSec, valPtr+3, 4);
i64val += fracSec * (Int64)pow(10, 6-constValueFracSecPrec);
}
break;
case DatetimeType::SUBTYPE_SQLTimestamp:
i64val = DatetimeType::julianTimestampValue(valPtr,
constValueFracSecPrec ? 11 : 7,
constValueFracSecPrec);
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_RANGE, level,
FALSE, QRDescriptorException,
"Invalid datetime subtype -- %d", constType->getSubtype());
}
// For time or timestamp (if date, both compared values will be 0), truncate
// any extra (wrt the col type) fractional precision from the value and mark
// it as truncated. The caller will use this to determine if the comparison
// operator with the truncated value needs to be changed (e.g., t<time'12:00:00.4'
// <--> t<=time'12:00:00', if t is time(0).
if (constValueFracSecPrec > rangeColFracSecPrec)
{
Int64 scaleFactor = (Int64)pow(10, 6-rangeColFracSecPrec);
Int64 unscaledI64Val = i64val;
i64val = i64val / scaleFactor * scaleFactor;
truncated = (i64val != unscaledI64Val);
}
return i64val;
}
// Return an interval value as an Int64 for use with range specifications.
// An interval value of either type (year-month or day-time) will be calculated
// in terms of the least significant field of that type, regardless of the
// actual fields used in the declaration. For example, an interval specified as
// HOUR TO MINUTE will return a value which is a number of microseconds. This
// facilitates comparing intervals of different granularity.
//
// A day-time interval that ends in seconds may specify a fractional precision
// less than the default (e.g., Interval '5.1' Second(2,2)), in which case its
// raw value is in units of a lower precision than microseconds (hundredths of
// a second in the example). We scale such values to microseconds so that
// everything is comparable. In the example above, the value used would be
// 5100000 instead of the original internal integral value of 510.
static Int64 getInt64ValueFromInterval(ConstValue* constVal,
const IntervalType* colIntvlType,
NABoolean& truncated,
NABoolean& valWasNegative,
logLevel level)
{
const IntervalType* constIntvlType =
static_cast<const IntervalType*>(constVal->getType());
Int64 i64val;
char* val = (char*)constVal->getConstValue();
Lng32 storageSize = constVal->getStorageSize();
Lng32 nullHdrSize = constVal->getType()->getSQLnullHdrSize();
val += nullHdrSize;
storageSize -= nullHdrSize;
switch (storageSize)
{
// No need to check for signed/unsigned before checking for negative;
// interval type is always signed.
case 2:
{
Int16 valx;
memcpy(&valx, val, sizeof(Int16));
valWasNegative = (valx < 0);
i64val = valx;
}
break;
case 4:
{
Lng32 valx;
memcpy(&valx, val, sizeof(Lng32));
valWasNegative = (valx < 0);
i64val = valx;
}
break;
case 8:
memcpy(&i64val, val, sizeof(i64val));
valWasNegative = (i64val < 0);
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_RANGE, level,
FALSE, QRDescriptorException,
"Invalid interval storage length -- %d",
storageSize);
break;
}
// Now cast the value in terms of the least significant field for the constant's
// interval type (months for year-month, microseconds for day-time), which may
// have different fields than those of the column's type.
switch (constIntvlType->getEndField())
{
case REC_DATE_YEAR:
// Multiply by number of months in a year
i64val *= 12;
break;
case REC_DATE_MONTH:
// Value is already in correct units (months).
break;
case REC_DATE_DAY:
// Multiply by number of microseconds in a day.
i64val *= (24LL * 60 * 60000000);
break;
case REC_DATE_HOUR:
// Multiply by number of microseconds in an hour.
i64val *= (60LL * 60000000);
break;
case REC_DATE_MINUTE:
// Multiply by number of microseconds in a minute.
i64val *= 60000000;
break;
case REC_DATE_SECOND:
// We want all intervals with seconds as the end field to be represented
// as a number of microseconds to ensure comparability, so if the
// fractional precision is less than the default (6), scale it to
// microseconds.
i64val *= (Int64)pow(10, 6 - constIntvlType->getFractionPrecision());
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_RANGE, level,
FALSE, QRDescriptorException,
"Invalid end field for interval -- %d",
constIntvlType->getEndField());
break;
}
// Now discard any part of the constant value owing to extra precision in the
// constant's type. For instance, if the column type is INTERVAL YEAR and the
// constant is "interval'2-7'year to month", the constant is truncated to 2
// years. We remember that this truncation occurred, as it may affect the
// comparison operator used in the rangespec.
Int64 scaleFactor;
Int64 origI64Val = i64val;
switch (colIntvlType->getEndField())
{
// Do integer division to scrape off any excess precision in the
// constant value, then multipy by same to restore it as a number of
// months (year-month interval) or microseconds (day-time).
case REC_DATE_YEAR:
// Divide/multiply by number of months in a year
i64val = i64val / 12 * 12;
break;
case REC_DATE_MONTH:
// No possibility of extra precision for the constant value.
break;
case REC_DATE_DAY:
// Divide/multiply by number of microseconds in a day.
i64val = i64val / (24LL * 60 * 60000000) * (24LL * 60 * 60000000);
break;
case REC_DATE_HOUR:
// Divide/multiply by number of microseconds in an hour.
i64val = i64val / (60LL * 60000000) * (60LL * 60000000);
break;
case REC_DATE_MINUTE:
// Divide/multiply by number of microseconds in a minute.
i64val = i64val / 60000000 * 60000000;
break;
case REC_DATE_SECOND:
// Divide/multiply by 10 ** <unused frac secs>.
scaleFactor = (Int64)pow(10, 6 - colIntvlType->getFractionPrecision());
i64val = i64val / scaleFactor * scaleFactor;
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_RANGE, level,
FALSE, QRDescriptorException,
"Invalid end field for interval -- %d",
colIntvlType->getEndField());
break;
}
truncated = (origI64Val != i64val);
return i64val;
}
Int64 getInt64Value(ConstValue* val, const NAType* rangeColType,
NABoolean& truncated, NABoolean& valWasNegative,
logLevel level)
{
truncated = FALSE;
valWasNegative = FALSE;
// If the constant value is a date, time, timestamp, or interval, pass it on
// to the correct function for handling that type.
const NAType* constValType = val->getType();
if (constValType->getTypeQualifier() == NA_DATETIME_TYPE)
return getInt64ValueFromDateTime(val,
static_cast<const DatetimeType*>(rangeColType),
static_cast<const DatetimeType*>(constValType),
truncated, level);
else if (constValType->getTypeQualifier() == NA_INTERVAL_TYPE)
return getInt64ValueFromInterval(val,
static_cast<const IntervalType*>(rangeColType),
truncated, valWasNegative, level);
assertLogAndThrow1(CAT_SQL_COMP_RANGE, level,
constValType->getTypeQualifier() == NA_NUMERIC_TYPE,
QRDescriptorException,
"Unexpected date type -- %d",
constValType->getTypeQualifier());
const NumericType* constValNumType = static_cast<const NumericType*>(constValType);
Lng32 rangeColumnScale = rangeColType->getScale();
NABoolean isExactNumeric = val->isExactNumeric();
Lng32 constValueScale = (isExactNumeric ? constValNumType->getScale() : 0);
char* valuePtr = (char*)val->getConstValue();
Lng32 storageSize = val->getStorageSize();
Lng32 nullHdrSize = constValNumType->getSQLnullHdrSize();
valuePtr += nullHdrSize;
storageSize -= nullHdrSize;
// Scale factor for exact numeric constant values.
Int64 scaleFactor = 1;
if (constValueScale < rangeColumnScale)
scaleFactor = (Int64)pow(10, rangeColumnScale - constValueScale);
// Scale factor for approximate (floating-point) constant values.
double dblScaleFactor = pow(10, rangeColumnScale);
Int64 i64val = 0;
Float64 flt64val = 0;
if (constValNumType->isDecimal())
{
// Decode the decimal value from the string of digits. The digit values
// are the lower nibble of each byte. As we go from left to right,
// multiple the accumulated value by 10, and add the value of the next
// digit. The value is negative if the upper nibble of the first byte is
// 11 (0xB0 after masking off lower nibble).
char* p = valuePtr;
for (Lng32 i=0; i<storageSize; i++)
(i64val *= 10) += (*p++ & 0x0F);
if ((*valuePtr & 0xF0) == 0xB0)
{
i64val *= -1;
valWasNegative = TRUE;
}
}
else
switch (storageSize)
{
case 1:
{
assertLogAndThrow1(CAT_SQL_COMP_RANGE, level,
isExactNumeric, QRDescriptorException,
"Constant value of size 1 not exact numeric, type is: %d",
constValNumType->getTypeQualifier());
Int8 i8val;
memcpy(&i8val, valuePtr, storageSize);
if (constValNumType->isSigned())
{
valWasNegative = (i8val < 0);
i64val = (Int64)(i8val * scaleFactor);
}
else
i64val = (Int64)((UInt8)i8val * scaleFactor);
}
break;
case 2:
{
assertLogAndThrow1(CAT_SQL_COMP_RANGE, level,
isExactNumeric, QRDescriptorException,
"Constant value of size 2 not exact numeric, type is: %d",
constValNumType->getTypeQualifier());
Int16 i16val;
memcpy(&i16val, valuePtr, storageSize);
if (constValNumType->isSigned())
{
valWasNegative = (i16val < 0);
i64val = (Int64)(i16val * scaleFactor);
}
else
i64val = (Int64)((UInt16)i16val * scaleFactor);
}
break;
case 4:
if (isExactNumeric)
{
Lng32 i32val;
memcpy(&i32val, valuePtr, storageSize);
if (constValNumType->isSigned())
{
valWasNegative = (i32val < 0);
i64val = (Int64)(i32val * scaleFactor);
}
else
i64val = (Int64)((UInt32)i32val * scaleFactor);
}
else
{
Float32 flt32val;
memcpy(&flt32val, valuePtr, storageSize);
valWasNegative = (flt32val < 0);
flt64val = flt32val * dblScaleFactor;
if (flt64val < LLONG_MIN)
i64val = LLONG_MIN;
else if (flt64val > LLONG_MAX)
i64val = LLONG_MAX;
else
i64val = (Int64)flt64val;
}
break;
case 8:
if (isExactNumeric)
{
memcpy(&i64val, valuePtr, storageSize);
valWasNegative = (i64val < 0);
i64val *= scaleFactor;
}
else
{
memcpy(&flt64val, valuePtr, storageSize);
valWasNegative = (flt64val < 0);
flt64val *= dblScaleFactor;
if (flt64val < LLONG_MIN)
i64val = LLONG_MIN;
else if (flt64val > LLONG_MAX)
i64val = LLONG_MAX;
else
i64val = (Int64)flt64val;
}
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_RANGE, level,
FALSE, QRDescriptorException,
"Numeric constant of unexpected size: %d bytes",
storageSize);
return 0; // avoid warning
}
// If the constant has more digits to the right of the decimal point than the
// range column, note it as truncated if any of the excess digits is nonzero.
// The caller will use this to determine if the comparison operator with the
// truncated value needs to be changed (e.g., i>4.3 <--> i>=4, if scale of i
// is 0).
if (isExactNumeric)
{
if (constValueScale > rangeColumnScale)
{
scaleFactor = (Int64)pow(10, constValueScale - rangeColumnScale);
truncated = (i64val / scaleFactor * scaleFactor != i64val);
return i64val / scaleFactor;
}
else
return i64val;
}
else
{
truncated = (i64val != flt64val);
return i64val;
}
}
double getDoubleValue(ConstValue* val, logLevel level)
{
// If the constant is a SystemLiteral (typically produced by the cast of
// constant value), the value pointer and storage size will include a null
// indicator header field that we have to take into account when accessing
// the value. It also means the actual value field may not be aligned
// properly, so we need to memcpy the value representation into a variable
// of the appropriate type.
const NAType* constValType = val->getType();
Lng32 nullHdrSize = constValType->getSQLnullHdrSize();
Lng32 valueStorageSize = val->getStorageSize() - nullHdrSize;
void* valuePtr = (char*)val->getConstValue() + nullHdrSize;
NABoolean isExactNumeric = val->isExactNumeric();
double scaleDivisor = pow(10, isExactNumeric ? constValType->getScale() : 0);
if (static_cast<const NumericType*>(constValType)->isDecimal())
{
// Decode the decimal value from the string of digits. The digit values
// are the lower nibble of each byte. As we go from left to right,
// multiple the accumulated value by 10, and add the value of the next
// digit. The value is negative if the upper nibble of the first byte is
// 11 (0xB0 after masking off lower nibble).
char* p = (char*)valuePtr;
Int64 i64val = 0;
for (Lng32 i=0; i<valueStorageSize; i++)
(i64val *= 10) += (*p++ & 0x0F);
if ((*(char*)valuePtr & 0xF0) == 0xB0)
i64val *= -1;
return i64val / scaleDivisor;
}
switch (valueStorageSize)
{
case 1: // tinyint
{
assertLogAndThrow1(CAT_SQL_COMP_RANGE, level,
isExactNumeric, QRDescriptorException,
"const value of size 1 not exact numeric: %d",
constValType->getTypeQualifier());
Int8 i8val;
memcpy(&i8val, valuePtr, valueStorageSize);
return i8val / scaleDivisor;
}
case 2: // smallint
{
assertLogAndThrow1(CAT_SQL_COMP_RANGE, level,
isExactNumeric, QRDescriptorException,
"const value of size 2 not exact numeric: %d",
constValType->getTypeQualifier());
Int16 i16val;
memcpy(&i16val, valuePtr, valueStorageSize);
return i16val / scaleDivisor;
}
case 4: // int
if (isExactNumeric)
{
Lng32 i32val;
memcpy(&i32val, valuePtr, valueStorageSize);
return i32val / scaleDivisor;
}
else
{
Float32 fltval;
memcpy(&fltval, valuePtr, valueStorageSize);
return fltval;
}
case 8: // largeint
if (isExactNumeric)
{
// possible loss of data
Int64 i64val;
memcpy(&i64val, valuePtr, valueStorageSize);
return (double)(i64val / scaleDivisor);
}
else
{
Float64 dblval;
memcpy(&dblval, valuePtr, valueStorageSize);
return dblval;
}
default:
assertLogAndThrow1(CAT_SQL_COMP_RANGE, level,
FALSE, QRDescriptorException,
"Numeric constant of unexpected size: %d bytes",
val->getStorageSize());
return 0; // avoid warning
}
}
NABoolean RangeInfo::operator==(const RangeInfo& other)
{
if (rangeSpec_->getRangeJoinPredId() != NULL_VALUE_ID)
return rangeSpec_->getRangeJoinPredId() ==
other.rangeSpec_->getRangeJoinPredId();
else if (rangeSpec_->getRangeColValueId() != NULL_VALUE_ID)
return rangeSpec_->getRangeColValueId() ==
other.rangeSpec_->getRangeColValueId();
else
return rangeSpec_->getRangeExprText()==other.rangeSpec_->getRangeExprText();
}