//------------------------------------------------------------------------- // Desc: Parse SQL // Tabs: 3 // // Copyright (c) 2006 Novell, Inc. All Rights Reserved. // // This program is free software; you can redistribute it and/or // modify it under the terms of version 2 of the GNU General Public // License as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, contact Novell, Inc. // // To contact Novell about this file by physical or electronic mail, // you may find current contact information at www.novell.com // // $Id$ //------------------------------------------------------------------------- #include "flaimsys.h" // Local function prototypes FSTATIC void sqlUnlinkFromParent( SQL_NODE * pSQLNode); FSTATIC void sqlLinkLastChild( SQL_NODE * pParent, SQL_NODE * pChild); FSTATIC FLMBOOL isTerminatingToken( const char * pszToken, const char ** ppszTerminatingTokens, const char ** ppszTerminator); static FLMUINT uiSQLOpPrecedenceTable[ SQL_NEG_OP - SQL_AND_OP + 1] = { 2, // SQL_AND_OP 1, // SQL_OR_OP 10, // SQL_NOT_OP 6, // SQL_EQ_OP 6, // SQL_NE_OP 6, // SQL_APPROX_EQ_OP 7, // SQL_LT_OP 7, // SQL_LE_OP 7, // SQL_GT_OP 7, // SQL_GE_OP 5, // SQL_BITAND_OP 3, // SQL_BITOR_OP 4, // SQL_BITXOR_OP 9, // SQL_MULT_OP 9, // SQL_DIV_OP 9, // SQL_MOD_OP 8, // SQL_PLUS_OP 8, // SQL_MINUS_OP 10 // SQL_NEG_OP }; FINLINE FLMUINT getSQLOpPrecedence( eSQLQueryOperators eOperator) { return( uiSQLOpPrecedenceTable [eOperator - SQL_AND_OP]); } //------------------------------------------------------------------------- // Desc: Constructor //------------------------------------------------------------------------- SQLQuery::SQLQuery() { m_uiLanguage = FLM_US_LANG; m_pool.poolInit( 1024); m_pFirstSubQuery = NULL; m_pLastSubQuery = NULL; m_pFirstSQLTable = NULL; m_pLastSQLTable = NULL; m_pFirstOrderBy = NULL; m_pLastOrderBy = NULL; m_bResolveNames = FALSE; m_bOptimized = FALSE; m_bEmpty = FALSE; m_pQuery = NULL; m_pDatabase = NULL; m_pDb = NULL; m_pNext = NULL; m_pPrev = NULL; } //------------------------------------------------------------------------- // Desc: freel all of the SQL_KEY structures associated with an SQL_INDEX. //------------------------------------------------------------------------- void freeIndexKeys( SQL_INDEX * pSQLIndex) { SQL_KEY * pSQLKey; for (pSQLKey = pSQLIndex->pFirstSQLKey; pSQLKey; pSQLKey = pSQLKey->pNext) { if (pSQLKey->pFSIndexCursor) { pSQLKey->pFSIndexCursor->Release(); pSQLKey->pFSIndexCursor = NULL; } } pSQLIndex->pFirstSQLKey = NULL; pSQLIndex->pLastSQLKey = NULL; } //------------------------------------------------------------------------- // Desc: Free all of the SQL_INDEX structures associated with an SQL_TABLE. //------------------------------------------------------------------------- void freeTableIndexes( SQL_TABLE * pSQLTable) { SQL_INDEX * pSQLIndex; for (pSQLIndex = pSQLTable->pFirstSQLIndex; pSQLIndex; pSQLIndex = pSQLIndex->pNext) { freeIndexKeys( pSQLIndex); } pSQLTable->pFirstSQLIndex = NULL; pSQLTable->pLastSQLIndex = NULL; } //------------------------------------------------------------------------- // Desc: Destructor //------------------------------------------------------------------------- SQLQuery::~SQLQuery() { SQL_TABLE * pSQLTable; // Free all of the table and index cursors. for (pSQLTable = m_pFirstSQLTable; pSQLTable; pSQLTable = pSQLTable->pNext) { if (pSQLTable->pFSTableCursor) { pSQLTable->pFSTableCursor->Release(); pSQLTable->pFSTableCursor = NULL; } freeTableIndexes( pSQLTable); } // Free all of the memory allocated from the memory pool. m_pool.poolFree(); // Unlink the query from the database it is associated with. if (m_pDatabase) { m_pDatabase->lockMutex(); // Unlink the query from the list off of the F_Database object. if (m_pPrev) { m_pPrev->m_pNext = m_pNext; } else { m_pDatabase->m_pFirstSQLQuery = m_pNext; } if (m_pNext) { m_pNext->m_pPrev = m_pPrev; } else { m_pDatabase->m_pLastSQLQuery = m_pPrev; } m_pDatabase->unlockMutex(); } } //------------------------------------------------------------------------- // Desc: Unlinks a node from its parent and siblings. This routine assumes // that the test has already been made that the node has a parent. //------------------------------------------------------------------------- FSTATIC void sqlUnlinkFromParent( SQL_NODE * pSQLNode) { flmAssert( pSQLNode->pParent); if (pSQLNode->pPrevSib) { pSQLNode->pPrevSib->pNextSib = pSQLNode->pNextSib; } else { pSQLNode->pParent->pFirstChild = pSQLNode->pNextSib; } if (pSQLNode->pNextSib) { pSQLNode->pNextSib->pPrevSib = pSQLNode->pPrevSib; } else { pSQLNode->pParent->pLastChild = pSQLNode->pPrevSib; } pSQLNode->pParent = NULL; pSQLNode->pPrevSib = NULL; pSQLNode->pNextSib = NULL; } //------------------------------------------------------------------------- // Desc: Links one SQL_NODE as the last child of another. Will unlink the // child node from any parent it may be linked to. //------------------------------------------------------------------------- FSTATIC void sqlLinkLastChild( SQL_NODE * pParent, SQL_NODE * pChild ) { // If necessary, unlink the child from parent and siblings if (pChild->pParent) { sqlUnlinkFromParent( pChild); } // Link child as the last child to parent pChild->pParent = pParent; pChild->pNextSib = NULL; if ((pChild->pPrevSib = pParent->pLastChild) != NULL) { pChild->pPrevSib->pNextSib = pChild; } else { pParent->pFirstChild = pChild; } pParent->pLastChild = pChild; } //------------------------------------------------------------------------- // Desc: Allocate a value node. //------------------------------------------------------------------------- RCODE SQLQuery::allocValueNode( FLMUINT uiValLen, eSQLValTypes eValType, SQL_NODE ** ppSQLNode ) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; if (!expectingOperand()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_VALUE); goto Exit; } if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_NODE), (void **)ppSQLNode))) { goto Exit; } pSQLNode = *ppSQLNode; pSQLNode->eNodeType = SQL_VALUE_NODE; pSQLNode->currVal.eValType = eValType; pSQLNode->currVal.uiFlags = SQL_VAL_IS_CONSTANT; // For string and binary data, allocate a buffer. if (uiValLen) { if (eValType == SQL_UTF8_VAL) { if (RC_BAD( rc = m_pool.poolAlloc( uiValLen, (void **)&pSQLNode->currVal.val.str.pszStr))) { goto Exit; } pSQLNode->currVal.val.str.uiByteLen = uiValLen; } else if (eValType == SQL_BINARY_VAL) { if (RC_BAD( rc = m_pool.poolAlloc( uiValLen, (void **)&pSQLNode->currVal.val.bin.pucValue))) { goto Exit; } pSQLNode->currVal.val.bin.uiByteLen = uiValLen; } } if (m_pQuery) { sqlLinkLastChild( m_pCurOperatorNode, pSQLNode); } else { m_pQuery = pSQLNode; } m_bExpectingOperator = TRUE; m_pLastNode = pSQLNode; Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add an operator to the query expression //------------------------------------------------------------------------- RCODE SQLQuery::addOperator( eSQLQueryOperators eOperator, FLMUINT uiCompareRules) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; SQL_NODE * pParentNode; if (eOperator == SQL_MINUS_OP && expectingOperand()) { eOperator = SQL_NEG_OP; } switch (eOperator) { case SQL_LPAREN_OP: // If the operator is a left paren, increment the nesting level if (expectingOperator()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_LPAREN); goto Exit; } m_uiNestLevel++; goto Exit; case SQL_RPAREN_OP: if (expectingOperand()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_RPAREN); goto Exit; } if (!m_uiNestLevel) { rc = RC_SET( NE_SFLM_Q_UNMATCHED_RPAREN); goto Exit; } m_uiNestLevel--; goto Exit; case SQL_NEG_OP: case SQL_NOT_OP: if (expectingOperator()) { rc = RC_SET( NE_SFLM_Q_EXPECTING_OPERATOR); goto Exit; } break; default: if (expectingOperand()) { rc = RC_SET( NE_SFLM_Q_EXPECTING_OPERAND); goto Exit; } if (!isLegalSQLOperator( eOperator)) { rc = RC_SET( NE_SFLM_Q_ILLEGAL_OPERATOR); goto Exit; } break; } // Cannot set both FLM_COMP_COMPRESS_WHITESPACE and FLM_COMP_NO_WHITESPACE // in comparison rules. Also, cannot set FLM_COMP_IGNORE_LEADING_SPACE or // FLM_COMP_IGNORE_TRAILING_SPACE with FLM_COMP_NO_WHITESPACE. if ((uiCompareRules & FLM_COMP_NO_WHITESPACE) && (uiCompareRules & (FLM_COMP_COMPRESS_WHITESPACE | FLM_COMP_IGNORE_LEADING_SPACE | FLM_COMP_IGNORE_TRAILING_SPACE))) { rc = RC_SET_AND_ASSERT( NE_SFLM_Q_ILLEGAL_COMPARE_RULES); goto Exit; } // Make a QNODE and find a place for it in the query tree if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_NODE), (void **)&pSQLNode))) { goto Exit; } pSQLNode->eNodeType = SQL_OPERATOR_NODE; pSQLNode->nd.op.eOperator = eOperator; pSQLNode->nd.op.uiCompareRules = uiCompareRules; pSQLNode->uiNestLevel = m_uiNestLevel; // Go up the stack until an operator whose nest level or precedence is < // this one's is encountered, then link this one in as the last child pParentNode = m_pCurOperatorNode; while (pParentNode && (pParentNode->uiNestLevel > pSQLNode->uiNestLevel || (pParentNode->uiNestLevel == pSQLNode->uiNestLevel && getSQLOpPrecedence( pParentNode->nd.op.eOperator) >= getSQLOpPrecedence( eOperator)))) { pParentNode = pParentNode->pParent; } if (!pParentNode) { if (m_pQuery) { sqlLinkLastChild( pSQLNode, m_pQuery); } m_pQuery = pSQLNode; } else if (eOperator == SQL_NOT_OP || eOperator == SQL_NEG_OP) { // Need to treat NOT and NEG as if they were operands. // Parent better be an operator. flmAssert( pParentNode->eNodeType == SQL_OPERATOR_NODE); #ifdef FLM_DEBUG if (pParentNode->nd.op.eOperator == SQL_NEG_OP || pParentNode->nd.op.eOperator == SQL_NOT_OP) { // Must have no children. flmAssert( pParentNode->pFirstChild == NULL); } else { // Must only have one or zero children. flmAssert( pParentNode->pFirstChild == pParentNode->pLastChild); } #endif sqlLinkLastChild( pParentNode, pSQLNode); flmAssert( !m_bExpectingOperator); } else { // Parent better be an operator. flmAssert( pParentNode->eNodeType == SQL_OPERATOR_NODE); // Unlink last child of parent node and replace with this // new node. The parent node better already have the correct // number of children, or we are not parsing correctly. flmAssert( pParentNode->pFirstChild); if (pParentNode->nd.op.eOperator == SQL_NEG_OP || pParentNode->nd.op.eOperator == SQL_NOT_OP) { // Better only be one child. flmAssert( !pParentNode->pFirstChild->pNextSib); sqlLinkLastChild( pSQLNode, pParentNode->pFirstChild); } else { // Better only be two child nodes flmAssert( pParentNode->pFirstChild->pNextSib == pParentNode->pLastChild); sqlLinkLastChild( pSQLNode, pParentNode->pLastChild); } sqlLinkLastChild( pParentNode, pSQLNode); } m_pCurOperatorNode = pSQLNode; m_bExpectingOperator = FALSE; m_pLastNode = pSQLNode; Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Allocate a node for an operand. //------------------------------------------------------------------------- RCODE SQLQuery::allocOperandNode( eSQLNodeTypes eNodeType, SQL_NODE ** ppSQLNode) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_NODE), (void **)&pSQLNode))) { goto Exit; } pSQLNode->eNodeType = eNodeType; if (m_pQuery) { sqlLinkLastChild( m_pCurOperatorNode, pSQLNode); } else { m_pQuery = pSQLNode; } m_bExpectingOperator = TRUE; m_pLastNode = *ppSQLNode = pSQLNode; Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add a table to the query. //------------------------------------------------------------------------- RCODE SQLQuery::addTable( FLMUINT uiTableNum, SQL_TABLE ** ppSQLTable) { RCODE rc = NE_SFLM_OK; SQL_TABLE * pSQLTable; // Add or find the table structure for the node. pSQLTable = m_pFirstSQLTable; while (pSQLTable && pSQLTable->uiTableNum != uiTableNum) { pSQLTable = pSQLTable->pNext; } if (!pSQLTable) { if (RC_BAD( rc = m_pool.poolCalloc( sizeof( SQL_TABLE), (void **)&pSQLTable))) { goto Exit; } pSQLTable->uiTableNum = uiTableNum; if ((pSQLTable->pPrev = m_pLastSQLTable) != NULL) { m_pLastSQLTable->pNext = pSQLTable; } else { m_pFirstSQLTable = pSQLTable; } m_pLastSQLTable = pSQLTable; } // These should have been set by the poolCalloc. // pSQLTable->bScan = FALSE; // pSQLTable->uiIndexNum = 0; if (ppSQLTable) { *ppSQLTable = pSQLTable; } Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add a column name. //------------------------------------------------------------------------- RCODE SQLQuery::addColumn( FLMUINT uiTableNum, FLMUINT uiColumnNum) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; SQL_TABLE * pSQLTable; // Must be expecting an operand if (!expectingOperand()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_COLUMN); goto Exit; } if (RC_BAD( rc = addTable( uiTableNum, &pSQLTable))) { goto Exit; } // Allocate a column node if (RC_BAD( rc = allocOperandNode( SQL_COLUMN_NODE, &pSQLNode))) { goto Exit; } pSQLNode->nd.column.pSQLTable = pSQLTable; pSQLNode->nd.column.uiColumnNum = uiColumnNum; Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Determine which table/column a table alias name and column name // refer to. //------------------------------------------------------------------------- RCODE resolveColumnName( F_Db * pDb, TABLE_ITEM * pTableList, const char * pszTableAlias, const char * pszColumnName, FLMUINT * puiTableNum, FLMUINT * puiColumnNum, SQLParseError * peParseError) { RCODE rc = NE_SFLM_OK; F_COLUMN * pColumn; F_TABLE * pTable; F_COLUMN * pFoundColumn; F_TABLE * pFoundTable; TABLE_ITEM * pTableItem; if (pszTableAlias) { for (pTableItem = pTableList; pTableItem->uiTableNum; pTableItem++) { if (f_stricmp( pszTableAlias, pTableItem->pszTableAlias) == 0) { pTable = pDb->getDict()->getTable( pTableItem->uiTableNum); if ((pColumn = pDb->getDict()->findColumn( pTable, pszColumnName)) == NULL) { if (peParseError) { *peParseError = SQL_ERR_INVALID_COLUMN_NAME; } rc = RC_SET( NE_SFLM_Q_INVALID_COLUMN_NAME); goto Exit; } *puiTableNum = pTable->uiTableNum; *puiColumnNum = pColumn->uiColumnNum; goto Exit; } } // If we get this far, we didn't find the table. if (peParseError) { *peParseError = SQL_ERR_UNDEFINED_TABLE_NAME; } rc = RC_SET( NE_SFLM_Q_UNDEFINED_TABLE_FOR_COLUMN); goto Exit; } else { pFoundColumn = NULL; pFoundTable = NULL; for (pTableItem = pTableList; pTableItem->uiTableNum; pTableItem++) { pTable = pDb->getDict()->getTable( pTableItem->uiTableNum); if ((pColumn = pDb->getDict()->findColumn( pTable, pszColumnName)) != NULL) { // Column name is ambiguous - belongs to more than one of // the tables specified. if (pFoundColumn) { if (peParseError) { *peParseError = SQL_ERR_AMBIGUOUS_COLUMN_NAME; } rc = RC_SET( NE_SFLM_Q_AMBIGUOUS_COLUMN_NAME); goto Exit; } pFoundColumn = pColumn; pFoundTable = pTable; } } if (!pFoundColumn) { if (peParseError) { *peParseError = SQL_ERR_INVALID_COLUMN_NAME; } rc = RC_SET( NE_SFLM_Q_INVALID_COLUMN_NAME); goto Exit; } *puiTableNum = pFoundTable->uiTableNum; *puiColumnNum = pFoundColumn->uiColumnNum; } if (peParseError) { *peParseError = SQL_NO_ERROR; } Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Resolve all of the column names that have been specified to one of // the tables specified in the table list. Each column must belong // to one of the tables in the list. //------------------------------------------------------------------------- RCODE SQLQuery::resolveColumnNames( TABLE_ITEM * pTableList) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; SQL_TABLE * pSQLTable; FLMUINT uiTableNum; FLMUINT uiColumnNum; // May not be any names to resolve if the select statement specifed "*" // for the list of columns. if (!m_bResolveNames) { goto Exit; } pSQLNode = m_pQuery; for (;;) { if (pSQLNode->pFirstChild) { pSQLNode = pSQLNode->pFirstChild; continue; } if (pSQLNode->eNodeType == SQL_COLUMN_NODE) { if (RC_BAD( rc = resolveColumnName( m_pDb, pTableList, pSQLNode->nd.column.pszTableAlias, pSQLNode->nd.column.pszColumnName, &uiTableNum, &uiColumnNum, NULL))) { goto Exit; } if (RC_BAD( rc = addTable( uiTableNum, &pSQLTable))) { goto Exit; } pSQLNode->nd.column.pSQLTable = pSQLTable; pSQLNode->nd.column.uiColumnNum = uiColumnNum; } // Continue to the next sibling - or the next sibling of the first // node in parent chain that has a sibling. for (;;) { if (pSQLNode->pNextSib) { pSQLNode = pSQLNode->pNextSib; break; } if ((pSQLNode = pSQLNode->pParent) == NULL) { break; } } if (!pSQLNode) { break; } } Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add a column name. //------------------------------------------------------------------------- RCODE SQLQuery::addColumn( const char * pszTableAlias, FLMUINT uiTableAliasLen, const char * pszColumnName, FLMUINT uiColumnNameLen) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; char * pszTmp; // Must be expecting an operand if (!expectingOperand()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_COLUMN); goto Exit; } // Allocate a column node if (RC_BAD( rc = allocOperandNode( SQL_COLUMN_NODE, &pSQLNode))) { goto Exit; } if (RC_BAD( rc = m_pool.poolAlloc( uiTableAliasLen + uiColumnNameLen + 2, (void **)&pszTmp))) { goto Exit; } // Save the names - will resolve to numbers later. if (uiTableAliasLen) { pSQLNode->nd.column.pszTableAlias = pszTmp; f_memcpy( pszTmp, pszTableAlias, uiTableAliasLen + 1); pszTmp += (uiTableAliasLen + 1); } else { pSQLNode->nd.column.pszTableAlias = NULL; } pSQLNode->nd.column.pszColumnName = pszTmp; f_memcpy( pszTmp, pszColumnName, uiColumnNameLen + 1); pSQLNode->nd.column.pSQLTable = NULL; pSQLNode->nd.column.uiColumnNum = 0; m_bResolveNames = TRUE; Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add a FLMUINT64 number constant. //------------------------------------------------------------------------- RCODE SQLQuery::addUINT64( FLMUINT64 ui64Num) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; // Must be expecting an operand if (!expectingOperand()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_CONSTANT); goto Exit; } // Allocate a value node if (RC_BAD( rc = allocOperandNode( SQL_VALUE_NODE, &pSQLNode))) { goto Exit; } pSQLNode->currVal.eValType = SQL_UINT64_VAL; pSQLNode->currVal.uiFlags |= SQL_VAL_IS_CONSTANT; pSQLNode->currVal.val.ui64Val = ui64Num; Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add a FLMUINT number constant. //------------------------------------------------------------------------- RCODE SQLQuery::addUINT( FLMUINT uiNum) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; // Must be expecting an operand if (!expectingOperand()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_CONSTANT); goto Exit; } // Allocate a value node if (RC_BAD( rc = allocOperandNode( SQL_VALUE_NODE, &pSQLNode))) { goto Exit; } pSQLNode->currVal.eValType = SQL_UINT_VAL; pSQLNode->currVal.uiFlags |= SQL_VAL_IS_CONSTANT; pSQLNode->currVal.val.uiVal = uiNum; Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add a FLMINT64 number constant. //------------------------------------------------------------------------- RCODE SQLQuery::addINT64( FLMINT64 i64Num) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; // Must be expecting an operand if (!expectingOperand()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_CONSTANT); goto Exit; } // Allocate a value node if (RC_BAD( rc = allocOperandNode( SQL_VALUE_NODE, &pSQLNode))) { goto Exit; } pSQLNode->currVal.eValType = SQL_INT64_VAL; pSQLNode->currVal.uiFlags |= SQL_VAL_IS_CONSTANT; pSQLNode->currVal.val.i64Val = i64Num; Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add a FLMINT number constant. //------------------------------------------------------------------------- RCODE SQLQuery::addINT( FLMINT iNum) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; // Must be expecting an operand if (!expectingOperand()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_CONSTANT); goto Exit; } // Allocate a value node if (RC_BAD( rc = allocOperandNode( SQL_VALUE_NODE, &pSQLNode))) { goto Exit; } pSQLNode->currVal.eValType = SQL_INT_VAL; pSQLNode->currVal.uiFlags |= SQL_VAL_IS_CONSTANT; pSQLNode->currVal.val.iVal = iNum; Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add a boolean constant. //------------------------------------------------------------------------- RCODE SQLQuery::addBoolean( FLMBOOL bValue, FLMBOOL bUnknown) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; // Must be expecting an operand if (!expectingOperand()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_BOOLEAN); goto Exit; } // Allocate a value node if (RC_BAD( rc = allocOperandNode( SQL_VALUE_NODE, &pSQLNode))) { goto Exit; } pSQLNode->currVal.eValType = SQL_BOOL_VAL; pSQLNode->currVal.uiFlags |= SQL_VAL_IS_CONSTANT; if (bUnknown) { pSQLNode->currVal.val.eBool = SQL_UNKNOWN; } else if (bValue) { pSQLNode->currVal.val.eBool = SQL_TRUE; } else { pSQLNode->currVal.val.eBool = SQL_FALSE; } Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add a UTF8 string constant. //------------------------------------------------------------------------- RCODE SQLQuery::addUTF8String( const FLMBYTE * pszUTF8Str, FLMUINT uiStrLen, FLMUINT uiNumChars) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; FLMBYTE * pszTmp; // Must be expecting an operand if (!expectingOperand()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_CONSTANT); goto Exit; } if (!uiStrLen) { uiStrLen = f_strlen( (const char *)pszUTF8Str); } // Allocate a value node if (RC_BAD( rc = m_pool.poolCalloc( uiStrLen + 1, (void **)&pszTmp))) { goto Exit; } if (RC_BAD( rc = allocOperandNode( SQL_VALUE_NODE, &pSQLNode))) { goto Exit; } pSQLNode->currVal.eValType = SQL_UTF8_VAL; pSQLNode->currVal.uiFlags |= SQL_VAL_IS_CONSTANT; pSQLNode->currVal.val.str.pszStr = pszTmp; pSQLNode->currVal.val.str.uiByteLen = uiStrLen + 1; pSQLNode->currVal.val.str.uiNumChars = uiNumChars; f_memcpy( pszTmp, pszUTF8Str, uiStrLen); pszTmp [uiStrLen] = 0; // See if there are any wildcards in the string. while (*pszTmp) { if (*pszTmp == '*') { pSQLNode->currVal.uiFlags |= SQL_VAL_HAS_WILDCARDS; break; } else if (*pszTmp == '\\') { // Skip over whatever comes after a backslash - it is an // escaped character, so it won't count as a wildcard. pszTmp++; if (!(*pszTmp)) { break; } } pszTmp++; } Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add a binary constant. //------------------------------------------------------------------------- RCODE SQLQuery::addBinary( const FLMBYTE * pucValue, FLMUINT uiValueLen) { RCODE rc = NE_SFLM_OK; SQL_NODE * pSQLNode; FLMBYTE * pucTmp; // Must be expecting an operand if (!expectingOperand()) { rc = RC_SET( NE_SFLM_Q_UNEXPECTED_CONSTANT); goto Exit; } // Allocate a value node if (RC_BAD( rc = m_pool.poolCalloc( uiValueLen, (void **)&pucTmp))) { goto Exit; } if (RC_BAD( rc = allocOperandNode( SQL_VALUE_NODE, &pSQLNode))) { goto Exit; } pSQLNode->currVal.eValType = SQL_BINARY_VAL; pSQLNode->currVal.uiFlags |= SQL_VAL_IS_CONSTANT; pSQLNode->currVal.val.bin.uiByteLen = uiValueLen; pSQLNode->currVal.val.bin.pucValue = pucTmp; f_memcpy( pucTmp, pucValue, uiValueLen); Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Add an ORDER BY component //------------------------------------------------------------------------- RCODE SQLQuery::orderBy( FLMUINT uiTableNum, FLMUINT uiColumnNum, FLMBOOL bDescending) { RCODE rc = NE_SFLM_OK; SQL_TABLE * pSQLTable; SQL_ORDER_BY * pOrderBy; // Find the table structure for the table - should already exist. pSQLTable = m_pFirstSQLTable; while (pSQLTable && pSQLTable->uiTableNum != uiTableNum) { pSQLTable = pSQLTable->pNext; } if (!pSQLTable) { rc = RC_BAD( NE_SFLM_Q_BAD_ORDER_BY_TABLE); goto Exit; } // Make sure that the order by component has not already been // specified. pOrderBy = m_pFirstOrderBy; while (pOrderBy) { if (pOrderBy->pSQLTable == pSQLTable && pOrderBy->uiColumnNum == uiColumnNum) { rc = RC_SET( NE_SFLM_Q_DUP_COLUMN_IN_ORDER_BY); goto Exit; } pOrderBy = pOrderBy->pNext; } // Allocate an SQL_ORDER_BY structure and link it into the list of // SQL_ORDER_BY structures. It is assumed that the order in which this // method is called specifies 1st, 2nd, 3rd, etc. order of the sort // components. if (RC_BAD( rc = m_pool.poolAlloc( sizeof( SQL_ORDER_BY), (void **)&pOrderBy))) { goto Exit; } pOrderBy->pSQLTable = pSQLTable; pOrderBy->uiColumnNum = uiColumnNum; pOrderBy->bDescending = bDescending; pOrderBy->pNext = NULL; if (m_pLastOrderBy) { m_pLastOrderBy->pNext = pOrderBy; } else { m_pFirstOrderBy = pOrderBy; } m_pLastOrderBy = pOrderBy; Exit: return( rc); } //------------------------------------------------------------------------- // Desc: Set an index on a table. //------------------------------------------------------------------------- RCODE SQLQuery::setIndex( FLMUINT uiTableNum, FLMUINT uiIndexNum) { RCODE rc = NE_SFLM_OK; SQL_TABLE * pSQLTable; flmAssert( !m_bOptimized); // Find the table structure for the table - should already exist. pSQLTable = m_pFirstSQLTable; while (pSQLTable && pSQLTable->uiTableNum != uiTableNum) { pSQLTable = pSQLTable->pNext; } if (!pSQLTable) { rc = RC_BAD( NE_SFLM_Q_INVALID_TABLE_FOR_INDEX); goto Exit; } if ((pSQLTable->uiIndexNum = uiIndexNum) == 0) { pSQLTable->bScan = TRUE; } else { pSQLTable->bScan = FALSE; } Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Determine if the current token is one that would terminate the // expression. //------------------------------------------------------------------------------ FSTATIC FLMBOOL isTerminatingToken( const char * pszToken, const char ** ppszTerminatingTokens, const char ** ppszTerminator) { FLMUINT uiLoop; if (ppszTerminatingTokens) { for (uiLoop = 0; ppszTerminatingTokens [uiLoop]; uiLoop++) { if (f_stricmp( ppszTerminatingTokens [uiLoop], pszToken) == 0) { *ppszTerminator = ppszTerminatingTokens [uiLoop]; return( TRUE); } } } return( FALSE); } //------------------------------------------------------------------------------ // Desc: Process a token in the expression that begins with an alphabetic // character. //------------------------------------------------------------------------------ RCODE SQLStatement::processAlphaToken( TABLE_ITEM * pTableList, const char ** ppszTerminatingTokens, const char ** ppszTerminator, SQLQuery * pSqlQuery, FLMBOOL * pbDone) { RCODE rc = NE_SFLM_OK; FLMUINT uiSaveLineNum; FLMUINT uiSaveLineOffset; FLMUINT uiSaveLineFilePos; FLMUINT uiSaveLineBytes; char szToken [MAX_SQL_NAME_LEN + 1]; FLMUINT uiTokenLen; char szColumnName [MAX_SQL_NAME_LEN + 1]; FLMUINT uiColumnNameLen; FLMUINT uiTokenLineOffset; FLMUINT uiTableNum; FLMUINT uiColumnNum; SQLParseError eParseError; FLMBYTE ucBuffer [200]; F_DynaBuf dynaBuf( ucBuffer, sizeof( ucBuffer)); *pbDone = FALSE; if (RC_BAD( rc = getName( szToken, sizeof( szToken), &uiTokenLen, &uiTokenLineOffset))) { goto Exit; } uiSaveLineNum = m_uiCurrLineNum; uiSaveLineOffset = uiTokenLineOffset; uiSaveLineFilePos = m_uiCurrLineFilePos; uiSaveLineBytes = m_uiCurrLineBytes; if (isTerminatingToken( szToken, ppszTerminatingTokens, ppszTerminator)) { if (pSqlQuery->criteriaIsComplete()) { *pbDone = TRUE; goto Exit; } } else if (f_stricmp( szToken, "and") == 0 || f_stricmp( szToken, "or") == 0) { if (pSqlQuery->expectingOperator()) { // Treat as AND operator rc = pSqlQuery->addOperator( (eSQLQueryOperators)(f_toupper( szToken[0]) == 'A' ? SQL_AND_OP : SQL_OR_OP), 0); goto Exit; } } else if (f_stricmp( szToken, "not") == 0) { // Interestingly, NOT operators should only appear when // we are expecting an operand. if (pSqlQuery->expectingOperator()) { setErrInfo( uiSaveLineNum, uiSaveLineOffset, SQL_ERR_UNEXPECTED_NOT_OPERATOR, uiSaveLineFilePos, uiSaveLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } else { rc = pSqlQuery->addOperator( SQL_NOT_OP, 0); goto Exit; } } else if (f_stricmp( szToken, "true") == 0 || f_stricmp( szToken, "false") == 0 || f_stricmp( szToken, "unknown") == 0) { if (pSqlQuery->expectingOperand()) { // Treat as AND operator rc = pSqlQuery->addBoolean( (FLMBOOL)(f_toupper( szToken[0]) == 'T' ? TRUE : FALSE), (FLMBOOL)(f_toupper( szToken[0]) == 'U' ? TRUE : FALSE)); goto Exit; } } else if (f_stricmp( szToken, "binary") == 0) { if (pSqlQuery->expectingOperand()) { if (RC_BAD( rc = haveToken( "(", TRUE))) { if (rc != NE_SFLM_NOT_FOUND && rc != NE_SFLM_EOF_HIT) { goto Exit; } } else { dynaBuf.truncateData( 0); if (RC_BAD( rc = getBinaryValue( &dynaBuf))) { goto Exit; } rc = pSqlQuery->addBinary( dynaBuf.getBufferPtr(), dynaBuf.getDataLength()); goto Exit; } } } // At this point, the only thing left is for it to be a column name // or a tablename.columnname. // If we are expecting an operator, it is an error for // a column name to be specified. if (pSqlQuery->expectingOperator()) { setErrInfo( uiSaveLineNum, uiSaveLineOffset, SQL_ERR_EXPECTING_OPERATOR, uiSaveLineFilePos, uiSaveLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } // If we fall through to here, treat the token as a column // name or a table alias. See if there is a period for the // next token. if (RC_BAD( rc = haveToken( ".", TRUE))) { if (rc != NE_SFLM_NOT_FOUND && rc != NE_SFLM_EOF_HIT) { goto Exit; } rc = NE_SFLM_OK; if (!pTableList) { // If there is no table list, the list of tables has not yet been // specified, so we will have to save this column in a slightly // different way. if (RC_BAD( rc = pSqlQuery->addColumn( NULL, 0, szToken, uiTokenLen))) { goto Exit; } } else { if (RC_BAD( rc = resolveColumnName( m_pDb, pTableList, NULL, szToken, &uiTableNum, &uiColumnNum, &eParseError))) { if (eParseError != SQL_NO_ERROR) { setErrInfo( uiSaveLineNum, uiSaveLineOffset, eParseError, uiSaveLineFilePos, uiSaveLineBytes); } goto Exit; } if (RC_BAD( rc = pSqlQuery->addColumn( uiTableNum, uiColumnNum))) { goto Exit; } } } else { if (RC_BAD( rc = getName( szColumnName, sizeof( szColumnName), &uiColumnNameLen, &uiTokenLineOffset))) { goto Exit; } if (!pTableList) { // No table list yet - save using the table alias and column name. if (RC_BAD( rc = pSqlQuery->addColumn( szToken, uiTokenLen, szColumnName, uiColumnNameLen))) { goto Exit; } } else { if (RC_BAD( rc = resolveColumnName( m_pDb, pTableList, szToken, szColumnName, &uiTableNum, &uiColumnNum, &eParseError))) { if (eParseError != SQL_NO_ERROR) { setErrInfo( uiSaveLineNum, uiSaveLineOffset, eParseError, uiSaveLineFilePos, uiSaveLineBytes); } goto Exit; } } if (RC_BAD( rc = pSqlQuery->addColumn( uiTableNum, uiColumnNum))) { goto Exit; } } Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Process criteria. //------------------------------------------------------------------------------ RCODE SQLStatement::parseCriteria( TABLE_ITEM * pTableList, const char ** ppszTerminatingTokens, FLMBOOL bEofOK, const char ** ppszTerminator, SQLQuery * pSqlQuery) { RCODE rc = NE_SFLM_OK; char cChar; FLMUINT64 ui64Num; FLMBOOL bNeg; FLMBYTE ucBuffer [200]; F_DynaBuf dynaBuf( ucBuffer, sizeof( ucBuffer)); FLMUINT uiNumChars; eSQLQueryOperators eOperator = SQL_UNKNOWN_OP; FLMUINT uiNestedParens = 0; FLMUINT uiTokenLineOffset; // Process tokens for (;;) { if (RC_BAD( rc = skipWhitespace( FALSE))) { if (rc == NE_SFLM_EOF_HIT) { *ppszTerminator = NULL; if (bEofOK) { if (!pSqlQuery->criteriaIsComplete()) { goto Incomplete_Query; } else { rc = NE_SFLM_OK; } } else { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_UNEXPECTED_EOF, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); } } goto Exit; } // See if we can figure out what kind of token it is. cChar = (char)m_pucCurrLineBuf [m_uiCurrLineOffset]; uiTokenLineOffset = m_uiCurrLineOffset; switch (cChar) { case '"': case '\'': // If we are expecting an operator, it is an error for // a string constant to be specified. if (pSqlQuery->expectingOperator()) { setErrInfo( m_uiCurrLineNum, uiTokenLineOffset, SQL_ERR_EXPECTING_OPERATOR, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } // Better be a quoted string. dynaBuf.truncateData( 0); if (RC_BAD( rc = getUTF8String( FALSE, FALSE, NULL, 0, NULL, &uiNumChars, &dynaBuf))) { goto Exit; } if (RC_BAD( rc = pSqlQuery->addUTF8String( dynaBuf.getBufferPtr(), dynaBuf.getDataLength() - 1, uiNumChars))) { goto Exit; } break; case '+': // A plus in front of an operand may be ignored. m_uiCurrLineOffset++; if (pSqlQuery->expectingOperand()) { continue; } eOperator = SQL_PLUS_OP; goto Add_Operator; case '-': eOperator = (pSqlQuery->expectingOperand()) ? SQL_NEG_OP : SQL_MINUS_OP; m_uiCurrLineOffset++; goto Add_Operator; case '*': eOperator = SQL_MULT_OP; m_uiCurrLineOffset++; goto Test_If_Expecting_Operand; case '/': eOperator = SQL_DIV_OP; m_uiCurrLineOffset++; goto Test_If_Expecting_Operand; case '%': eOperator = SQL_MOD_OP; m_uiCurrLineOffset++; goto Test_If_Expecting_Operand; case '(': if (pSqlQuery->expectingOperator()) { setErrInfo( m_uiCurrLineNum, uiTokenLineOffset, SQL_ERR_UNEXPECTED_LPAREN, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } eOperator = SQL_LPAREN_OP; m_uiCurrLineOffset++; uiNestedParens++; goto Add_Operator; case ')': if (pSqlQuery->expectingOperand()) { setErrInfo( m_uiCurrLineNum, uiTokenLineOffset, SQL_ERR_UNEXPECTED_RPAREN, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } else if (!uiNestedParens) { setErrInfo( m_uiCurrLineNum, uiTokenLineOffset, SQL_ERR_UNMATCHED_RPAREN, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } eOperator = SQL_RPAREN_OP; m_uiCurrLineOffset++; uiNestedParens--; break; case '!': if (pSqlQuery->expectingOperator()) { setErrInfo( m_uiCurrLineNum, uiTokenLineOffset, SQL_ERR_UNEXPECTED_NOT_OPERATOR, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } m_uiCurrLineOffset++; goto Add_Operator; case '=': eOperator = SQL_EQ_OP; if (m_uiCurrLineOffset + 1 < m_uiCurrLineBytes && m_pucCurrLineBuf [m_uiCurrLineOffset + 1] == '&') { m_uiCurrLineOffset += 2; } else { m_uiCurrLineOffset++; } goto Test_If_Expecting_Operand; case '&': if (m_uiCurrLineOffset + 1 < m_uiCurrLineBytes && m_pucCurrLineBuf [m_uiCurrLineOffset + 1] == '&') { eOperator = SQL_AND_OP; m_uiCurrLineOffset += 2; } else { eOperator = SQL_BITAND_OP; m_uiCurrLineOffset++; } goto Test_If_Expecting_Operand; case '|': if (m_uiCurrLineOffset + 1 < m_uiCurrLineBytes && m_pucCurrLineBuf [m_uiCurrLineOffset + 1] == '|') { eOperator = SQL_OR_OP; m_uiCurrLineOffset += 2; } else { eOperator = SQL_BITOR_OP; m_uiCurrLineOffset++; } goto Test_If_Expecting_Operand; case '^': eOperator = SQL_BITXOR_OP; m_uiCurrLineOffset++; goto Test_If_Expecting_Operand; Test_If_Expecting_Operand: if (pSqlQuery->expectingOperand()) { setErrInfo( m_uiCurrLineNum, uiTokenLineOffset, SQL_ERR_INVALID_OPERAND, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } Add_Operator: if (RC_BAD( rc = pSqlQuery->addOperator( eOperator, 0))) { goto Exit; } break; default: if ((cChar >= 'a' && cChar <= 'z') || (cChar >= 'A' && cChar <= 'Z')) { FLMBOOL bDone; if (RC_BAD( rc = processAlphaToken( pTableList, ppszTerminatingTokens, ppszTerminator, pSqlQuery, &bDone))) { goto Exit; } if (bDone) { goto Exit; } } else if (cChar >= '0' && cChar <= '9') { // If we are expecting an operator, it is an error for // a number to be specified. if (pSqlQuery->expectingOperator()) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_EXPECTING_OPERATOR, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } // Better be a number. if (RC_BAD( rc = getNumber( FALSE, &ui64Num, &bNeg, FALSE))) { goto Exit; } if (RC_BAD( rc = pSqlQuery->addNumber( ui64Num, bNeg))) { goto Exit; } } // At this point, it has to be an invalid token for selection // criteria. else { Incomplete_Query: if (pSqlQuery->expectingOperator()) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_EXPECTING_OPERATOR, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } else { flmAssert( pSqlQuery->expectingOperand()); setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_INVALID_OPERAND, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } } break; } } Exit: return( rc); }