git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@709 0109f412-320b-0410-ab79-c3e0c5ffbbe6
2948 lines
70 KiB
C++
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);
|
|
}
|
|
|