Files
mars-flaim/sql/src/sqloptimize.cpp
dsandersoremutah a7e6b942f8 Latest round of changes for SQL query optimization.
git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@709 0109f412-320b-0410-ab79-c3e0c5ffbbe6
2006-07-25 22:40:53 +00:00

2948 lines
70 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"
#define MINIMUM_COST_ESTIMATE 8
// Local function prototypes
FSTATIC RCODE sqlGetRowIdValue(
SQL_VALUE * pValue);
FSTATIC RCODE setupPredicate(
SQL_PRED * pPred,
SQL_TABLE * pSQLTable,
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);
FSTATIC FLMBOOL predIsForOnlyThisTable(
SQL_NODE * pPredRootNode,
SQL_TABLE * pSQLTable);
FSTATIC void rankIndexes(
F_Db * pDb,
SQL_INDEX ** ppFirstSQLIndex,
SQL_INDEX ** ppLastSQLIndex);
//-------------------------------------------------------------------------
// 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_SFLM_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 * pSQLTable,
FLMUINT uiColumnNum,
eSQLQueryOperators eOperator,
FLMUINT uiCompareRules,
FLMBOOL bNotted,
SQL_VALUE * pValue)
{
RCODE rc = NE_SFLM_OK;
pPred->pSQLTable = pSQLTable;
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->pSQLTable,
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: 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 * pSQLTable,
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;
FLMBOOL bAlwaysFalse;
SQL_PRED * pPred;
// Convert the constant value in a row 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->pSQLTable == pSQLTable && 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, 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,
pSQLTable, uiColumnNum,
eOperator, uiCompareRules, bNotted, pValue)))
{
goto Exit;
}
pSubQuery->ppOperands [uiOperand] = pOperandNode;
// 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 * pSQLTable;
FLMUINT uiColumnNum;
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.pSQLTable,
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)))
{
eSQLQueryOperators eOperator = pSQLNode->nd.op.eOperator;
// 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)
{
pSQLTable = pSQLNode->pFirstChild->nd.column.pSQLTable;
uiColumnNum = pSQLNode->pFirstChild->nd.column.uiColumnNum;
pValue = &pSQLNode->pLastChild->currVal;
}
else
{
pSQLTable = pSQLNode->pLastChild->nd.column.pSQLTable;
uiColumnNum = pSQLNode->pLastChild->nd.column.uiColumnNum;
pValue = &pSQLNode->pFirstChild->currVal;
// 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,
pSQLTable, 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->currVal.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: Reduce the query tree. This will strip out NOT nodes and
// resolve constant expressions to a single node. It also weeds
// out all boolean constants that are operands of AND or OR operators.
// Finally, if the bFlattenTree parameter is TRUE, it will coalesce
// AND and OR nodes so that they can have multiple operands.
//-------------------------------------------------------------------------
RCODE SQLQuery::reduceTree(
FLMBOOL bFlattenTree)
{
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 (bFlattenTree && 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->currVal.eValType == SQL_BOOL_VAL)
{
if (pSQLNode->currVal.val.eBool == SQL_TRUE)
{
pSQLNode->currVal.val.eBool = SQL_FALSE;
}
else if (pSQLNode->currVal.val.eBool == SQL_FALSE)
{
pSQLNode->currVal.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->currVal.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->currVal,
&pSQLNode->pLastChild->currVal,
pSQLNode->nd.op.eOperator,
&pSQLNode->currVal)))
{
goto Exit;
}
pSQLNode->eNodeType = SQL_VALUE_NODE;
pSQLNode->currVal.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;
}
// Link the newly created sub-tree to the passed in parent node as its
// last child.
flmAssert( pNewSubTree);
pNewSubTree->pParent = pParentNode;
pNewSubTree->pNextSib = NULL;
if ((pNewSubTree->pPrevSib = pParentNode->pLastChild) != NULL)
{
pNewSubTree->pPrevSib->pNextSib = pNewSubTree;
}
else
{
pParentNode->pFirstChild = pNewSubTree;
}
pParentNode->pLastChild = pNewSubTree;
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;
}
}
flmAssert( uiLoop == pSubQuery->uiOperandCount);
pAndList = pAndList->pNextSib;
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// 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 predIsForOnlyThisTable(
SQL_NODE * pPredRootNode,
SQL_TABLE * pSQLTable)
{
FLMBOOL bIsAssociated = FALSE;
SQL_NODE * pCurrNode = pPredRootNode;
for (;;)
{
if (pCurrNode->eNodeType == SQL_COLUMN_NODE)
{
if (pCurrNode->nd.column.pSQLTable == pSQLTable)
{
bIsAssociated = TRUE;
}
else
{
// Predicate is associated with more than the table that
// was passed in.
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(
F_TABLE * pTable,
FLMUINT uiForceIndexNum,
SQL_PRED * pPred,
SQL_INDEX ** ppFirstSQLIndex,
SQL_INDEX ** ppLastSQLIndex)
{
RCODE rc = NE_SFLM_OK;
ICD * pIcd;
SQL_INDEX * pSQLIndex;
SQL_KEY * pKey;
F_COLUMN * pColumn = m_pDb->m_pDict->getColumn( pTable, pPred->uiColumnNum);
F_INDEX * pIndex;
FLMUINT uiKeyComponent;
// This ICD chain will only contain ICDs for this particular column on
// the table the column belongs to.
for (pIcd = pColumn->pFirstIcd; pIcd; pIcd = pIcd->pNextInChain)
{
// If the table has an index specified for it, skip this ICD if
// it is not that index.
if (uiForceIndexNum && uiForceIndexNum != pIcd->uiIndexNum)
{
continue;
}
pIndex = m_pDb->m_pDict->getIndex( pIcd->uiIndexNum);
// Cannot use the index if it is off-line.
if (pIndex->uiFlags & (IXD_OFFLINE | IXD_SUSPENDED))
{
continue;
}
// Find the index off of the table. If not there, add it.
pSQLIndex = *ppFirstSQLIndex;
while (pSQLIndex->uiIndexNum != pIcd->uiIndexNum)
{
pSQLIndex = pSQLIndex->pNext;
}
if (!pSQLIndex)
{
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_INDEX),
(void **)&pSQLIndex)))
{
goto Exit;
}
pSQLIndex->uiIndexNum = pIcd->uiIndexNum;
if ((pSQLIndex->pPrev = *ppLastSQLIndex) != NULL)
{
pSQLIndex->pPrev->pNext = pSQLIndex;
}
else
{
*ppFirstSQLIndex = pSQLIndex;
}
*ppLastSQLIndex = pSQLIndex;
// Allocate a single key for the index.
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_KEY),
(void **)&pKey)))
{
goto Exit;
}
pSQLIndex->pLastSQLKey = pSQLIndex->pFirstSQLKey = pKey;
// Allocate an array of key components for the key.
if (RC_BAD( rc = m_pool.poolCalloc(
sizeof( SQL_PRED *) * pIndex->uiNumKeyComponents,
(void **)&pKey->ppPredicates)))
{
goto Exit;
}
}
else
{
pKey = pSQLIndex->pFirstSQLKey;
}
// There should not be multiple predicates in a sub-query that
// have the same column, so this key component should NOT already
// be populated.
uiKeyComponent = (FLMUINT)(pIcd - pIndex->pKeyIcds);
flmAssert( !pKey->ppPredicates [uiKeyComponent]);
pKey->ppPredicates [uiKeyComponent] = pPred;
// NOTE: Costs will be calculated later.
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Determine the order in which to evaluate indexes. Those that have
// a primary key will be given preference over those that don't.
//-------------------------------------------------------------------------
FSTATIC void rankIndexes(
F_Db * pDb,
SQL_INDEX ** ppFirstSQLIndex,
SQL_INDEX ** ppLastSQLIndex)
{
SQL_INDEX * pSQLIndex;
SQL_INDEX * pPrevSQLIndex;
SQL_INDEX * pNextSQLIndex;
SQL_KEY * pKey;
F_INDEX * pIndex;
pSQLIndex = *ppFirstSQLIndex;
while (pSQLIndex)
{
pNextSQLIndex = pSQLIndex->pNext;
pPrevSQLIndex = pSQLIndex->pPrev;
// There should only be one key off of the index right now.
pKey = pSQLIndex->pFirstSQLKey;
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.
pIndex = pDb->getDict()->getIndex( pSQLIndex->uiIndexNum);
pKey->uiComponentsUsed = 0;
while (pKey->uiComponentsUsed < pIndex->uiNumKeyComponents &&
pKey->ppPredicates [pKey->uiComponentsUsed])
{
pKey->uiComponentsUsed++;
}
// See if this key is using more components that the key for
// prior indexes.
while (pPrevSQLIndex)
{
if (pKey->uiComponentsUsed > pPrevSQLIndex->pFirstSQLKey->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. pSQLIndex->pPrev
// must be non-NULL - otherwise, we wouldn't have a pPrevSQLIndex.
flmAssert( pSQLIndex->pPrev);
pSQLIndex->pPrev->pNext = pSQLIndex->pNext;
if (pSQLIndex->pNext)
{
pSQLIndex->pNext->pPrev = pSQLIndex->pPrev;
}
else
{
*ppLastSQLIndex = pSQLIndex->pPrev;
}
// Now, link it in front of pPrevSQLIndex
pSQLIndex->pNext = pPrevSQLIndex;
if ((pSQLIndex->pPrev = pPrevSQLIndex->pPrev) != NULL)
{
pSQLIndex->pPrev->pNext = pSQLIndex;
}
else
{
*ppFirstSQLIndex = pSQLIndex;
}
pPrevSQLIndex->pPrev = pSQLIndex;
pPrevSQLIndex = pSQLIndex->pPrev;
}
else
{
pPrevSQLIndex = pPrevSQLIndex->pPrev;
}
}
pSQLIndex = pNextSQLIndex;
}
}
//-------------------------------------------------------------------------
// Desc: Choose the best index for a table of the indexes for which we have
// generated predicate keys. All other indexes for the table will
// be freed
//-------------------------------------------------------------------------
RCODE SQLQuery::chooseBestIndex(
F_TABLE * pTable,
SQL_INDEX * pFirstSQLIndex,
SQL_INDEX ** ppBestSQLIndex
)
{
RCODE rc = NE_SFLM_OK;
SQL_INDEX * pBestSQLIndex = NULL;
SQL_KEY * pSQLKey;
FSIndexCursor * pFSIndexCursor = NULL;
F_INDEX * pIndex;
while (pFirstSQLIndex)
{
// Should only be one key on each index at this point.
flmAssert( pFirstSQLIndex->pFirstSQLKey &&
pFirstSQLIndex->pFirstSQLKey == pFirstSQLIndex->pLastSQLKey);
pSQLKey = pFirstSQLIndex->pFirstSQLKey;
pIndex = m_pDb->m_pDict->getIndex( pFirstSQLIndex->uiIndexNum);
flmAssert( pIndex);
// Allocate an index cursor, if necessary.
if (!pFSIndexCursor)
{
if ((pFSIndexCursor = f_new FSIndexCursor) == NULL)
{
rc = RC_SET( NE_SFLM_MEM);
goto Exit;
}
}
else
{
pFSIndexCursor->resetCursor();
}
// Setup from and until keys and calculate the cost.
if (RC_BAD( rc = pFSIndexCursor->setupKeys( m_pDb, pIndex, pTable,
pSQLKey->ppPredicates)))
{
goto Exit;
}
// See if this index has a lower cost than our current best index.
if (!pBestSQLIndex ||
pFSIndexCursor->getCost() < pBestSQLIndex->pFirstSQLKey->pFSIndexCursor->getCost())
{
pFirstSQLIndex->pFirstSQLKey->pFSIndexCursor = pFSIndexCursor;
// If we have a best index, keep its index cursor so we can just
// reset it in the loop above rather than having to free it and
// allocate another one - a little optimization.
if (!pBestSQLIndex)
{
pFSIndexCursor = NULL;
}
else
{
pFSIndexCursor = pBestSQLIndex->pFirstSQLKey->pFSIndexCursor;
}
pBestSQLIndex = pFirstSQLIndex;
// If our best index's cost is low enough, no need to check any other
// indexes.
if (pFSIndexCursor->getCost() < MINIMUM_COST_ESTIMATE)
{
break;
}
}
pFirstSQLIndex = pFirstSQLIndex->pNext;
}
Exit:
if (pFSIndexCursor)
{
pFSIndexCursor->Release();
}
*ppBestSQLIndex = pBestSQLIndex;
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Merge keys from pSQLIndex into the list of indexes for pSQLTable.
//-------------------------------------------------------------------------
RCODE SQLQuery::mergeKeys(
SQL_TABLE * pSQLTable,
SQL_INDEX * pSQLIndex)
{
RCODE rc = NE_SFLM_OK;
SQL_INDEX * pFoundSQLIndex;
// Should only be one key for this index.
flmAssert( pSQLIndex->pFirstSQLKey == pSQLIndex->pLastSQLKey);
// See if there is an index for the table that matches this one. There
// should only be one.
pFoundSQLIndex = pSQLTable->pFirstSQLIndex;
while (pFoundSQLIndex && pFoundSQLIndex->uiIndexNum != pSQLIndex->uiIndexNum)
{
pFoundSQLIndex = pFoundSQLIndex->pNext;
}
if (!pFoundSQLIndex)
{
// Did not find a matching index.
// Just link the index at the end of the table's list of indexes.
// NOTE: It really doesn't matter if it is linked in at the beginning
// or the end or the middle somewhere as long as it gets into the
// list.
if ((pSQLIndex->pPrev = pSQLTable->pLastSQLIndex) != NULL)
{
pSQLIndex->pPrev->pNext = pSQLIndex;
}
else
{
pSQLTable->pFirstSQLIndex = pSQLIndex;
}
pSQLTable->pLastSQLIndex = pSQLIndex;
// Increment the total cost for the table
pSQLTable->ui64TotalCost += pSQLIndex->pFirstSQLKey->pFSIndexCursor->getCost();
}
else
{
FLMUINT ui64SaveCost;
FLMBOOL bUnioned = FALSE;
SQL_KEY * pSQLKey;
SQL_KEY * pIncomingSQLKey = pSQLIndex->pFirstSQLKey;
FSIndexCursor * pMergeFromFSIndexCursor = pIncomingSQLKey->pFSIndexCursor;
FLMINT iCompare;
// Found a matching index, see if we can union the incoming index's
// key with one of the existing keys for the found index.
// If not, we will simply link the new key into the list.
pSQLKey = pFoundSQLIndex->pFirstSQLKey;
while (pSQLKey)
{
ui64SaveCost = pSQLKey->pFSIndexCursor->getCost();
if (RC_BAD( rc = pSQLKey->pFSIndexCursor->unionKeys( m_pDb,
pMergeFromFSIndexCursor, &bUnioned, &iCompare)))
{
goto Exit;
}
if (bUnioned)
{
pSQLTable->ui64TotalCost -= ui64SaveCost;
pSQLTable->ui64TotalCost += pSQLKey->pFSIndexCursor->getCost();
break;
}
else if (iCompare < 0)
{
// The incoming key does not overlap the current key, and is
// less than it, so we will link it in before the current key.
pIncomingSQLKey->pNext = pSQLKey;
if ((pIncomingSQLKey->pPrev = pSQLKey->pPrev) != NULL)
{
pSQLKey->pPrev->pNext = pIncomingSQLKey;
}
else
{
pFoundSQLIndex->pFirstSQLKey = pIncomingSQLKey;
}
pSQLKey->pPrev = pIncomingSQLKey;
// We didn't really union, but we did link it into the list,
// so we set this flag to keep us from linking it at the end
// of the list outside this loop.
bUnioned = TRUE;
break;
}
pSQLKey = pSQLKey->pNext;
}
// If we did not union with any of the existing keys, this incoming
// key is greater than all of the keys in the index (otherwise it
// would have been linked into the list in the above loop). Hence,
// we link it in at the end of the list of keys for the found index.
// The keys in the list are deliberately kept in ascending order
// so that we can traverse through the index in order if need be.
if (!bUnioned)
{
// Link the incoming key as the last key off of the found index.
pSQLKey = pSQLIndex->pFirstSQLKey;
if ((pSQLKey->pPrev = pFoundSQLIndex->pLastSQLKey) != NULL)
{
pSQLKey->pPrev->pNext = pSQLKey;
}
else
{
pFoundSQLIndex->pFirstSQLKey = pSQLKey;
}
pFoundSQLIndex->pLastSQLKey = pSQLKey;
}
// Probably don't need to do this, because we should be getting
// rid of the SQL_INDEX structure pointed to by pSQLIndex (the caller
// should not use it anymore), but this it keeps us from having
// two SQL_INDEX structures point to the same key - just in case
// something on the outside changes.
pSQLIndex->pFirstSQLKey = NULL;
pSQLIndex->pLastSQLKey = NULL;
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Optimize a particular table for a particular sub-query.
//-------------------------------------------------------------------------
RCODE SQLQuery::optimizeTable(
SQL_SUBQUERY * pSubQuery,
SQL_TABLE * pSQLTable)
{
RCODE rc = NE_SFLM_OK;
F_TABLE * pTable = m_pDb->m_pDict->getTable( pSQLTable->uiTableNum);
SQL_INDEX * pFirstSQLIndex = NULL;
SQL_INDEX * pLastSQLIndex = NULL;
SQL_INDEX * pSQLIndex;
FLMUINT uiLoop;
SQL_NODE * pOperand;
void * pvMark = m_pool.poolMark();
// This routine should not be called if the table has already been
// marked to do a table scan.
flmAssert( !pSQLTable->bScan);
// 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 pFirstSQLIndex and pLastSQLIndex to NULL will cause this to
// happen below.
if (predIsForOnlyThisTable( pOperand, pSQLTable))
{
if (RC_BAD( rc = setupTableScan( pSQLTable)))
{
goto Exit;
}
freeTableIndexes( pSQLTable);
m_pool.poolReset( pvMark);
break;
}
}
else if (pOperand->nd.pred.pSQLTable == pSQLTable)
{
SQL_PRED * pPred = &pOperand->nd.pred;
// We cannot use from and until keys for not/negative operators.
// We set pFirstSQLIndex and pLastSQLIndex to NULL to indicate that a
// table scan must occur.
if ((pPred->bNotted && pPred->eOperator == SQL_MATCH_OP) ||
pPred->eOperator == SQL_NE_OP)
{
if (RC_BAD( rc = setupTableScan( pSQLTable)))
{
goto Exit;
}
freeTableIndexes( pSQLTable);
m_pool.poolReset( pvMark);
break;
}
// 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( pTable, pSQLTable->uiIndexNum,
pPred, &pFirstSQLIndex, &pLastSQLIndex)))
{
goto Exit;
}
}
}
// If we found indexes for the table, choose the best one.
if (pFirstSQLIndex)
{
// Rank the indexes to determine which ones to estimate cost for
// first.
rankIndexes( m_pDb, &pFirstSQLIndex, &pLastSQLIndex);
// Find the index with the lowest cost.
if (RC_BAD( rc = chooseBestIndex( pTable, pFirstSQLIndex, &pSQLIndex)))
{
goto Exit;
}
// Merge the selected key for selected index into the keys for the
// master table.
if (RC_BAD( rc = mergeKeys( pSQLTable, pSQLIndex)))
{
goto Exit;
}
}
Exit:
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 * pSQLTable;
// For each table in our expression, attempt to pick an index for each
// subquery.
for (pSQLTable = m_pFirstSQLTable; pSQLTable; pSQLTable = pSQLTable->pNext)
{
if (pSQLTable->bScan)
{
if (RC_BAD( rc = setupTableScan( pSQLTable)))
{
goto Exit;
}
}
else
{
pSubQuery = m_pFirstSubQuery;
while (pSubQuery)
{
if (RC_BAD( rc = optimizeTable( pSubQuery, pSQLTable)))
{
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 (pSQLTable->bScan)
{
break;
}
pSubQuery = pSubQuery->pNext;
}
// If the table's cost is still zero, that means it was not optimized for
// any of the sub-queries, so we need to have it do a table scan or
// an index scan.
if (!pSQLTable->ui64TotalCost)
{
if (pSQLTable->uiIndexNum)
{
if (RC_BAD( rc = setupIndexScan( pSQLTable)))
{
goto Exit;
}
}
else
{
if (RC_BAD( rc = setupTableScan( pSQLTable)))
{
goto Exit;
}
}
}
else if (!pSQLTable->bScan &&
pSQLTable->ui64TotalCost > MINIMUM_COST_ESTIMATE)
{
FLMUINT64 ui64SaveCost = pSQLTable->ui64TotalCost;
if (RC_BAD( rc = setupTableScan( pSQLTable)))
{
goto Exit;
}
if (pSQLTable->pFSTableCursor->getCost() < ui64SaveCost)
{
pSQLTable->uiIndexNum = 0;
freeTableIndexes( pSQLTable);
}
else
{
// Get rid of the table cursor that was set up and stay
// with the index cursors.
pSQLTable->bScan = FALSE;
pSQLTable->pFSTableCursor->Release();
pSQLTable->pFSTableCursor = NULL;
pSQLTable->ui64TotalCost = ui64SaveCost;
}
}
}
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Add an SQL_INDEX structure to the list of indexes for an
// SQL_TABLE structure.
//-------------------------------------------------------------------------
RCODE SQLQuery::addIndexToTable(
SQL_TABLE * pSQLTable,
FLMUINT uiIndexNum,
SQL_INDEX ** ppSQLIndex)
{
RCODE rc = NE_SFLM_OK;
SQL_INDEX * pSQLIndex;
// Allocate the SQL_INDEX structure
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_INDEX),
(void **)&pSQLIndex)))
{
goto Exit;
}
// Link index to end of the list of indexes for the table.
pSQLIndex->uiIndexNum = uiIndexNum;
if ((pSQLIndex->pPrev = pSQLTable->pLastSQLIndex) != NULL)
{
pSQLTable->pLastSQLIndex->pNext = pSQLIndex;
}
else
{
pSQLTable->pFirstSQLIndex = pSQLIndex;
}
pSQLTable->pLastSQLIndex = pSQLIndex;
if (ppSQLIndex)
{
*ppSQLIndex = pSQLIndex;
}
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Add an SQL_KEY structure to the list of keys for an
// SQL_INDEX structure.
//-------------------------------------------------------------------------
RCODE SQLQuery::addKeyToIndex(
SQL_INDEX * pSQLIndex,
SQL_KEY ** ppSQLKey)
{
RCODE rc = NE_SFLM_OK;
SQL_KEY * pSQLKey;
// Allocate the SQL_KEY structure
if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_KEY),
(void **)&pSQLKey)))
{
goto Exit;
}
// Link key to end of the list of keys for the index.
if ((pSQLKey->pPrev = pSQLIndex->pLastSQLKey) != NULL)
{
pSQLIndex->pLastSQLKey->pNext = pSQLKey;
}
else
{
pSQLIndex->pFirstSQLKey = pSQLKey;
}
pSQLIndex->pLastSQLKey = pSQLKey;
*ppSQLKey = pSQLKey;
Exit:
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Setup to scan an index for a table.
//-------------------------------------------------------------------------
RCODE SQLQuery::setupIndexScan(
SQL_TABLE * pSQLTable)
{
RCODE rc = NE_SFLM_OK;
F_INDEX * pIndex;
F_TABLE * pTable;
FSIndexCursor * pFSIndexCursor = NULL;
SQL_INDEX * pSQLIndex;
SQL_KEY * pSQLKey;
// At this point, there should not be any SQL_INDEX structures
// associated with the table as yet.
flmAssert( !pSQLTable->pFirstSQLIndex);
if ((pIndex = m_pDb->m_pDict->getIndex( pSQLTable->uiIndexNum)) == NULL)
{
rc = RC_SET( NE_SFLM_Q_INVALID_INDEX);
goto Exit;
}
if (pSQLTable->uiTableNum != pIndex->uiTableNum)
{
rc = RC_SET( NE_SFLM_Q_INVALID_TABLE_FOR_INDEX);
goto Exit;
}
pTable = m_pDb->m_pDict->getTable( pSQLTable->uiTableNum);
// Make sure the index is not offline.
if (pIndex->uiFlags & (IXD_OFFLINE | IXD_SUSPENDED))
{
rc = RC_SET( NE_SFLM_INDEX_OFFLINE);
goto Exit;
}
// Allocate an index structure and associate it with the table.
if (RC_BAD( rc = addIndexToTable( pSQLTable, pSQLTable->uiIndexNum,
&pSQLIndex)))
{
goto Exit;
}
// Allocate a key structure and associate it with the index.
if (RC_BAD( rc = addKeyToIndex( pSQLIndex, &pSQLKey)))
{
goto Exit;
}
// Allocate an index cursor and set it up to scan the index from
// first to last.
if ((pFSIndexCursor = f_new FSIndexCursor) == NULL)
{
rc = RC_SET( NE_SFLM_MEM);
goto Exit;
}
// Setup to scan from beginning of index to end of index.
if (RC_BAD( rc = pFSIndexCursor->setupKeys( m_pDb, pIndex, pTable, NULL)))
{
goto Exit;
}
// Setup a single key for the index.
pSQLKey->pFSIndexCursor = pFSIndexCursor;
pSQLTable->ui64TotalCost = pFSIndexCursor->getCost();
// Set pFSIndexCursor to NULL so it will not be released
// below.
pFSIndexCursor = NULL;
Exit:
if (pFSIndexCursor)
{
pFSIndexCursor->Release();
}
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Setup to scan a table.
//-------------------------------------------------------------------------
RCODE SQLQuery::setupTableScan(
SQL_TABLE * pSQLTable)
{
RCODE rc = NE_SFLM_OK;
FSTableCursor * pFSTableCursor = NULL;
pSQLTable->bScan = TRUE;
// No index set, or the index that was set was zero, so do
// a full table scan.
if ((pFSTableCursor = f_new FSTableCursor) == NULL)
{
rc = RC_SET( NE_SFLM_MEM);
goto Exit;
}
if (RC_BAD( rc = pFSTableCursor->setupRange( m_pDb,
pSQLTable->uiTableNum, 1, FLM_MAX_UINT64,
TRUE)))
{
goto Exit;
}
pSQLTable->pFSTableCursor = pFSTableCursor;
pSQLTable->ui64TotalCost = pFSTableCursor->getCost();
// Set to NULL so it won't be released at Exit.
pFSTableCursor = NULL;
Exit:
if (pFSTableCursor)
{
pFSTableCursor->Release();
}
return( rc);
}
//-------------------------------------------------------------------------
// Desc: Setup to scan each table that has been specified. It may be that
// indexes were specified for the tables - in which case we will
// setup to scan the indexes.
//-------------------------------------------------------------------------
RCODE SQLQuery::setupScans( void)
{
RCODE rc = NE_SFLM_OK;
SQL_TABLE * pSQLTable;
// If no tables were specified, the query will return an
// empty result.
if (!m_pFirstSQLTable)
{
m_bEmpty = TRUE;
goto Exit;
}
for (pSQLTable = m_pFirstSQLTable; pSQLTable; pSQLTable = pSQLTable->pNext)
{
if (pSQLTable->uiIndexNum)
{
if (RC_BAD( rc = setupIndexScan( pSQLTable)))
{
goto Exit;
}
}
else
{
if (RC_BAD( rc = setupTableScan( pSQLTable)))
{
goto Exit;
}
}
}
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 (!criteriaIsComplete())
{
rc = RC_SET( NE_SFLM_Q_INCOMPLETE_QUERY_EXPR);
goto Exit;
}
m_uiLanguage = m_pDb->getDefaultLanguage();
// An empty expression should scan the tables listed - using either
// the index that was specified for the table, or just scanning
// the rows of the table.
if (!m_pQuery)
{
rc = setupScans();
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->currVal.eValType == SQL_BOOL_VAL &&
m_pQuery->currVal.val.eBool == SQL_TRUE)
{
rc = setupScans();
goto Exit;
}
else
{
m_bEmpty = TRUE;
}
}
else if (m_pQuery->eNodeType == SQL_OPERATOR_NODE &&
isSQLArithOp( m_pQuery->nd.op.eOperator))
{
m_bEmpty = TRUE;
goto Exit;
}
// Flatten the AND and OR operators in the query tree. Strip out
// NOT operators, resolve constant arithmetic expressions, and
// weed out boolean constants.
if (RC_BAD( rc = reduceTree( TRUE)))
{
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);
}