Files
mars-flaim/sql/src/sqloptimize.cpp
dsandersoremutah 021073907f Added .cpp and .h files under the sql/src subdirectory
git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@469 0109f412-320b-0410-ab79-c3e0c5ffbbe6
2006-05-26 23:17:49 +00:00

2763 lines
65 KiB
C++

//-------------------------------------------------------------------------
// Desc: Optimize an SQL query
// Tabs: 3
//
// Copyright (c) 2006 Novell, Inc. All Rights Reserved.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 2 of the GNU General Public
// License as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, contact Novell, Inc.
//
// To contact Novell about this file by physical or electronic mail,
// you may find current contact information at www.novell.com
//
// $Id$
//-------------------------------------------------------------------------
#include "flaimsys.h"
// Local function prototypes
FSTATIC RCODE sqlGetRowIdValue(
SQL_VALUE * pValue);
FSTATIC RCODE setupPredicate(
SQL_PRED * pPred,
SQL_TABLE * pTable,
FLMUINT uiColumnNum,
eSQLQueryOperators eOperator,
FLMUINT uiCompareRules,
FLMBOOL bNotted,
SQL_VALUE * pValue);
FSTATIC RCODE sqlCompareValues(
SQL_VALUE * pValue1,
FLMBOOL bInclusive1,
FLMBOOL bNullIsLow1,
SQL_VALUE * pValue2,
FLMBOOL bInclusive2,
FLMBOOL bNullIsLow2,
FLMUINT uiCompareRules,
FLMUINT uiLanguage,
FLMINT * piCmp);
FSTATIC SQL_NODE * sqlEvalLogicalOperands(
SQL_NODE * pSQLNode);
FSTATIC SQL_NODE * sqlClipNotNode(
SQL_NODE * pNotNode,
SQL_NODE ** ppExpr);
FSTATIC RCODE createDNFNode(
F_Pool * pPool,
SQL_DNF_NODE * pParentDNFNode,
SQL_DNF_NODE ** ppDNFNode,
SQL_NODE * pNode);
FSTATIC RCODE copyAndLinkSubTree(
F_Pool * pPool,
SQL_DNF_NODE * pSrcSubTree,
SQL_DNF_NODE * pParentNode);
FSTATIC RCODE distributeAndOverOr(
F_Pool * pPool,
SQL_DNF_NODE * pOldOrNode,
SQL_DNF_NODE ** ppDNFTree);
#if 0
FSTATIC FLMBOOL predIsForTable(
SQL_NODE * pPredRootNode,
SQL_TABLE * pTable);
FSTATIC void rankIndexes(
SQL_TABLE * pTable);
#endif
//-------------------------------------------------------------------------
// Desc: Get the row ID constant from an SQL_VALUE node.
//-------------------------------------------------------------------------
FSTATIC RCODE sqlGetRowIdValue(
SQL_VALUE * pValue)
{
RCODE rc = NE_SFLM_OK;
switch (pValue->eValType)
{
case SQL_UINT_VAL:
pValue->eValType = SQL_UINT64_VAL;
pValue->val.ui64Val = (FLMUINT64)pValue->val.uiVal;
break;
case SQL_MISSING_VAL:
case SQL_UINT64_VAL:
break;
case SQL_INT_VAL:
pValue->eValType = SQL_UINT64_VAL;
pValue->val.ui64Val = (FLMUINT64)((FLMINT64)(pValue->val.iVal));
break;
case SQL_INT64_VAL:
pValue->eValType = SQL_UINT64_VAL;
pValue->val.ui64Val = (FLMUINT64)(pValue->val.i64Val);
break;
default:
rc = RC_SET_AND_ASSERT( NE_Q_INVALID_ROW_ID_VALUE);
goto Exit;
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Setup a predicate using the passed in parameters.
//-------------------------------------------------------------------------
FSTATIC RCODE setupPredicate(
SQL_PRED * pPred,
SQL_TABLE * pTable,
FLMUINT uiColumnNum,
eSQLQueryOperators eOperator,
FLMUINT uiCompareRules,
FLMBOOL bNotted,
SQL_VALUE * pValue)
{
RCODE rc = NE_SFLM_OK;
pPred->pTable = pTable;
pPred->uiColumnNum = uiColumnNum;
if (!pValue || pValue->eValType != SQL_UTF8_VAL)
{
// Comparison rules don't matter for anything that is
// not text, so we normalize them to zero, so the test
// below to see if the comparison rule is the same as
// the comparison rule of the operator will work.
pPred->uiCompareRules = 0;
}
else
{
pPred->uiCompareRules = uiCompareRules;
}
pPred->bNotted = bNotted;
switch (eOperator)
{
case SQL_EXISTS_OP:
case SQL_NE_OP:
pPred->eOperator = eOperator;
pPred->pFromValue = pValue;
break;
case SQL_APPROX_EQ_OP:
pPred->eOperator = eOperator;
pPred->pFromValue = pValue;
pPred->bInclFrom = TRUE;
pPred->bInclUntil = TRUE;
break;
case SQL_EQ_OP:
if ((pValue->uiFlags & SQL_VAL_IS_CONSTANT) &&
(pValue->uiFlags & SQL_VAL_HAS_WILDCARDS))
{
pPred->eOperator = SQL_MATCH_OP;
pPred->pFromValue = pValue;
}
else
{
pPred->eOperator = SQL_RANGE_OP;
pPred->pFromValue = pValue;
pPred->pUntilValue = pValue;
pPred->bInclFrom = TRUE;
pPred->bInclUntil = TRUE;
}
break;
case SQL_LE_OP:
pPred->eOperator = SQL_RANGE_OP;
pPred->pFromValue = NULL;
pPred->pUntilValue = pValue;
pPred->bInclUntil = TRUE;
break;
case SQL_LT_OP:
pPred->eOperator = SQL_RANGE_OP;
pPred->pFromValue = NULL;
pPred->pUntilValue = pValue;
pPred->bInclUntil = FALSE;
break;
case SQL_GE_OP:
pPred->eOperator = SQL_RANGE_OP;
pPred->pFromValue = pValue;
pPred->pUntilValue = NULL;
pPred->bInclFrom = TRUE;
break;
case SQL_GT_OP:
pPred->eOperator = SQL_RANGE_OP;
pPred->pFromValue = pValue;
pPred->pUntilValue = NULL;
pPred->bInclFrom = FALSE;
break;
default:
rc = RC_SET_AND_ASSERT( NE_SFLM_NOT_IMPLEMENTED);
goto Exit;
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Compare two values
//-------------------------------------------------------------------------
FSTATIC RCODE sqlCompareValues(
SQL_VALUE * pValue1,
FLMBOOL bInclusive1,
FLMBOOL bNullIsLow1,
SQL_VALUE * pValue2,
FLMBOOL bInclusive2,
FLMBOOL bNullIsLow2,
FLMUINT uiCompareRules,
FLMUINT uiLanguage,
FLMINT * piCmp)
{
RCODE rc = NE_SFLM_OK;
// We have already called sqlCanCompare, so no need to do it here
if (!pValue1)
{
if (!pValue2)
{
if (bNullIsLow2)
{
*piCmp = (FLMINT)(bNullIsLow1 ? 0 : 1);
}
else
{
*piCmp = (FLMINT)(bNullIsLow1 ? -1 : 0);
}
}
else
{
*piCmp = (FLMINT)(bNullIsLow1 ? -1 : 1);
}
goto Exit;
}
else if (!pValue2)
{
*piCmp = (FLMINT)(bNullIsLow2 ? 1 : -1);
goto Exit;
}
if (RC_BAD( rc = sqlCompare( pValue1, pValue2,
uiCompareRules, uiLanguage, piCmp)))
{
goto Exit;
}
// If everything else is equal, the last distinguisher
// is the inclusive flags and which side of the
// value we are on if we are exclusive which is indicated
// by the bNullIsLow flags
if (*piCmp == 0)
{
if (bInclusive1 != bInclusive2)
{
if (bNullIsLow1)
{
if (bNullIsLow2)
{
// *--> v1
// o--> v2 v1 < v2
// o--> v1
// *--> v2 v1 > v2
*piCmp = bInclusive1 ? -1 : 1;
}
else
{
// *--> v1
// v2 <--o v1 > v2
// o--> v1
// v2 <--* v1 > v2
*piCmp = 1;
}
}
else
{
if (bNullIsLow2)
{
// v1 <--*
// o--> v2 v1 < v2
// v1 <--o
// *--> v2 v1 < v2
*piCmp = -1;
}
else
{
// v1 <--*
// v2 <--o v1 > v2
// v1 <--o
// v2 <--* v1 < v2
*piCmp = bInclusive1 ? 1 : -1;
}
}
}
else if (!bInclusive1)
{
// bInclusive2 is also FALSE
if (bNullIsLow1)
{
if (!bNullIsLow2)
{
// o--> v1
// v2 <--o v1 > v2
*piCmp = 1;
}
// else
// {
// o--> v1
// o--> v2 v1 == v2
// *piCmp = 0;
// }
}
else
{
if (bNullIsLow2)
{
// v1 <--o
// o--> v2 v1 < v2
*piCmp = -1;
}
// else
// {
// v1 <--o
// v2 <--o v1 == v2
// *piCmp = 0;
// }
}
}
// else
// {
// bInclusive1 == TRUE && bInclusive2 == TRUE
// else case covers the cases where
// both are inclusive, in which case it
// doesn't matter which is low and which
// is high
// v1 <--*
// *--> v2 v1 == v2
// v1 <--*
// v2 <--* v1 == v2
// *--> v1
// v2 <--* v1 == v2
// *--> v1
// *--> v2 v1 == v2
// }
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Intersect a predicate into an existing predicate.
//-------------------------------------------------------------------------
RCODE SQLQuery::intersectPredicates(
SQL_PRED * pPred,
eSQLQueryOperators eOperator,
FLMUINT uiCompareRules,
FLMBOOL bNotted,
SQL_VALUE * pValue,
FLMBOOL * pbAlwaysFalse,
FLMBOOL * pbIntersected)
{
RCODE rc = NE_SFLM_OK;
FLMINT iCmp;
FLMBOOL bDoMatch;
*pbIntersected = FALSE;
if (!pValue || pValue->eValType != SQL_UTF8_VAL)
{
bDoMatch = FALSE;
// Comparison rules don't matter for anything that is
// not text, so we normalize them to zero, so the test
// below to see if the comparison rule is the same as
// the comparison rule of the operator will work.
uiCompareRules = 0;
}
else
{
bDoMatch = (eOperator == SQL_EQ_OP &&
(pValue->uiFlags & SQL_VAL_IS_CONSTANT) &&
(pValue->uiFlags & SQL_VAL_HAS_WILDCARDS))
? TRUE
: FALSE;
}
if (eOperator == SQL_EXISTS_OP)
{
*pbIntersected = TRUE;
// An exists operator will either merge with an existing predicate or
// cancel the whole thing out as an empty result.
// If this predicate is not-exists, another predicate ANDed with this
// one can never return a result that will match, unless that predicate
// is also a not-exists, in which case, we simply combine this one
// with that one.
if (bNotted)
{
if (pPred->eOperator != SQL_EXISTS_OP || !pPred->bNotted)
{
*pbAlwaysFalse = TRUE;
}
}
}
else if (pPred->eOperator == SQL_EXISTS_OP)
{
*pbIntersected = TRUE;
// If the first predicate is an exists operator
// it will be the only one, because otherwise
// it will have been merged with another operator
// in the code just above.
flmAssert( !pPred->pNext);
// If the predicate is notted, another predicate
// ANDed with this one can never return a result.
if (pPred->bNotted)
{
*pbAlwaysFalse = TRUE;
}
else
{
// Change the predicate to the current
// operator.
if (RC_BAD( rc = setupPredicate( pPred, pPred->pTable,
pPred->uiColumnNum, eOperator, uiCompareRules,
bNotted, pValue)))
{
goto Exit;
}
}
}
// See if the operator intersects a range operator
else if (pPred->eOperator == SQL_RANGE_OP &&
pPred->uiCompareRules == uiCompareRules &&
!bDoMatch &&
(eOperator == SQL_EQ_OP ||
eOperator == SQL_LE_OP ||
eOperator == SQL_LT_OP ||
eOperator == SQL_GE_OP ||
eOperator == SQL_GT_OP))
{
SQL_VALUE * pFromValue;
SQL_VALUE * pUntilValue;
FLMBOOL bInclFrom;
FLMBOOL bInclUntil;
*pbIntersected = TRUE;
pFromValue = (eOperator == SQL_EQ_OP ||
eOperator == SQL_GE_OP ||
eOperator == SQL_GT_OP)
? pValue
: NULL;
pUntilValue = (eOperator == SQL_EQ_OP ||
eOperator == SQL_LE_OP ||
eOperator == SQL_LT_OP)
? pValue
: NULL;
bInclFrom = (FLMBOOL)(eOperator == SQL_EQ_OP ||
eOperator == SQL_GE_OP
? TRUE
: FALSE);
bInclUntil = (FLMBOOL)(eOperator == SQL_EQ_OP ||
eOperator == SQL_LE_OP
? TRUE
: FALSE);
// If the value type is not compatible with the predicate's
// value type, we cannot do the comparison, and there is
// no intersection.
if (!sqlCanCompare( pValue, pPred->pFromValue) ||
!sqlCanCompare( pValue, pPred->pUntilValue))
{
*pbAlwaysFalse = TRUE;
}
else if (RC_BAD( rc = sqlCompareValues( pFromValue,
bInclFrom, TRUE,
pPred->pFromValue, pPred->bInclFrom, TRUE,
uiCompareRules, m_uiLanguage, &iCmp)))
{
goto Exit;
}
else if (iCmp > 0)
{
// From value is greater than predicate's from value.
// If the from value is also greater than the predicate's
// until value, we have no intersection.
if (RC_BAD( rc = sqlCompareValues( pFromValue,
bInclFrom, TRUE,
pPred->pUntilValue, pPred->bInclUntil, FALSE,
uiCompareRules, m_uiLanguage, &iCmp)))
{
goto Exit;
}
if (iCmp > 0)
{
*pbAlwaysFalse = TRUE;
}
else
{
pPred->pFromValue = pFromValue;
pPred->bInclFrom = bInclFrom;
}
}
else if (RC_BAD( rc = sqlCompareValues( pUntilValue,
bInclUntil, FALSE,
pPred->pUntilValue, pPred->bInclUntil, FALSE,
uiCompareRules, m_uiLanguage, &iCmp)))
{
goto Exit;
}
else if (iCmp < 0)
{
// Until value is less than predicate's until value. If the
// until value is also less than predicate's from value, we
// have no intersection.
if (RC_BAD( rc = sqlCompareValues( pUntilValue,
bInclUntil, FALSE,
pPred->pFromValue, pPred->bInclFrom, TRUE,
uiCompareRules, m_uiLanguage, &iCmp)))
{
goto Exit;
}
if (iCmp < 0)
{
*pbAlwaysFalse = TRUE;
}
else
{
pPred->pUntilValue = pUntilValue;
pPred->bInclUntil = bInclUntil;
}
}
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: See if a predicate can be unioned with another one.
//-------------------------------------------------------------------------
RCODE SQLQuery::unionPredicates(
SQL_PRED * pPred,
eSQLQueryOperators eOperator,
FLMUINT uiCompareRules,
FLMBOOL bNotted,
SQL_VALUE * pValue,
FLMBOOL * pbUnioned)
{
RCODE rc = NE_SFLM_OK;
FLMINT iCmp;
FLMBOOL bDoMatch;
*pbUnioned = FALSE;
if (!pValue || pValue->eValType != SQL_UTF8_VAL)
{
bDoMatch = FALSE;
// Comparison rules don't matter for anything that is
// not text, so we normalize them to zero, so the test
// below to see if the comparison rule is the same as
// the comparison rule of the operator will work.
uiCompareRules = 0;
}
else
{
bDoMatch = (eOperator == SQL_EQ_OP &&
(pValue->uiFlags & SQL_VAL_IS_CONSTANT) &&
(pValue->uiFlags & SQL_VAL_HAS_WILDCARDS))
? TRUE
: FALSE;
}
if (eOperator == SQL_EXISTS_OP || eOperator == SQL_NE_OP)
{
// See if there is another operator that is an exact
// match of this one.
if (pPred->eOperator == eOperator &&
pPred->bNotted == bNotted)
{
// Perfect match - no need to do any more.
*pbUnioned = TRUE;
}
}
// See if the operator overlaps with another range operator
else if (pPred->eOperator == SQL_RANGE_OP &&
pPred->uiCompareRules == uiCompareRules &&
!bDoMatch &&
(eOperator == SQL_EQ_OP ||
eOperator == SQL_LE_OP ||
eOperator == SQL_LT_OP ||
eOperator == SQL_GE_OP ||
eOperator == SQL_GT_OP))
{
SQL_VALUE * pFromValue;
SQL_VALUE * pUntilValue;
FLMBOOL bInclFrom;
FLMBOOL bInclUntil;
pFromValue = (eOperator == SQL_EQ_OP ||
eOperator == SQL_GE_OP ||
eOperator == SQL_GT_OP)
? pValue
: NULL;
pUntilValue = (eOperator == SQL_EQ_OP ||
eOperator == SQL_LE_OP ||
eOperator == SQL_LT_OP)
? pValue
: NULL;
bInclFrom = (FLMBOOL)(eOperator == SQL_EQ_OP ||
eOperator == SQL_GE_OP
? TRUE
: FALSE);
bInclUntil = (FLMBOOL)(eOperator == SQL_EQ_OP ||
eOperator == SQL_LE_OP
? TRUE
: FALSE);
// If the value type is not compatible with the predicate's
// value type, we cannot do the comparison, and there is
// no overlap.
if (!sqlCanCompare( pValue, pPred->pFromValue) ||
!sqlCanCompare( pValue, pPred->pUntilValue))
{
// Nothing to do here
}
else if (RC_BAD( rc = sqlCompareValues( pFromValue,
bInclFrom, TRUE,
pPred->pFromValue, pPred->bInclFrom, TRUE,
uiCompareRules, m_uiLanguage, &iCmp)))
{
goto Exit;
}
else if (iCmp >= 0)
{
// From value is greater than or equal to the predicate's
// from value.
// If the from value is also less than or equal to the
// predicate's until value, we have an overlap.
if (RC_BAD( rc = sqlCompareValues( pFromValue,
bInclFrom, TRUE,
pPred->pUntilValue, pPred->bInclUntil, FALSE,
uiCompareRules, m_uiLanguage, &iCmp)))
{
goto Exit;
}
if (iCmp <= 0)
{
// If the until value is greater than the predicate's
// until value, change the predicate's until value.
if (RC_BAD( rc = sqlCompareValues( pUntilValue,
bInclUntil, FALSE,
pPred->pUntilValue, pPred->bInclUntil, FALSE,
uiCompareRules, m_uiLanguage, &iCmp)))
{
goto Exit;
}
if (iCmp > 0)
{
pPred->pUntilValue = pUntilValue;
pPred->bInclUntil = bInclUntil;
}
*pbUnioned = TRUE;
goto Exit;
}
}
// At this point we already know that the from value is
// less than the predicate's from value.
// See if the until value is greater than or equal
// to the from value. If it is we have an overlap.
else if (RC_BAD( rc = sqlCompareValues( pUntilValue,
bInclUntil, FALSE,
pPred->pFromValue, pPred->bInclFrom, TRUE,
uiCompareRules, m_uiLanguage, &iCmp)))
{
goto Exit;
}
else if (iCmp >= 0)
{
// Until value is greater than or equal to the predicate's
// from value, so we definitely have an overlap. We
// already know that the from value is less than the
// predicate's from value, so we will change that for sure.
pPred->pFromValue = pFromValue;
pPred->bInclFrom = bInclFrom;
// See if the until value is greater than the
// predicate's until value, in which case we need to
// change the predicate's until value.
if (RC_BAD( rc = sqlCompareValues( pUntilValue,
bInclUntil, FALSE,
pPred->pUntilValue, pPred->bInclUntil, FALSE,
uiCompareRules, m_uiLanguage, &iCmp)))
{
goto Exit;
}
if (iCmp > 0)
{
pPred->pUntilValue = pUntilValue;
pPred->bInclUntil = bInclUntil;
}
*pbUnioned = TRUE;
}
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Convert an operand to a predicate. If it is merged with another
// predicate, remove it and return the next node in the list of
// operands. If it is not merged, still return the next node in
// the list of operands.
//-------------------------------------------------------------------------
RCODE SQLQuery::addPredicate(
SQL_SUBQUERY * pSubQuery,
FLMUINT * puiOperand,
SQL_TABLE * pTable,
FLMUINT uiColumnNum,
eSQLQueryOperators eOperator,
FLMUINT uiCompareRules,
FLMBOOL bNotted,
SQL_VALUE * pValue)
{
RCODE rc = NE_SFLM_OK;
FLMUINT uiOperand = *puiOperand;
FLMUINT uiLoop;
SQL_NODE * pCurrNode;
SQL_NODE * pOperandNode = pSubQuery->ppOperands [uiOperand];
FLMBOOL bAlwaysFalse;
SQL_PRED * pPred;
// Convert the constant value in a node id predicate to
// a 64 bit unsigned value.
if (eOperator != SQL_EXISTS_OP && !uiColumnNum)
{
if (RC_BAD( rc = sqlGetRowIdValue( pValue)))
{
goto Exit;
}
}
// Look at all of the operands up to the one we are processing to
// see if this operand should be merged with a previous one.
for (uiLoop = 0; uiLoop < uiOperand; uiLoop++)
{
pCurrNode = pSubQuery->ppOperands [uiLoop];
if (pCurrNode->eNodeType != SQL_PRED_NODE)
{
pCurrNode = pCurrNode->pNextSib;
continue;
}
pPred = &pCurrNode->nd.pred;
if (pPred->pTable == pTable && pPred->uiColumnNum == uiColumnNum)
{
FLMBOOL bIntersected;
if (RC_BAD( rc = intersectPredicates( pPred, eOperator,
uiCompareRules, bNotted, pValue,
&bAlwaysFalse, &bIntersected)))
{
goto Exit;
}
if (!bIntersected && !bAlwaysFalse)
{
continue;
}
// If we get a false result, then we know that the
// intersection of predicates is creating a situation where
// it can never be true, so this sub-query can never return
// anything. Therefore, we remove the sub-query.
if (bAlwaysFalse)
{
// Remove the sub-query - it will never return anything.
if (pSubQuery->pPrev)
{
pSubQuery->pPrev->pNext = pSubQuery->pNext;
}
else
{
m_pFirstSubQuery = pSubQuery->pNext;
}
if (pSubQuery->pNext)
{
pSubQuery->pNext->pPrev = pSubQuery->pPrev;
}
else
{
m_pLastSubQuery = pSubQuery->pPrev;
}
// Setup so that we will quit processing this sub-query's
// operands - it is now unlinked.
uiOperand = pSubQuery->uiOperandCount;
}
else
{
flmAssert( bIntersected);
// We intersected, so we want to remove the current
// operand node out of the list and set up so that
// we will increment to the next one in the list.
pSubQuery->uiOperandCount--;
if (uiOperand < pSubQuery->uiOperandCount)
{
f_memmove( &pSubQuery->ppOperands [uiOperand],
&pSubQuery->ppOperands [uiOperand + 1],
sizeof( SQL_NODE *) * (pSubQuery->uiOperandCount - uiOperand));
}
}
goto Exit;
}
}
// If we didn't find one to intersect with or union with, we need to
// create a new operand node of type SQL_PRED_NODE. Can't just modify
// this node, because other sub-queries may be pointing to it also, and
// they would modify it in a different way. Unlike other nodes, predicate
// nodes are ALWAYS tied to one and only one sub-query.
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_NODE),
(void **)&pOperandNode)))
{
goto Exit;
}
// Set the stuff that needs to be set for this predicate.
pOperandNode->eNodeType = SQL_PRED_NODE;
if (RC_BAD( rc = setupPredicate( &pOperandNode->nd.pred,
pTable, uiColumnNum,
eOperator, uiCompareRules, bNotted, pValue)))
{
goto Exit;
}
// Go to the next operand
uiOperand++;
Exit:
*puiOperand = uiOperand;
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Convert all of the operands underneath an AND or OR operator to
// predicates where possible, except for the operands which are AND
// or OR nodes.
//-------------------------------------------------------------------------
RCODE SQLQuery::convertOperandsToPredicates( void)
{
RCODE rc = NE_SFLM_OK;
SQL_NODE * pSQLNode;
SQL_VALUE * pValue;
SQL_TABLE * pTable;
FLMUINT uiColumnNum;
eSQLQueryOperators eOperator;
FLMUINT uiOperand;
SQL_SUBQUERY * pSubQuery;
SQL_SUBQUERY * pNextSubQuery;
pSubQuery = m_pFirstSubQuery;
while (pSubQuery)
{
pNextSubQuery = pSubQuery->pNext;
uiOperand = 0;
while (uiOperand < pSubQuery->uiOperandCount)
{
pSQLNode = pSubQuery->ppOperands [uiOperand];
if (pSQLNode->eNodeType == SQL_COLUMN_NODE)
{
if (RC_BAD( rc = addPredicate( pSubQuery, &uiOperand,
pSQLNode->nd.column.pTable,
pSQLNode->nd.column.uiColumnNum,
SQL_EXISTS_OP, 0, pSQLNode->bNotted, NULL)))
{
goto Exit;
}
}
else if (pSQLNode->eNodeType == SQL_OPERATOR_NODE &&
isSQLCompareOp( pSQLNode->nd.op.eOperator) &&
((pSQLNode->pFirstChild->eNodeType == SQL_COLUMN_NODE &&
pSQLNode->pLastChild->eNodeType == SQL_VALUE_NODE) ||
(pSQLNode->pFirstChild->eNodeType == SQL_VALUE_NODE &&
pSQLNode->pLastChild->eNodeType == SQL_COLUMN_NODE)))
{
// Have a Column,Op,Value or Value,Op,Column. Convert to a
// predicate node and merge with other predicate nodes that
// have already been created, if possible.
if (pSQLNode->pFirstChild->eNodeType == SQL_COLUMN_NODE)
{
pTable = pSQLNode->pFirstChild->nd.column.pTable;
uiColumnNum = pSQLNode->pFirstChild->nd.column.uiColumnNum;
pValue = &pSQLNode->pLastChild->nd.value;
eOperator = pSQLNode->nd.op.eOperator;
}
else
{
pTable = pSQLNode->pFirstChild->nd.column.pTable;
uiColumnNum = pSQLNode->pFirstChild->nd.column.uiColumnNum;
pValue = &pSQLNode->pFirstChild->nd.value;
// Need to invert the operator in this case.
switch (pSQLNode->nd.op.eOperator)
{
case SQL_EQ_OP:
case SQL_NE_OP:
// No change.
break;
case SQL_LT_OP:
eOperator = SQL_GE_OP;
break;
case SQL_LE_OP:
eOperator = SQL_GT_OP;
break;
case SQL_GT_OP:
eOperator = SQL_LE_OP;
break;
case SQL_GE_OP:
eOperator = SQL_LT_OP;
break;
default:
// Should never get here!
flmAssert( 0);
break;
}
}
if (RC_BAD( rc = addPredicate( pSubQuery, &uiOperand,
pTable, uiColumnNum,
eOperator, pSQLNode->nd.op.uiCompareRules,
pSQLNode->bNotted, pValue)))
{
goto Exit;
}
}
else
{
// Can't do anything with this operand, leave it and go to the
// next one.
uiOperand++;
}
}
pSubQuery = pNextSubQuery;
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Evaluate operands of an AND or OR operator to see if we can
// replace one.
// TRUE && P1 will be replaced with P1
// FALSE && P1 will be replaced with FALSE
// UNKNOWN && P1 will be replaced with UNKNOWN
// TRUE || P1 will be replaced with TRUE
// UNKNOWN || P1 will be replaced with UNKNOWN
// FALSE || P1 will be replaced with P1
//-------------------------------------------------------------------------
FSTATIC SQL_NODE * sqlEvalLogicalOperands(
SQL_NODE * pSQLNode)
{
eSQLQueryOperators eOperator = pSQLNode->nd.op.eOperator;
SQL_NODE * pChildNode;
SQLBoolType eChildBoolVal;
SQLBoolType eClipValue = (eOperator == SQL_AND_OP)
? SQL_TRUE
: SQL_FALSE;
SQL_NODE * pReplacementNode = NULL;
pChildNode = pSQLNode->pFirstChild;
while (pChildNode)
{
if (isSQLNodeBool( pChildNode))
{
eChildBoolVal = pChildNode->nd.value.val.eBool;
}
else
{
pChildNode = pChildNode->pNextSib;
continue;
}
// For AND operators eClipValue will be SQL_TRUE. For OR
// operators, it will be SQL_FALSE. Those nodes should all be
// clipped out. If, after clipping the value, there is only
// one node left, whatever it is should be moved up to replace
// the AND or the OR node.
if (eChildBoolVal == eClipValue)
{
if (pChildNode->pPrevSib)
{
pChildNode->pPrevSib->pNextSib = pChildNode->pNextSib;
}
else
{
pSQLNode->pFirstChild = pChildNode->pNextSib;
}
if (pChildNode->pNextSib)
{
pChildNode->pNextSib->pPrevSib = pChildNode->pPrevSib;
}
else
{
pSQLNode->pLastChild = pChildNode->pPrevSib;
}
if (pSQLNode->pFirstChild != pSQLNode->pLastChild)
{
pChildNode = pChildNode->pNextSib;
continue;
}
else
{
pReplacementNode = pSQLNode->pFirstChild;
break;
}
}
else
{
// The child node is a a boolean value that should simply replace
// the AND or OR operator node. This handles the following cases:
// 1. Value is SQL_UNKNOWN and operator is SQL_OR or SQL_AND
// 2. Value is SQL_FALSE and operator is SQL_AND
// 3. Value is SQL_TRUE and operator is SQL_OR.
pReplacementNode = pChildNode;
break;
}
}
// If we got a replacement node, link it in where the AND or OR
// node was.
if (pReplacementNode)
{
SQL_NODE * pParentNode;
if ((pParentNode = pSQLNode->pParent) == NULL)
{
pReplacementNode->pParent = NULL;
pReplacementNode->pPrevSib = NULL;
pReplacementNode->pNextSib = NULL;
}
else
{
pReplacementNode->pParent = pParentNode;
if ((pReplacementNode->pPrevSib = pSQLNode->pPrevSib) != NULL)
{
pReplacementNode->pPrevSib->pNextSib = pReplacementNode;
}
else
{
pParentNode->pFirstChild = pReplacementNode;
}
if ((pReplacementNode->pNextSib = pSQLNode->pNextSib) != NULL)
{
pReplacementNode->pNextSib->pPrevSib = pReplacementNode;
}
else
{
pParentNode->pLastChild = pReplacementNode;
}
}
pSQLNode = pReplacementNode;
}
return( pSQLNode);
}
//-------------------------------------------------------------------------
// Desc: Clip a NOT node out of the tree.
//-------------------------------------------------------------------------
FSTATIC SQL_NODE * sqlClipNotNode(
SQL_NODE * pNotNode,
SQL_NODE ** ppExpr)
{
SQL_NODE * pKeepNode;
// If this NOT node has no parent, the root
// of the tree needs to be set to its child.
pKeepNode = pNotNode->pFirstChild;
// Child better not have any siblings - NOT nodes only have
// one operand.
flmAssert( !pKeepNode->pNextSib && !pKeepNode->pPrevSib);
// Set child to point to the NOT node's parent.
if ((pKeepNode->pParent = pNotNode->pParent) == NULL)
{
*ppExpr = pKeepNode;
}
else
{
// Link child in where the NOT node used to be.
if ((pKeepNode->pPrevSib = pNotNode->pPrevSib) != NULL)
{
pKeepNode->pPrevSib->pNextSib = pKeepNode;
}
else
{
pKeepNode->pParent->pFirstChild = pKeepNode;
}
if ((pKeepNode->pNextSib = pNotNode->pNextSib) != NULL)
{
pKeepNode->pNextSib->pPrevSib = pKeepNode;
}
else
{
pKeepNode->pParent->pLastChild = pKeepNode;
}
}
return( pKeepNode);
}
//-------------------------------------------------------------------------
// Desc: Flatten the query tree. This coalesces AND and OR nodes so that
// they can have multiple operands. This will also strip out NOT nodes
// and resolve constant expressions to a single node.
//-------------------------------------------------------------------------
RCODE SQLQuery::flattenTree( void)
{
RCODE rc = NE_SFLM_OK;
SQL_NODE * pSQLNode = m_pQuery;
SQL_NODE * pTmpNode;
SQL_NODE * pParentNode = NULL;
eSQLNodeTypes eNodeType;
eSQLQueryOperators eOperator;
FLMBOOL bNotted = FALSE;
for (;;)
{
eNodeType = pSQLNode->eNodeType;
// Need to save bNotted on each node so that when we traverse
// back up the tree it can be reset properly. If bNotted is
// TRUE and pSQLNode is an operator, we may change the operator in
// some cases. Even if we change the operator, we still want to
// set the bNotted flag because it also implies "for every" when set
// to TRUE, and we need to remember that as well.
pSQLNode->bNotted = bNotted;
if (eNodeType == SQL_OPERATOR_NODE)
{
eOperator = pSQLNode->nd.op.eOperator;
if (eOperator == SQL_AND_OP || eOperator == SQL_OR_OP)
{
// AND and OR nodes better have child nodes
if (!pSQLNode->pFirstChild || !pSQLNode->pLastChild)
{
flmAssert( 0);
rc = RC_SET( NE_SFLM_Q_ILLEGAL_OPERAND);
goto Exit;
}
if (bNotted)
{
eOperator = (eOperator == SQL_AND_OP
? SQL_OR_OP
: SQL_AND_OP);
pSQLNode->nd.op.eOperator = eOperator;
}
if (pParentNode)
{
// Logical sub-expressions can only be operands of
// AND, OR, or NOT operators.
if (!isSQLLogicalOp( pParentNode->nd.op.eOperator))
{
rc = RC_SET( NE_SFLM_Q_ILLEGAL_OPERAND);
goto Exit;
}
if (pParentNode->nd.op.eOperator == eOperator)
{
// Move all of pSQLNode's children become the immediate
// children of pParentNode.
pTmpNode = pSQLNode->pFirstChild;
while (pTmpNode)
{
pTmpNode->pParent = pParentNode;
pTmpNode = pTmpNode->pNextSib;
}
if (pSQLNode->pPrevSib)
{
pSQLNode->pPrevSib->pNextSib = pSQLNode->pFirstChild;
pSQLNode->pFirstChild->pPrevSib = pSQLNode->pPrevSib;
}
if (pSQLNode->pNextSib)
{
pSQLNode->pNextSib->pPrevSib = pSQLNode->pLastChild;
pSQLNode->pLastChild->pNextSib = pSQLNode->pNextSib;
}
// Continue processing from pSQLNode's first child, which
// is the beginning of the list of nodes we just replaced
// pSQLNode with.
pSQLNode = pSQLNode->pFirstChild;
continue;
}
}
}
else if (eOperator == SQL_NOT_OP)
{
// Logical sub-expressions can only be operands of
// AND, OR, or NOT operators.
if (pParentNode)
{
if (!isSQLLogicalOp( pParentNode->nd.op.eOperator))
{
rc = RC_SET( NE_SFLM_Q_ILLEGAL_OPERAND);
goto Exit;
}
}
bNotted = !bNotted;
// Clip NOT nodes out of the tree.
pSQLNode = sqlClipNotNode( pSQLNode, &m_pQuery);
pParentNode = pSQLNode->pParent;
continue;
}
else if (isSQLCompareOp( eOperator))
{
// Comparison sub-expressions can only be operands of
// AND, OR, or NOT operators.
if (pParentNode)
{
if (!isSQLLogicalOp( pParentNode->nd.op.eOperator))
{
rc = RC_SET( NE_SFLM_Q_ILLEGAL_OPERAND);
goto Exit;
}
}
if (bNotted)
{
switch (eOperator)
{
case SQL_EQ_OP:
eOperator = SQL_NE_OP;
break;
case SQL_NE_OP:
eOperator = SQL_EQ_OP;
break;
case SQL_LT_OP:
eOperator = SQL_GE_OP;
break;
case SQL_LE_OP:
eOperator = SQL_GT_OP;
break;
case SQL_GT_OP:
eOperator = SQL_LE_OP;
break;
case SQL_GE_OP:
eOperator = SQL_LT_OP;
break;
default:
// Don't change the other operators.
// Will just use the bNotted flag when
// evaluating.
break;
}
pSQLNode->nd.op.eOperator = eOperator;
}
}
else
{
// Better be an arithmetic operator we are dealing with
// at this point.
flmAssert( isSQLArithOp( eOperator));
// Arithmetic sub-expressions can only be operands
// of arithmetic or comparison operators
if (pParentNode)
{
if (!isSQLCompareOp( pParentNode->nd.op.eOperator) &&
!isSQLArithOp( pParentNode->nd.op.eOperator))
{
rc = RC_SET( NE_SFLM_Q_ILLEGAL_OPERAND);
goto Exit;
}
}
}
}
else if (eNodeType == SQL_COLUMN_NODE)
{
flmAssert( !pSQLNode->pFirstChild);
}
else
{
flmAssert( eNodeType == SQL_VALUE_NODE);
// If bNotted is TRUE and we have a boolean value, change
// the value: FALSE ==> TRUE, TRUE ==> FALSE.
if (bNotted && pSQLNode->nd.value.eValType == SQL_BOOL_VAL)
{
if (pSQLNode->nd.value.val.eBool == SQL_TRUE)
{
pSQLNode->nd.value.val.eBool = SQL_FALSE;
}
else if (pSQLNode->nd.value.val.eBool == SQL_FALSE)
{
pSQLNode->nd.value.val.eBool = SQL_TRUE;
}
}
// Values can only be operands of arithmetic or comparison operators,
// unless they are boolean values, in which case they can only be
// operands of logical operators.
if (pParentNode)
{
if (pSQLNode->nd.value.eValType == SQL_BOOL_VAL)
{
if (!isSQLLogicalOp( pParentNode->nd.op.eOperator))
{
rc = RC_SET( NE_SFLM_Q_ILLEGAL_OPERAND);
goto Exit;
}
}
else
{
if (!isSQLCompareOp( pParentNode->nd.op.eOperator) &&
!isSQLArithOp( pParentNode->nd.op.eOperator))
{
rc = RC_SET( NE_SFLM_Q_ILLEGAL_OPERAND);
goto Exit;
}
}
}
// A value node should not have any children
flmAssert( !pSQLNode->pFirstChild);
}
// Do traversal to child node, if any
if (pSQLNode->pFirstChild)
{
pParentNode = pSQLNode;
pSQLNode = pSQLNode->pFirstChild;
continue;
}
// Go back up the tree until we hit something that has
// a sibling.
while (!pSQLNode->pNextSib)
{
// If there are no more parents, we are done.
if ((pSQLNode = pSQLNode->pParent) == NULL)
{
goto Exit;
}
flmAssert( pSQLNode->eNodeType == SQL_OPERATOR_NODE);
// Evaluate arithmetic expressions if both operands are
// constants.
if (isSQLArithOp( pSQLNode->nd.op.eOperator) &&
pSQLNode->pFirstChild->eNodeType == SQL_VALUE_NODE &&
pSQLNode->pLastChild->eNodeType == SQL_VALUE_NODE)
{
if (RC_BAD( rc = sqlEvalArithOperator(
&pSQLNode->pFirstChild->nd.value,
&pSQLNode->pLastChild->nd.value,
pSQLNode->nd.op.eOperator,
&pSQLNode->nd.value)))
{
goto Exit;
}
pSQLNode->eNodeType = SQL_VALUE_NODE;
pSQLNode->nd.value.uiFlags = SQL_VAL_IS_CONSTANT;
pSQLNode->pFirstChild = NULL;
pSQLNode->pLastChild = NULL;
}
else
{
// For the AND and OR operators, check the operands to
// see if they are boolean values. Boolean values can
// be weeded out of the criteria as we go back up the
// tree.
if (pSQLNode->nd.op.eOperator == SQL_OR_OP ||
pSQLNode->nd.op.eOperator == SQL_AND_OP)
{
pSQLNode = sqlEvalLogicalOperands( pSQLNode);
if (!pSQLNode->pParent)
{
m_pQuery = pSQLNode;
}
}
}
pParentNode = pSQLNode->pParent;
}
// pSQLNode will NEVER be NULL if we get here, because we
// will jump to Exit in those cases.
pSQLNode = pSQLNode->pNextSib;
// Need to reset the bNotted flag to what it would have
// been as we traverse back up the tree.
bNotted = pParentNode->bNotted;
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Allocate and set up a DNF node.
//-------------------------------------------------------------------------
FSTATIC RCODE createDNFNode(
F_Pool * pPool,
SQL_DNF_NODE * pParentDNFNode,
SQL_DNF_NODE ** ppDNFNode,
SQL_NODE * pNode)
{
RCODE rc = NE_SFLM_OK;
SQL_DNF_NODE * pDNFNode;
if (RC_BAD( rc = pPool->poolCalloc( sizeof( SQL_DNF_NODE),
(void **)&pDNFNode)))
{
goto Exit;
}
// pDNFNode->pNode will be NULL if it is an AND or OR operator.
if (pNode->eNodeType == SQL_OPERATOR_NODE)
{
if (pNode->nd.op.eOperator == SQL_AND_OP)
{
pDNFNode->bAndOp = TRUE;
}
else if (pNode->nd.op.eOperator == SQL_OR_OP)
{
// No need to really set as it is already 0 from poolCalloc.
// pDNFNode->bAndOp = FALSE;
}
else
{
pDNFNode->pNode = pNode;
}
}
else
{
pDNFNode->pNode = pNode;
}
if ((pDNFNode->pParent = pParentDNFNode) != NULL)
{
if ((pDNFNode->pPrevSib = pParentDNFNode->pLastChild) != NULL)
{
pDNFNode->pPrevSib->pNextSib = pDNFNode;
}
else
{
pParentDNFNode->pFirstChild = pDNFNode;
}
pParentDNFNode->pLastChild = pDNFNode;
}
*ppDNFNode = pDNFNode;
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Copy the sub-tree pointed to by pSrcSubTree and then link the
// new sub-tree as the last child of pParentNode.
//-------------------------------------------------------------------------
FSTATIC RCODE copyAndLinkSubTree(
F_Pool * pPool,
SQL_DNF_NODE * pSrcSubTree,
SQL_DNF_NODE * pParentNode)
{
RCODE rc = NE_SFLM_OK;
SQL_DNF_NODE * pNewSubTree = NULL;
SQL_DNF_NODE * pCurrDestParentNode = NULL;
SQL_DNF_NODE * pCurrSrcNode = pSrcSubTree;
SQL_DNF_NODE * pNewDestNode = NULL;
for (;;)
{
if (RC_BAD( rc = pPool->poolCalloc( sizeof( SQL_DNF_NODE),
(void **)&pNewDestNode)))
{
goto Exit;
}
pNewDestNode->pNode = pCurrSrcNode->pNode;
pNewDestNode->bAndOp = pCurrSrcNode->bAndOp;
if (!pNewSubTree)
{
pNewSubTree = pNewDestNode;
}
else
{
pNewDestNode->pParent = pCurrDestParentNode;
if ((pNewDestNode->pPrevSib = pCurrDestParentNode->pLastChild) != NULL)
{
pNewDestNode->pPrevSib->pNextSib = pNewDestNode;
}
else
{
pCurrDestParentNode->pFirstChild = pNewDestNode;
}
pCurrDestParentNode->pLastChild = pNewDestNode;
}
// Try to go down to a child node
if (pCurrSrcNode->pFirstChild)
{
pCurrSrcNode = pCurrSrcNode->pFirstChild;
pCurrDestParentNode = pNewDestNode;
continue;
}
// No child nodes, go back up parent chain until we find one that
// has a sibling.
for (;;)
{
if (pCurrSrcNode == pSrcSubTree)
{
break;
}
if (pCurrSrcNode->pNextSib)
{
break;
}
pCurrSrcNode = pCurrSrcNode->pParent;
pCurrDestParentNode = pCurrDestParentNode->pParent;
}
if (pCurrSrcNode == pSrcSubTree)
{
break;
}
pCurrSrcNode = pCurrSrcNode->pNextSib;
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Distribute an AND operator over an OR operator. The AND operator
// is the parent node of the passed in pOldOrNode. A new list of
// AND nodes is created which will replace the original AND node in
// the tree.
//-------------------------------------------------------------------------
FSTATIC RCODE distributeAndOverOr(
F_Pool * pPool,
SQL_DNF_NODE * pOldOrNode,
SQL_DNF_NODE ** ppDNFTree)
{
RCODE rc = NE_SFLM_OK;
SQL_DNF_NODE * pOldAndNode;
SQL_DNF_NODE * pOldAndParentNode;
SQL_DNF_NODE * pNewAndNode;
SQL_DNF_NODE * pFirstNewAndNode;
SQL_DNF_NODE * pLastNewAndNode;
SQL_DNF_NODE * pOrChildNode;
SQL_DNF_NODE * pAndChildNode;
// Parent node to pOldOrNode better be an AND node.
pOldAndNode = pOldOrNode->pParent;
flmAssert( !pOldAndNode->pNode && pOldAndNode->bAndOp);
// Distribute ALL of the AND node's children (except this OR node)
// across ALL of the OR node's children
pFirstNewAndNode = NULL;
pLastNewAndNode = NULL;
pOrChildNode = pOldOrNode->pFirstChild;
while (pOrChildNode)
{
if (RC_BAD( rc = pPool->poolCalloc( sizeof( SQL_DNF_NODE),
(void **)&pNewAndNode)))
{
goto Exit;
}
pNewAndNode->bAndOp = TRUE;
if ((pNewAndNode->pPrevSib = pLastNewAndNode) != NULL)
{
pLastNewAndNode->pNextSib = pNewAndNode;
}
else
{
pFirstNewAndNode = pNewAndNode;
}
pLastNewAndNode = pNewAndNode;
// Copy all of the old AND node's children, except for this
// OR node as children of the new AND node.
pAndChildNode = pOldAndNode->pFirstChild;
while (pAndChildNode)
{
if (pAndChildNode != pOldOrNode)
{
if (RC_BAD( rc = copyAndLinkSubTree( pPool, pAndChildNode, pNewAndNode)))
{
goto Exit;
}
}
pAndChildNode = pAndChildNode->pNextSib;
}
// Copy the entire sub-tree of pOrChildNode and link it as the last
// child of the new AND node.
if (RC_BAD( rc = copyAndLinkSubTree( pPool, pOrChildNode, pNewAndNode)))
{
goto Exit;
}
pOrChildNode = pOrChildNode->pNextSib;
}
// Link the newly created AND list in where the old
// AND node was (pOldAndNode). If it was at the root
// of the tree, we will need to create a new OR root.
if ((pOldAndParentNode = pOldAndNode->pParent) == NULL)
{
if (RC_BAD( rc = pPool->poolCalloc( sizeof( SQL_DNF_NODE),
(void **)&pOldAndParentNode)))
{
goto Exit;
}
// NOTE: No need to set anything in this new node, we want it to be
// an OR node, which means that bAndOp is FALSE and pNode is NULL - both
// of which are set by the poolCalloc.
*ppDNFTree = pOldAndParentNode;
}
// Point all of the new AND nodes to the parent of the old AND node.
pAndChildNode = pFirstNewAndNode;
while (pAndChildNode)
{
pAndChildNode->pParent = pOldAndParentNode;
pAndChildNode = pAndChildNode->pNextSib;
}
// Link the new list of AND nodes where the old AND node was.
// Although the old AND node is still allocated, it is no longer
// pointed to from the tree.
if ((pFirstNewAndNode->pPrevSib = pOldAndNode->pPrevSib) != NULL)
{
pFirstNewAndNode->pPrevSib->pNextSib = pFirstNewAndNode;
}
else
{
pOldAndParentNode->pFirstChild = pFirstNewAndNode;
}
if ((pLastNewAndNode->pNextSib = pOldAndNode->pNextSib) != NULL)
{
pLastNewAndNode->pNextSib->pPrevSib = pLastNewAndNode;
}
else
{
pOldAndParentNode->pLastChild = pLastNewAndNode;
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Convert query tree to disjunctive normal form (DNF). Result is
// a list of sub-queries that are ORed together.
//-------------------------------------------------------------------------
RCODE SQLQuery::convertToDNF( void)
{
RCODE rc = NE_SFLM_OK;
SQL_NODE * pCurrNode;
SQL_DNF_NODE * pParentDNFNode;
SQL_DNF_NODE * pCurrDNFNode;
SQL_DNF_NODE * pDNFTree;
SQL_DNF_NODE * pAndList;
SQL_DNF_NODE * pExprList;
F_Pool pool;
SQL_SUBQUERY * pSubQuery;
FLMUINT uiLoop;
pool.poolInit( 1024);
// If the top node in the tree is not an AND or OR operator,
// create a single subquery that has a single operand.
if (m_pQuery->eNodeType != SQL_OPERATOR_NODE ||
m_pQuery->nd.op.eOperator != SQL_AND_OP ||
m_pQuery->nd.op.eOperator != SQL_OR_OP)
{
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_SUBQUERY),
(void **)&m_pFirstSubQuery)))
{
goto Exit;
}
m_pLastSubQuery = m_pFirstSubQuery;
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_NODE *),
(void **)&m_pFirstSubQuery->ppOperands)))
{
goto Exit;
}
m_pFirstSubQuery->uiOperandCount = 1;
m_pFirstSubQuery->ppOperands [0] = m_pQuery;
goto Exit;
}
// Create the tree of DNF nodes to point to all of the AND and OR nodes
// in the tree and their immediate child nodes.
pCurrNode = m_pQuery;
pParentDNFNode = NULL;
pDNFTree = NULL;
for (;;)
{
if (RC_BAD( rc = createDNFNode( &pool, pParentDNFNode,
&pCurrDNFNode, pCurrNode)))
{
goto Exit;
}
if (!pDNFTree)
{
pDNFTree = pCurrDNFNode;
}
// Don't traverse down to child nodes if it is not an AND or OR node.
if (pCurrNode->eNodeType == SQL_OPERATOR_NODE &&
(pCurrNode->nd.op.eOperator == SQL_AND_OP ||
pCurrNode->nd.op.eOperator == SQL_OR_OP))
{
if (pCurrNode->pFirstChild)
{
pCurrNode = pCurrNode->pFirstChild;
pParentDNFNode = pCurrDNFNode;
continue;
}
}
// Go back up to parent until we find one that has a sibling.
while (!pCurrNode->pNextSib)
{
if ((pCurrNode = pCurrNode->pParent) == NULL)
{
break;
}
pParentDNFNode = pParentDNFNode->pParent;
}
if (!pCurrNode)
{
break;
}
pCurrNode = pCurrNode->pNextSib;
}
// Now traverse the DNF tree and move all OR operators to the top.
// When we are done we should have a DNF tree with either a single AND
// node and a list of subordinate expressions, or a single OR node with
// a mix of AND child nodes or non-AND expressions.
pCurrDNFNode = pDNFTree;
for (;;)
{
// If we hit an OR node that is not the root node, it's parent should be
// an AND node. Distribute the AND node's operands over all of the
// OR node's operands.
if (pCurrDNFNode->pNode->eNodeType == SQL_OPERATOR_NODE &&
pCurrDNFNode->pNode->nd.op.eOperator == SQL_OR_OP &&
pCurrDNFNode->pParent)
{
if (RC_BAD( rc = distributeAndOverOr( &pool, pCurrDNFNode,
&pDNFTree)))
{
goto Exit;
}
// Start over at the top of the tree.
pCurrDNFNode = pDNFTree;
continue;
}
// Go to first child, if there is one.
if (pCurrDNFNode->pFirstChild)
{
pCurrDNFNode = pCurrDNFNode->pFirstChild;
continue;
}
// No child nodes, go to sibling nodes. If no sibling nodes,
// traverse back up parent chain until we find one.
while (!pCurrDNFNode->pNextSib)
{
if ((pCurrDNFNode = pCurrDNFNode->pParent) == NULL)
{
break;
}
}
if (!pCurrDNFNode)
{
break;
}
pCurrDNFNode = pCurrDNFNode->pNextSib;
}
// If we get to this point, we have created a DNF tree that either
// as an OR at the top or an AND at the top. If it is an OR at the
// top, we have multiple sub-queries. If it is an AND at the top, we
// have a single sub-query.
if (pDNFTree->bAndOp)
{
pAndList = pDNFTree;
}
else
{
pAndList = pDNFTree->pFirstChild;
flmAssert( pAndList);
}
while (pAndList)
{
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_SUBQUERY),
(void **)&pSubQuery)))
{
goto Exit;
}
// Link the subquery as the last sub-query in our sub-query list
if ((pSubQuery->pPrev = m_pLastSubQuery) != NULL)
{
pSubQuery->pPrev->pNext = pSubQuery;
}
else
{
m_pFirstSubQuery = pSubQuery;
}
m_pLastSubQuery = pSubQuery;
// The child may be a simple expression, in which case it is its
// own sub-query.
if (pAndList->pNode)
{
pSubQuery->uiOperandCount = 1;
// The expression should not have any child nodes.
flmAssert( !pExprList->pFirstChild && !pExprList->pLastChild);
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_NODE *),
(void **)&pSubQuery->ppOperands)))
{
goto Exit;
}
pSubQuery->ppOperands [0] = pAndList->pNode;
// NULL out the node's parent pointer and sibling pointers - just
// to keep things tidy.
pAndList->pNode->pParent = NULL;
pAndList->pNode->pNextSib = NULL;
pAndList->pNode->pPrevSib = NULL;
}
else
{
// Count the expressions in the list - should be at least one.
pExprList = pAndList->pFirstChild;
flmAssert( pExprList);
while (pExprList)
{
// All of the expressions should point to nodes in the query
// tree, and should not be AND or OR nodes. Furthermore,
// they should not have child nodes
flmAssert( pExprList->pNode && !pExprList->pFirstChild &&
!pExprList->pLastChild);
pSubQuery->uiOperandCount++;
pExprList = pExprList->pNextSib;
}
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_NODE *) * pSubQuery->uiOperandCount,
(void **)&pSubQuery->ppOperands)))
{
goto Exit;
}
// Set the pointers in the operand list for the sub-query.
for (uiLoop = 0, pExprList = pAndList->pFirstChild;
pExprList;
uiLoop++, pExprList = pExprList->pNextSib)
{
pSubQuery->ppOperands [uiLoop] = pExprList->pNode;
// NULL out the node's parent pointer and sibling pointers - just
// to keep things tidy.
pExprList->pNode->pParent = NULL;
pExprList->pNode->pNextSib = NULL;
pExprList->pNode->pPrevSib = NULL;
}
}
flmAssert( uiLoop == pSubQuery->uiOperandCount);
pAndList = pAndList->pNextSib;
}
Exit:
return( rc);
}
#if 0
//-------------------------------------------------------------------------
// Desc: Determine if a particular predicate is associated with the
// specified table. Only return TRUE if the predicate is associated
// with this table and only with this table.
//-------------------------------------------------------------------------
FSTATIC FLMBOOL predIsForTable(
SQL_NODE * pPredRootNode,
SQL_TABLE * pTable)
{
FLMBOOL bIsAssociated = FALSE;
SQL_NODE * pCurrNode = pPredRootNode;
for (;;)
{
if (pCurrNode->eNodeType == SQL_COLUMN_NODE)
{
if (pCurrNode->nd.column.pTable == pTable)
{
bIsAssociated = TRUE;
}
else
{
bIsAssociated = FALSE;
break;
}
}
if (pCurrNode->pFirstChild)
{
pCurrNode = pCurrNode->pFirstChild;
continue;
}
// No child nodes, traverse to sibling - or sibling of first node
// in the parent chain that has a next sibling.
for (;;)
{
if (pCurrNode == pPredRootNode)
{
break;
}
if (pCurrNode->pNextSib)
{
break;
}
pCurrNode = pCurrNode->pParent;
}
if (pCurrNode == pPredRootNode)
{
break;
}
// If we get to here, there should be a next sibling.
pCurrNode = pCurrNode->pNextSib;
flmAssert( pCurrNode);
}
return( bIsAssociated);
}
//-------------------------------------------------------------------------
// Desc: Associate a predicate with all of the indexes it pertains to
// with respect to a particular table.
//-------------------------------------------------------------------------
RCODE SQLQuery::getPredKeys(
SQL_PRED * pPred,
SQL_TABLE * pTable)
{
RCODE rc = NE_SFLM_OK;
ICD * pIcd;
SQL_INDEX * pIndex;
SQL_KEY * pKey;
//visit - notes for reference: column number should be unique for the table.
//visit - notes for reference: no required/non-required pieces for indexes on tables
if (RC_BAD( rc = m_pDb->m_pDict->getAttribute( m_pDb, pPred->uiColumnNum,
&defInfo)))
{
goto Exit;
}
// This ICD chain will only contain ICDs for this particular column on
// the table the column belongs to - because column numbers are globally
// unique.
for (pIcd = defInfo.m_pFirstIcd; pIcd; pIcd = pIcd->pNextInChain)
{
// If the table has an index specified for it, skip this ICD if
// it is not that index.
if (pTable->bIndexSet && pTable->uiIndex != pIcd->pIxd->uiIndexNum)
{
continue;
}
// Cannot use the index if it is off-line.
if (pIcd->pIxd->uiFlags & (IXD_OFFLINE | IXD_SUSPENDED))
{
continue;
}
// Find the index off of the table. If not there, add it.
pIndex = pTable->pFirstIndex;
while (pIndex->uiIndexNum != pIcd->pIxd->uiIndexNum)
{
pIndex = pIndex->pNext;
}
if (!pIndex)
{
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_INDEX),
(void **)&pIndex)))
{
goto Exit;
}
pIndex->pTable = pTable;
pIndex->uiIndexNum = pIcd->pIxd->uiIndexNum;
pIndex->uiNumComponents = pIcd->pIxd->uiNumKeyComponents;
if ((pIndex->pPrev = pTable->pLastIndex) != NULL)
{
pIndex->pPrev->pNext = pIndex;
}
else
{
pTable->pFirstIndex = pIndex;
}
pTable->pLastIndex = pIndex;
// Allocate a single key for the index.
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_KEY),
(void **)&pKey)))
{
goto Exit;
}
pIndex->pLastKey = pIndex->pFirstKey = pKey;
pKey->pIndex = pIndex;
// Allocate an array of key components for the key.
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_PRED *) * pIndex->uiNumComponents,
(void **)&pKey->ppKeyComponents)))
{
goto Exit;
}
}
else
{
pKey = pIndex->pFirstKey;
}
// There should not be multiple predicates in a sub-query that
// have the same column, so this key component should NOT already
// be populated.
flmAssert( !pKey->ppKeyComponents [pIcd->uiKeyComponent - 1]);
pKey->ppKeyComponents [pIcd->uiKeyComponent - 1] = pPred;
// NOTE: Costs will be calculated later.
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Determine the order in which to evaluate indexes for a particular
// table and sub-query. Those that have a primary key will be
// give preference over those that don't.
//-------------------------------------------------------------------------
FSTATIC void rankIndexes(
SQL_TABLE * pTable)
{
SQL_INDEX * pIndex;
SQL_INDEX * pPrevIndex;
SQL_INDEX * pNextIndex;
SQL_KEY * pKey;
FLMUINT uiComponentCount;
FLMUINT uiPrevComponentCount;
pIndex = pTable->pFirstIndex;
while (pIndex)
{
pNextIndex = pIndex->pNext;
pPrevIndex = pIndex->pPrev;
// There should only be one key off of the index right now.
pKey = pIndex->pFirstKey;
flmAssert( !pKey->pNext);
// Determine how many of the key's components point to a
// predicate. This stops at the first NULL pointer. There may
// be pointers after that one, but we really don't care, because
// we won't use those components to generate a key.
pKey->uiComponentsUsed = 0;
while (pKey->uiComponentsUsed < pIndex->uiNumComponents &&
pKey->ppKeyComponents [pKey->uiComponentsUsed])
{
pKey->uiComponentsUsed++;
}
// See if this key is using more components that the key for
// prior indexes.
while (pPrevIndex)
{
if (pKey->uiComponentsUsed > pPrevIndex->pFirstKey->uiComponentsUsed)
{
// Move our current key up in front of the previous key - meaning
// it will be evaluated ahead of that key.
// First, unlink the index from its current spot. pIndex->pPrev
// must be non-NULL - otherwise, we wouldn't have a pPrevIndex.
flmAssert( pIndex->pPrev);
pIndex->pPrev->pNext = pIndex->pNext;
if (pIndex->pNext)
{
pIndex->pNext->pPrev = pIndex->pPrev;
}
else
{
pTable->pLastIndex = pIndex->pPrev;
}
// Now, link it in front of pPrevIndex
pIndex->pNext = pPrevIndex;
if ((pIndex->pPrev = pPrevIndex->pPrev) != NULL)
{
pIndex->pPrev->pNext = pIndex;
}
else
{
pTable->pFirstIndex = pIndex;
}
pPrevIndex->pPrev = pIndex;
pPrevIndex = pIndex->pPrev;
}
else
{
pPrevIndex = pPrevIndex->pPrev;
}
}
pIndex = pNextIndex;
}
}
//-------------------------------------------------------------------------
// Desc: Choose the best index for a table of the indexes for which we have
// generated predicate keys.
//-------------------------------------------------------------------------
RCODE SQLQuery::chooseBestIndex(
SQL_TABLE * pTable,
FLMUINT * puiCost)
{
RCODE rc = NE_SFLM_OK
SQL_INDEX * pIndex = pTable->pFirstIndex;
while (pIndex)
{
// Should only be one key on each index at this point.
flmAssert( pIndex->pFirstKey && pIndex->pFirstKey == pIndex->pLastKey);
pIndex = pIndex->pNext;
}
visit
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Calculate the cost of doing a table scan for a table.
//-------------------------------------------------------------------------
RCODE SQLQuery::calcTableScanCost(
SQL_TABLE * pTable,
FLMUINT64 * pui64Cost,
SQLTableCursor ** ppSQLTableCursor)
{
RCODE rc = NE_SFLM_OK;
FLMUINT64 ui64LeafBlocksBetween;
FLMUINT64 ui64TotalRefs;
FLMUINT bTotalsEstimated;
if ((*ppSQLTableCursor = f_new SQLTableCursor) == NULL)
{
rc = RC_SET( NE_SFLM_MEM);
goto Exit;
}
if (RC_BAD( rc = (*ppSQLTableCursor)->setupRange( m_pDb,
pTable->uiTableNum, TRUE, 1, FLM_MAX_UINT64,
&ui64LeafBlocksBetween, &ui64TotalRefs,
&bTotalsEstimated)))
{
(*ppSQLTableCursor)->Release();
*ppSQLTableCursor = NULL;
goto Exit;
}
if (!ui64LeafBlocksBetween)
{
*pui64Cost = 1;
}
else
{
*pui64Cost = ui64LeafBlocksBetween;
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Merge keys from pSrcTable into pDestTable.
//-------------------------------------------------------------------------
RCODE SQLQuery::mergeKeys(
SQL_TABLE * pDestTable,
SQL_TABLE * pSrcTable)
{
visit
}
//-------------------------------------------------------------------------
// Desc: Optimize a particular table for a particular sub-query.
//-------------------------------------------------------------------------
RCODE SQLQuery::optimizeTable(
SQL_SUBQUERY * pSubQuery,
SQL_TABLE * pTable)
{
RCODE rc = NE_SFLM_OK;
SQL_TABLE tmpTable;
FLMUINT uiLoop;
SQL_NODE * pOperand;
SQL_PRED * pPred;
void * pvMark = m_pool.poolMark();
SQLTableCursor * pSQLTableCursor = NULL;
// This routine should not be called if the table has already been
// marked to do a table scan.
visit - the caller should handle this case
flmAssert( !pTable->bScan);
f_memset( &tmpTable, 0, sizeof( SQL_TABLE));
tmpTable.uiTableNum = pTable->uiTableNum;
tmpTable.uiIndex = pTable->uiIndex;
tmpTable.bIndexSet = pTable->bIndexSet;
// Traverse the predicates of the sub-query. If any are found
// that are not predicates, the table must be scanned.
for (uiLoop = 0, pOperand = pSubQuery->ppOperands [0];
uiLoop < pSubQuery->uiOperandCount;
uiLoop++, pOperand = pSubQuery->ppOperands [uiLoop])
{
// If we hit a predicate that has not been turned into
// an SQL_PRED_NODE, it is not optimizable.
if (pOperand->eNodeType != SQL_PRED_NODE)
{
// See if the current table is involved in this predicate. If so,
// and it is the only table involved, the table should be scanned.
// Setting pFirstIndex and pLastIndex to NULL will cause this to
// happen below.
if (predIsForTable( pOperand, pTable))
{
m_pool.poolReset( pvMark);
tmpTable.pFirstIndex = NULL;
tmpTable.pLastIndex = NULL;
break;
}
}
else if (pOperand->nd.pred.pTable == pTable)
{
SQL_PRED * pPred = &pOperand->nd.pred;
// We cannot use from and until keys for not/negative operators.
// We set pFirstIndex and pLastIndex to NULL to indicate that a
// table scan must occur.
if ((pPred->bNotted && pPred->eOperator == SQL_MATCH_OP) ||
pPred->eOperator == SQL_NE_OP)
{
m_pool.poolReset( pvMark);
tmpTable.pFirstIndex = NULL;
tmpTable.pLastIndex = NULL;
break;
}
visit - before we collect keys for this predicate, we should check to see
if the predicate is a subset of any other predicate in previous sub-queries that
we have already optimized where the predicate in the previous sub-query was used
to optimize that previous sub-query. If so, we should simply merge this sub-query with
that one - it will be a waste of time to get another set of keys for this
sub-query. This, of course, implies that we need to keep track of the
predicates that were selected to optimize a particular sub-query.
// See if there are any indexes for this predicate's column.
// For now we are just collecting them. We will calculate
// the best one later.
if (RC_BAD( rc = getPredKeys( pPred, &tmpTable)))
{
goto Exit;
}
}
}
// If we didn't find indexes for this table, set the bScan flag.
if (!tmpTable.pFirstIndex)
{
tmpTable.bScan = TRUE;
if (RC_BAD( rc = calcTableScanCost( &tmpTable, &tmpTable.uiCost,
&pSQLTableCursor)))
{
goto Exit;
}
}
else
{
// Rank the indexes to determine which ones to estimate cost for
// first.
rankIndexes( &tmpTable);
// Find the index with the lowest cost, if any.
// If the lowest cost index is still high, estimate the cost of doing
// a table scan.
if (RC_BAD( rc = chooseBestIndex( &tmpTable, &tmpTable.uiCost)))
{
goto Exit;
}
// Should be one index left after this. If the cost is high, see if
// a table scan would be cheaper.
if (tmpTable.uiCost > 8)
{
FLMUINT uiScanCost;
if (RC_BAD( rc = calcTableScanCost( &tmpTable, &uiScanCost,
&pSQLTableCursor)))
{
goto Exit;
}
if (uiScanCost < tmpTable.uiCost)
{
m_pool.poolReset( pvMark);
tmpTable.uiCost = uiScanCost;
tmpTable.bScan = TRUE;
tmpTable.pFirstIndex = NULL;
tmpTable.pLastIndex = NULL;
}
else
{
pSQLTableCursor->Release();
pSQLTableCursor = NULL;
}
}
}
// If we determined that we must do a table scan, set the bScan flag
// for the master table. Otherwise, merge these keys
if (tmpTable.bScan)
{
pTable->bScan = TRUE;
pTable->uiCost = tmpTable.uiCost;
// Better have calculated a cost and have a collection
// cursor at this point.
flmAssert( pSQLTableCursor);
pTable->pSQLTableCursor = pSQLTableCursor;
pSQLTableCursor = NULL;
pTable->pFirstIndex = NULL;
pTable->pLastIndex = NULL;
}
else
{
if (RC_BAD( rc = mergeKeys( pTable, &tmpTable)))
{
goto Exit;
}
pTable->uiCost += tmpTable.uiCost;
}
Exit:
if (pSQLTableCursor)
{
pSQLTableCursor->Release();
}
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Optimize the sub-queries of an SQL query.
//-------------------------------------------------------------------------
RCODE SQLQuery::optimizeSubQueries( void)
{
RCODE rc = NE_SFLM_OK;
SQL_SUBQUERY * pSubQuery;
SQL_TABLE * pTable;
// For each table in our expression, attempt to pick an index for each
// subquery.
for (pTable = m_pFirstTable; pTable; pTable = pTable->pNext)
{
pSubQuery = m_pFirstSubQuery;
while (pSubQuery)
{
if (RC_BAD( rc = optimizeTable( pSubQuery, pTable)))
{
goto Exit;
}
// If the optimization decided we should scan the table, there
// is no need to look at any more sub-queries for this table.
if (pTable->bScan)
{
break;
}
pSubQuery = pSubQuery->pNext;
}
// See if a table scan is going to be cheaper.
if (!pTable->bScan && pTable->uiCost > 8)
{
SQLTableCursor * pSQLTableCursor = NULL;
FLMUINT uiScanCost;
if (RC_BAD( rc = calcTableScanCost( pTable, &uiScanCost,
&pSQLTableCursor)))
{
goto Exit;
}
if (uiScanCost < pTable->uiCost)
{
pTable->uiCost = uiScanCost;
pTable->bScan = TRUE;
pTable->pSQLTableCursor = pSQLTableCursor;
pTable->pFirstIndex = NULL;
pTable->pLastIndex = NULL;
}
else
{
pSQLTableCursor->Release();
}
}
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Optimize an SQL query.
//-------------------------------------------------------------------------
RCODE SQLQuery::optimize( void)
{
RCODE rc = NE_SFLM_OK;
if (m_bOptimized)
{
goto Exit;
}
// We save the F_Database object so that we can always check and make
// sure we are associated with this database on any query operations
// that occur after optimization. -- Link it into the list of queries
// off of the F_Database object. NOTE: We may not always use the
// same F_Db object, but it must always be the same F_Database object.
m_pDatabase = m_pDb->m_pDatabase;
m_pNext = NULL;
m_pDatabase->lockMutex();
if ((m_pPrev = m_pDatabase->m_pLastSQLQuery) != NULL)
{
m_pPrev->m_pNext = this;
}
else
{
m_pDatabase->m_pFirstSQLQuery = this;
}
m_pDatabase->m_pLastSQLQuery = this;
m_pDatabase->unlockMutex();
// Make sure we have a completed expression
if (m_pCurrParseState)
{
if (m_pCurrParseState->pPrev ||
m_pCurrParseState->uiNestLevel ||
(m_pCurrParseState->pLastNode &&
m_pCurrParseState->pLastNode->eNodeType == FLM_OPERATOR_NODE))
{
rc = RC_SET( NE_SFLM_Q_INCOMPLETE_QUERY_EXPR);
goto Exit;
}
m_pQuery = m_pCurrParseState->pRootNode;
}
m_uiLanguage = m_pDb->getDefaultLanguage();
// An empty expression should scan the database and return everything.
if (!m_pQuery)
{
if (m_bIndexSet && m_uiIndex)
{
rc = setupIndexScan();
}
else
{
m_bScan = TRUE;
}
goto Exit;
}
// Handle the case of a value node or arithmetic expression at the root
// These types of expressions do not return results from the database.
if (m_pQuery->eNodeType == SQL_VALUE_NODE)
{
if (m_pQuery->nd.value.eValType == SQL_BOOL_VAL &&
m_pQuery->nd.value.val.eBool == SQL_TRUE)
{
m_bScan = TRUE;
}
else
{
m_bEmpty = TRUE;
}
}
else if (m_pQuery->eNodeType == SQL_OPERATOR_NODE &&
isSQLArithOp( pQNode->nd.op.eOperator))
{
m_bEmpty = TRUE;
goto Exit;
}
// If the user explicitly said to NOT use an index, we will not
if (m_bIndexSet && !m_uiIndex)
{
m_bScan = TRUE;
goto Exit;
}
// Flatten the AND and OR operators in the query tree.
if (RC_BAD( rc = flattenTree()))
{
goto Exit;
}
// Convert to DNF
if (RC_BAD( rc = convertToDNF()))
{
goto Exit;
}
// Convert all operands of each sub-query to predicates where
// possible.
if (RC_BAD( rc = convertOperandsToPredicates()))
{
goto Exit;
}
// Optimize each sub-query.
if (RC_BAD( rc = optimizeSubQueries()))
{
goto Exit;
}
m_bOptimized = TRUE;
Exit:
return( rc);
}
#endif