//------------------------------------------------------------------------- // Desc: Various cursor/query functions // Tabs: 3 // // Copyright (c) 1994-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: fqcur.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $ //------------------------------------------------------------------------- #include "flaimsys.h" #define FLM_WILD_MASK 0x03 #define FLM_CASE_MASK 0x0C #define FLM_GRAN_MASK 0xF00 POOL_STATS g_SQPoolStats = {0,0}; POOL_STATS g_QueryPoolStats = {0,0}; // Local Function Prototypes FSTATIC RCODE flmCurCopyQTInfo( QTINFO * pSrc, QTINFO * pDest, F_Pool * pPool); FSTATIC void flmCurClearSelect( CURSOR * pCursor); FSTATIC RCODE flmCurPosToEOF( CURSOR * pCursor); FSTATIC RCODE flmCurPosToBOF( CURSOR * pCursor); FSTATIC RCODE flmCurSetPos( CURSOR * pDestCursor, CURSOR * pSrcCursor ); FSTATIC RCODE flmCurSetAbsolutePos( CURSOR * pCursor, FLMUINT uiPosition, FLMBOOL bFallForward, FLMUINT * puiPosition); FSTATIC RCODE flmCurPositionable( CURSOR * pCursor, FLMBOOL * pbPositionable); FSTATIC RCODE flmCurAbsPositionable( CURSOR * pCursor, FLMBOOL * pbAbsPositionable); FSTATIC RCODE flmCurGetAbsolutePos( CURSOR * pCursor, FLMUINT * puiPosition); FSTATIC RCODE flmCurGetAbsoluteCount( CURSOR * pCursor, FLMUINT * puiCount); FSTATIC FLMBOOL flmCurMatchIndexPath( IFD * pIfd, FLMUINT * puiField, FLMUINT uiPos); /**************************************************************************** Desc: Finishes a source's invisible transaction, if any. ****************************************************************************/ void flmCurFinishTrans( CURSOR * pCursor) { FLMBOOL bIgnore; if (pCursor->bInvTrans && pCursor->pDb) { if (RC_OK( fdbInit( pCursor->pDb, FLM_NO_TRANS, 0, 0, &bIgnore))) { if (pCursor->pDb->uiTransType != FLM_NO_TRANS && pCursor->pDb->uiTransCount == pCursor->uiTransSeq) { // If the commit fails, then do the abort. if (RC_BAD( flmCommitDbTrans( pCursor->pDb, 0, FALSE))) { (void)flmAbortDbTrans( pCursor->pDb); } } } fdbExit( pCursor->pDb); pCursor->bInvTrans = FALSE; } } /**************************************************************************** Desc: Initializes an FDB for a source. ****************************************************************************/ RCODE flmCurDbInit( CURSOR * pCursor) { RCODE rc; FLMBOOL bStartedTrans; if (RC_OK( rc = fdbInit( pCursor->pDb, FLM_READ_TRANS, FDB_TRANS_GOING_OK | FDB_INVISIBLE_TRANS_OK, 0, &bStartedTrans))) { if (bStartedTrans) { pCursor->pDb->uiFlags |= FDB_INVISIBLE_TRANS; pCursor->uiTransSeq = pCursor->pDb->uiTransCount; pCursor->bInvTrans = TRUE; } } return( rc); } /**************************************************************************** Desc: Initializes a cursor for subsequent definition and navigation of a record set. A cursor must be initialized before it can be used. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmCursorInit( HFDB hDb, FLMUINT uiContainer, HFCURSOR * phCursor) { RCODE rc = FERR_OK; CURSOR * pCursor = NULL; FDB * pDb = (FDB *)hDb; flmAssert( hDb != HFDB_NULL); flmAssert( uiContainer != 0); // See if the database is being forced to close if( RC_BAD( rc = flmCheckDatabaseState( pDb))) { goto Exit; } if (RC_BAD( rc = f_calloc( (FLMUINT)sizeof( CURSOR), &pCursor))) { goto Exit; } pCursor->QTInfo.uiMaxPredicates = MAX_USER_PREDICATES; pCursor->QTInfo.ppPredicates = &pCursor->QTInfo.Predicates [0]; // Initialize cursor members pCursor->QueryPool.smartPoolInit( &g_QueryPoolStats); pCursor->SQPool.smartPoolInit( &g_SQPoolStats); pCursor->pDb = pDb; pCursor->uiContainer = uiContainer; // Default is to have FLAIM select an index. pCursor->uiIndexNum = FLM_SELECT_INDEX; pCursor->pCSContext = pCursor->pDb->pCSContext; pCursor->uiCursorId = FCS_INVALID_ID; pCursor->QTInfo.uiExpecting = FLM_Q_OPERAND; pCursor->QTInfo.uiFlags = FLM_COMP_CASE_INSENSITIVE | FLM_COMP_WILD; Exit: if (RC_BAD( rc)) { if (pCursor) { pCursor->QueryPool.poolFree(); pCursor->SQPool.poolFree(); f_free( &pCursor); } } *phCursor = (HFCURSOR)pCursor; return( rc); } /**************************************************************************** Desc: Copies a passed-in query tree into a new tree, using the passed-in memory pool. ****************************************************************************/ FSTATIC RCODE flmCurCopyQTInfo( QTINFO * pSrc, QTINFO * pDest, F_Pool * pPool) { RCODE rc = FERR_OK; FQNODE * pDestParentNode; FQNODE * pSrcCurrNode; FQNODE * pDestCurrNode; FLMBOOL bGoingUp = FALSE; FLMBOOL bTreeComplete; // If the source query has been optimized, the query is found // in pSrc->pSaveQuery. Otherwise, it is found in // pSrc->pTopNode. if (pSrc->pSaveQuery) { pSrcCurrNode = pSrc->pSaveQuery; bTreeComplete = TRUE; } else { pSrcCurrNode = pSrc->pTopNode; bTreeComplete = FALSE; pDest->pCurOpNode = NULL; pDest->pCurAtomNode = NULL; } // If there is no query tree, don't need to copy. if (pSrcCurrNode) { // Must not do a recursive copy, because tree may not // have been flattened, and the recursion might go // too deep. if (RC_BAD( rc = flmCurCopyQNode( pSrcCurrNode, pDest, &pDest->pTopNode, pPool))) { goto Exit; } pDestParentNode = NULL; pDestCurrNode = pDest->pTopNode; if (!bTreeComplete) { if (pSrcCurrNode == pSrc->pCurOpNode) { pDest->pCurOpNode = pDestCurrNode; } else if (pSrcCurrNode == pSrc->pCurAtomNode) { pDest->pCurAtomNode = pDestCurrNode; } } for (;;) { if (bGoingUp) { if (pSrcCurrNode->pNextSib) { pSrcCurrNode = pSrcCurrNode->pNextSib; bGoingUp = FALSE; } else if ((pSrcCurrNode = pSrcCurrNode->pParent) == NULL) { break; } else { pDestCurrNode = pDestCurrNode->pParent; pDestParentNode = pDestCurrNode->pParent; continue; } } else { if (pSrcCurrNode->pChild) { pSrcCurrNode = pSrcCurrNode->pChild; pDestParentNode = pDestCurrNode; } else { bGoingUp = TRUE; continue; } } if (RC_BAD( rc = flmCurCopyQNode( pSrcCurrNode, pDest, &pDestCurrNode, pPool))) { goto Exit; } flmCurLinkLastChild( pDestParentNode, pDestCurrNode); if (!bTreeComplete) { if (pSrcCurrNode == pSrc->pCurOpNode) { pDest->pCurOpNode = pDestCurrNode; } else if( pSrcCurrNode == pSrc->pCurAtomNode) { pDest->pCurAtomNode = pDestCurrNode; } } } } if (bTreeComplete) { pDest->pCurOpNode = pDest->pTopNode; pDest->uiNestLvl = 0; pDest->pCurAtomNode = NULL; if (pDest->pTopNode) { pDest->uiExpecting = FLM_Q_OPERATOR; } else { pDest->uiExpecting = FLM_Q_OPERAND; } } else { pDest->uiNestLvl = pSrc->uiNestLvl; pDest->uiExpecting = pSrc->uiExpecting; // Defect #84610 -- Need to force pCurOpNode and pCurAtomNode into pDest // in cases where the query has no operator (i.e., existence query). if (!pDest->pTopNode) { if (pSrc->pCurOpNode) { if (RC_BAD( rc = flmCurCopyQNode( pSrc->pCurOpNode, pDest, &pDest->pCurOpNode, pPool))) { goto Exit; } } if (pSrc->pCurAtomNode) { if (RC_BAD( rc = flmCurCopyQNode( pSrc->pCurAtomNode, pDest, &pDest->pCurAtomNode, pPool))) { goto Exit; } } } } pDest->uiFlags = pSrc->uiFlags; Exit: return( rc); } /**************************************************************************** Desc: Initializes a new cursor and sets its selection criteria and record sources to be the same as those of the passed-in cursor. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmCursorClone( HFCURSOR hSource, HFCURSOR * phCursor) { RCODE rc = FERR_OK; CURSOR * pSrcCursor; CURSOR * pDestCursor = NULL; if ((pSrcCursor = (CURSOR *)hSource) == NULL) { rc = RC_SET( FERR_MEM); goto Exit; } if (pSrcCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } // See if the database is being forced to close if( RC_BAD( rc = flmCheckDatabaseState( pSrcCursor->pDb))) { goto Exit; } if (RC_BAD( rc = f_calloc( (FLMUINT)sizeof( CURSOR), &pDestCursor))) { goto Exit; } pDestCursor->QTInfo.uiMaxPredicates = MAX_USER_PREDICATES; pDestCursor->QTInfo.ppPredicates = &pDestCursor->QTInfo.Predicates [0]; // Initialize cursor members pDestCursor->QueryPool.smartPoolInit( &g_QueryPoolStats); pDestCursor->SQPool.smartPoolInit( &g_SQPoolStats); // Set up a tree info structure for query declaration. if (RC_BAD( rc = flmCurCopyQTInfo( &pSrcCursor->QTInfo, &pDestCursor->QTInfo, &pDestCursor->QueryPool))) { goto Exit; } // Copy source information. pDestCursor->pDb = pSrcCursor->pDb; pDestCursor->uiContainer = pSrcCursor->uiContainer; pDestCursor->pCSContext = pSrcCursor->pDb->pCSContext; pDestCursor->uiCursorId = FCS_INVALID_ID; // Copy index information. pDestCursor->uiIndexNum = pSrcCursor->uiIndexNum; pDestCursor->uiRecType = pSrcCursor->uiRecType; // Initialize various structure elements pDestCursor->bOkToReturnKeys = pSrcCursor->bOkToReturnKeys; pDestCursor->bOptimized = FALSE; Exit: if (RC_BAD( rc)) { if (pDestCursor) { (void)flmCurFree( pDestCursor, TRUE); pDestCursor = NULL; } } *phCursor = (HFCURSOR)pDestCursor; return( rc); } /**************************************************************************** Desc: Frees up memory associated with a subquery structure. ****************************************************************************/ void flmSQFree( SUBQUERY * pSubQuery, FLMBOOL bFreeEverything) { if (!bFreeEverything) { if (pSubQuery->pFSIndexCursor) { pSubQuery->pFSIndexCursor->releaseBlocks(); } if (pSubQuery->pFSDataCursor) { pSubQuery->pFSDataCursor->releaseBlocks(); } } else { FQNODE * pCurrNode = pSubQuery->pTree; QTYPES eType; // Free the memory associated with callbacks in the query tree. while (pCurrNode) { eType = GET_QNODE_TYPE( pCurrNode); if (IS_FLD_CB( eType, pCurrNode)) { (void)pCurrNode->pQAtom->val.QueryFld.fnGetField( pCurrNode->pQAtom->val.QueryFld.pvUserData, NULL, HFDB_NULL, pCurrNode->pQAtom->val.QueryFld.puiFldPath, FLM_FLD_CLEANUP, NULL, NULL, NULL); } // Find the next node to process if (pCurrNode->pChild) { pCurrNode = pCurrNode->pChild; } else { // Travel back up the tree until we find a node // that has a sibling. for (;;) { if (pCurrNode->pNextSib) { pCurrNode = pCurrNode->pNextSib; break; } if ((pCurrNode = pCurrNode->pParent) == NULL) break; } } } pSubQuery->OptPool.poolFree(); // Free up the file system cursors, if any. if (pSubQuery->pFSIndexCursor) { pSubQuery->pFSIndexCursor->Release(); pSubQuery->pFSIndexCursor = NULL; } if (pSubQuery->pFSDataCursor) { pSubQuery->pFSDataCursor->Release(); pSubQuery->pFSDataCursor = NULL; } } if (pSubQuery->pRec) { pSubQuery->pRec->Release(); pSubQuery->pRec = NULL; } } /**************************************************************************** Desc: Frees up memory associated with a cursor. ****************************************************************************/ void flmCurFree( CURSOR * pCursor, FLMBOOL bFinishTrans) { FLMUINT uiCnt; CS_CONTEXT * pCSContext; if (bFinishTrans) { flmCurFinishTransactions( pCursor, TRUE); } // Free the memory associated with positioning keys. flmCurFreePosKeys( pCursor); // Free the memory associated with any subqueries. flmCurFreeSQList( pCursor, TRUE); pCursor->SQPool.poolFree(); // Free the memory associated with the pool structures pCursor->QueryPool.poolFree(); if (pCursor->pDRNSet) { pCursor->pDRNSet->Release(); pCursor->pDRNSet = NULL; } for (uiCnt = 0; uiCnt < pCursor->QTInfo.uiNumPredicates; uiCnt++) { pCursor->QTInfo.ppPredicates [uiCnt]->Release(); pCursor->QTInfo.ppPredicates [uiCnt] = NULL; } if (pCursor->QTInfo.uiMaxPredicates > MAX_USER_PREDICATES) { f_free( &pCursor->QTInfo.ppPredicates); } f_memset( &pCursor->QTInfo, 0, sizeof( QTINFO)); pCursor->QTInfo.uiMaxPredicates = MAX_USER_PREDICATES; pCursor->QTInfo.ppPredicates = &pCursor->QTInfo.Predicates [0]; if ((pCSContext = pCursor->pCSContext) != NULL) { // Send message to free the cursor - if one was allocated. if ((pCursor->uiCursorId != FCS_INVALID_ID) && (pCSContext->bConnectionGood)) { FCL_WIRE Wire( pCSContext); // Send a request to free the cursor. if (RC_BAD( Wire.sendOp( FCS_OPCLASS_ITERATOR, FCS_OP_ITERATOR_FREE))) { goto CS_Exit; } if (RC_BAD( Wire.sendNumber( WIRE_VALUE_ITERATOR_ID, pCursor->uiCursorId))) { pCSContext->bConnectionGood = FALSE; goto CS_Exit; } if (RC_BAD( Wire.sendTerminate())) { pCSContext->bConnectionGood = FALSE; goto CS_Exit; } // Read the response - just discard it. if (RC_BAD( Wire.read())) { pCSContext->bConnectionGood = FALSE; goto CS_Exit; } } pCursor->pCSContext = NULL; } CS_Exit: f_free( &pCursor); return; } /**************************************************************************** Desc: Frees resources of the cursor without actually freeing the cursor. Keeps around the stuff that is needed to display the cursor information for debugging purposes after the fact. At this point the cursor is no longer usable. ****************************************************************************/ FLMEXP void FLMAPI FlmCursorReleaseResources( HFCURSOR hCursor) { FLMUINT uiCnt; CURSOR * pCursor = (CURSOR *)hCursor; flmCurFinishTransactions( pCursor, TRUE); flmCurFreeSQList( pCursor, FALSE); for (uiCnt = 0; uiCnt < pCursor->QTInfo.uiNumPredicates; uiCnt++) { pCursor->QTInfo.ppPredicates [uiCnt]->releaseResources(); } } /**************************************************************************** Desc: Frees memory allocated to an initialized cursor. The cursor handle cannot be used for additional cursor operations unless it is re-initialized. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmCursorFree( HFCURSOR * phCursor) { CURSOR * pCursor = (CURSOR *)*phCursor; IF_LogMessageClient * pLogMsg = NULL; flmAssert( pCursor != NULL); if( !gv_FlmSysData.pLogger) { return( FERR_OK); } if ((pLogMsg = gv_FlmSysData.pLogger->beginMessage( FLM_QUERY_MESSAGE, F_DEBUG_MESSAGE)) != NULL) { flmLogQuery( pLogMsg, 0, pCursor); f_endLogMessage( &pLogMsg); } if (!pCursor->pCSContext && gv_FlmSysData.uiMaxQueries) { FlmCursorReleaseResources( (HFCURSOR)pCursor); flmSaveQuery( *phCursor); *phCursor = HFCURSOR_NULL; } else { flmCurFree( pCursor, TRUE); *phCursor = HFCURSOR_NULL; } return( FERR_OK); } /**************************************************************************** Desc: Sets flags in the query that determine text comparision modes and the granularity for QuickFinder indexes. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmCursorSetMode( HFCURSOR hCursor, FLMUINT uiFlags) { CURSOR * pCursor = (CURSOR *)hCursor; flmAssert( pCursor != NULL); if ((pCursor->pCSContext) && (pCursor->uiCursorId != FCS_INVALID_ID)) { return( RC_SET( FERR_NOT_IMPLEMENTED)); } pCursor->QTInfo.uiFlags = uiFlags; return( FERR_OK); } /**************************************************************************** Desc: Clears the selection criteria in a query. ****************************************************************************/ FSTATIC void flmCurClearSelect( CURSOR * pCursor) { flmCurFreeSQList( pCursor, TRUE); pCursor->pTree = NULL; pCursor->bOptimized = FALSE; flmCurFinishTransactions( pCursor, FALSE); pCursor->QTInfo.pTopNode = NULL; pCursor->QTInfo.pCurOpNode = NULL; pCursor->QTInfo.pCurAtomNode = NULL; pCursor->QTInfo.uiNestLvl = 0; pCursor->QTInfo.uiExpecting = FLM_Q_OPERAND; pCursor->QTInfo.uiFlags = FLM_COMP_CASE_INSENSITIVE | FLM_COMP_WILD; pCursor->QueryPool.poolReset(); pCursor->uiLastRecID = 0; pCursor->ReadRc = FERR_OK; pCursor->rc = FERR_OK; } /**************************************************************************** Desc: Positions a cursor to EOF. ****************************************************************************/ FSTATIC RCODE flmCurPosToEOF( CURSOR * pCursor) { RCODE rc = FERR_OK; FlmRecord * pRecord = NULL; if (RC_BAD( rc = flmCurPerformRead( FLM_CURSOR_LAST, (HFCURSOR)pCursor, FALSE, TRUE, 0, &pRecord, NULL))) { if (rc == FERR_BOF_HIT) { // BOF HIT means that all entries were rejected. Change ReadRc // of cursor to be EOF hit. pCursor->ReadRc = RC_SET( FERR_EOF_HIT); rc = FERR_OK; } } else { rc = flmCurPerformRead( FLM_CURSOR_NEXT, (HFCURSOR)pCursor, TRUE, FALSE, 0, &pRecord, NULL); if (rc == FERR_EOF_HIT) { rc = FERR_OK; } } if (pRecord) { pRecord->Release(); } return( rc); } /**************************************************************************** Desc: Positions a cursor to BOF. ****************************************************************************/ FSTATIC RCODE flmCurPosToBOF( CURSOR * pCursor ) { RCODE rc = FERR_OK; FlmRecord * pRecord = NULL; if (RC_BAD( rc = flmCurPerformRead( FLM_CURSOR_FIRST, (HFCURSOR)pCursor, TRUE, TRUE, 0, &pRecord, NULL))) { if (rc == FERR_EOF_HIT) { // EOF HIT means that all entries were rejected. Change ReadRc // of destination to be BOF hit. pCursor->ReadRc = RC_SET( FERR_BOF_HIT); rc = FERR_OK; } } else { rc = flmCurPerformRead( FLM_CURSOR_PREV, (HFCURSOR)pCursor, FALSE, FALSE, 0, &pRecord, NULL); if (rc == FERR_BOF_HIT) { rc = FERR_OK; } } if (pRecord) { pRecord->Release(); } return( rc); } /**************************************************************************** Desc: Sets the positioning information in a query to be the same as that of another query. ****************************************************************************/ FSTATIC RCODE flmCurSetPos( CURSOR * pDestCursor, CURSOR * pSrcCursor) { RCODE rc = FERR_OK; FDB * pDb = NULL; FlmRecord * pRecord = NULL; FLMUINT uiRecordDrn; FLMUINT uiContainerNum; SUBQUERY * pSrcSubQuery; SUBQUERY * pDestSubQuery; FLMUINT uiRecMatch; FLMBYTE * pucKeyBuffer = NULL; FLMUINT uiKeyLen; IXD * pIxd; void * pvMark = NULL; FLMBOOL bSavedInvisTrans; // Must be at most one index used for optimizing - which means that // there will only be one sub-query. if ((pSrcSubQuery = pSrcCursor->pSubQueryList) != NULL) { // If the source query has been optimized, but there is either // no index or multiple sub-queries, we can't use it for // positioning. if (pSrcSubQuery->pNext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } } if ((!pDestCursor->bOptimized)) { if( RC_BAD( rc = flmCurPrep( pDestCursor))) { goto Exit; } } // Destination query has same restrictions as source query. // Must be at most one index used for optimizing. This means // there must be at most one sub-query. if ((pDestSubQuery = pDestCursor->pSubQueryList) != NULL) { if (pDestSubQuery->pNext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } } // If there is no current record in the source cursor, it is for one of // three reasons: the source cursor has not yet been positioned, it is at // EOF, or it is at BOF. Position the destination cursor to reflect these // cases. if (!pSrcCursor->uiLastRecID || !pSrcCursor->bOptimized) { if (pSrcCursor->ReadRc == FERR_EOF_HIT && pSrcCursor->bOptimized) { rc = flmCurPosToEOF( pDestCursor); } else { // Anything else we will just position it to FIRST - don't // really know what else to do. rc = flmCurPosToBOF( pDestCursor); } goto Exit; } // Initialize an FDB structure for various and sundry operations. pDb = pDestCursor->pDb; pvMark = pDb->TempPool.poolMark(); if( RC_BAD( rc = flmCurDbInit( pDestCursor))) { goto Exit; } // At this point, we know we have an optimized source cursor and // an optimized destination cursor and both of them have exactly // one sub-query. We also know that we are not positioned on // EOF or BOF. // Get the current record from the source sub-query. switch (pSrcSubQuery->OptInfo.eOptType) { case QOPT_USING_INDEX: // Get the current key and DRN. if (RC_BAD( rc = pSrcSubQuery->pFSIndexCursor->currentKeyBuf( pDb, &pDb->TempPool, &pucKeyBuffer, &uiKeyLen, &uiRecordDrn, &uiContainerNum))) { // Although currentKeyBuf is coded to allow for returning // EOF or BOF, by this point we should have already taken // care of that case. flmAssert( rc != FERR_BOF_HIT && rc != FERR_EOF_HIT); goto Exit; } // Retrieve the full record - must have for evaluation. flmAssert( uiContainerNum == pSrcCursor->uiContainer); if (RC_BAD( rc = flmRcaRetrieveRec( pDb, NULL, uiContainerNum, uiRecordDrn, TRUE, NULL, NULL, &pRecord))) { goto Exit; } break; case QOPT_USING_PREDICATE: if (pDestSubQuery->OptInfo.eOptType != QOPT_USING_PREDICATE) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } CB_ENTER( pDb, &bSavedInvisTrans); rc = pDestSubQuery->pPredicate->positionTo( (HFDB)pDb, pSrcSubQuery->pPredicate); CB_EXIT( pDb, bSavedInvisTrans); goto Exit; case QOPT_SINGLE_RECORD_READ: // Retrieve the record. if (!pSrcSubQuery->pRec) { if (RC_BAD( rc = flmRcaRetrieveRec( pDb, NULL, pSrcCursor->uiContainer, pSrcSubQuery->OptInfo.uiDrn, TRUE, NULL, NULL, &pSrcSubQuery->pRec))) { goto Exit; } pSrcSubQuery->uiDrn = pSrcSubQuery->OptInfo.uiDrn; pSrcSubQuery->bRecIsAKey = FALSE; } pRecord = pSrcSubQuery->pRec; pRecord->AddRef(); uiRecordDrn = pSrcSubQuery->uiDrn; break; case QOPT_PARTIAL_CONTAINER_SCAN: case QOPT_FULL_CONTAINER_SCAN: // Get the current record and DRN if (!pSrcSubQuery->pRec) { if (RC_BAD( rc = pSrcSubQuery->pFSDataCursor->currentRec( pDb, &pSrcSubQuery->pRec, &pSrcSubQuery->uiDrn))) { goto Exit; } } pRecord = pSrcSubQuery->pRec; pRecord->AddRef(); uiRecordDrn = pSrcSubQuery->uiDrn; pSrcSubQuery->bRecIsAKey = FALSE; break; default: break; } // If the destination sub-query is optimized with a user predicate, // cannot position source into it at this point. If the source // was a user predicate, the positioning will already have been // taken care of up above. // Also, source and destination cursors must be on the same // container. if ((pDestSubQuery->OptInfo.eOptType == QOPT_USING_PREDICATE) || (pDestCursor->uiContainer != pSrcCursor->uiContainer)) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } // Verify that the record passes the destination criteria. if (RC_BAD( rc = flmCurEvalCriteria( pDestCursor, pDestSubQuery, pRecord, FALSE, &uiRecMatch))) { goto Exit; } if (uiRecMatch != FLM_TRUE) { rc = RC_SET( FERR_NOT_FOUND); goto Exit; } // Record passes destination criteria - do the positioning. switch (pDestSubQuery->OptInfo.eOptType) { case QOPT_USING_INDEX: // VISIT: Should we insist that if the source is // also using an index that it be using the same // index as the destination? if (RC_BAD( rc = fdictGetIndex( pDb->pDict, pDb->pFile->bInLimitedMode, pDestSubQuery->OptInfo.uiIxNum, NULL, &pIxd))) { goto Exit; } // If the source is not using an index, we need to generate // our key buffer from pRecord. if (pSrcSubQuery->OptInfo.eOptType != QOPT_USING_INDEX) { if (RC_BAD( rc = flmCurMakeKeyFromRec( pDb, pIxd, &pDb->TempPool, pRecord, &pucKeyBuffer, &uiKeyLen))) { // FERR_ILLEGAL_OP means the record had multiple keys. We // don't know where to position to in that case. if (rc == FERR_ILLEGAL_OP) { rc = RC_SET( FERR_NOT_IMPLEMENTED); } goto Exit; } } if (RC_BAD( rc = pDestSubQuery->pFSIndexCursor->positionTo( pDb, pucKeyBuffer, uiKeyLen, uiRecordDrn))) { goto Exit; } if (RC_BAD( rc = pDestSubQuery->pFSIndexCursor->currentKey( pDb, &pDestSubQuery->pRec, &pDestSubQuery->uiDrn))) { goto Exit; } // We already know this key matches the criteria because we // tested it above. pDestSubQuery->bFirstReference = FALSE; // These should have been set by the call to currentKey. flmAssert( pDestSubQuery->pRec->getContainerID() == pDestCursor->uiContainer); flmAssert( pDestSubQuery->pRec->getID() == pDestSubQuery->uiDrn); pDestSubQuery->bRecIsAKey = TRUE; pDestSubQuery->uiCurrKeyMatch = FLM_UNK; break; case QOPT_SINGLE_RECORD_READ: if (uiRecordDrn != pDestSubQuery->OptInfo.uiDrn) { rc = RC_SET( FERR_NOT_FOUND); goto Exit; } break; case QOPT_PARTIAL_CONTAINER_SCAN: case QOPT_FULL_CONTAINER_SCAN: if (RC_BAD( rc = pDestSubQuery->pFSDataCursor->positionTo( pDb, uiRecordDrn))) { goto Exit; } break; default: flmAssert( 0); break; } pDestCursor->pCurrSubQuery = pDestSubQuery; Exit: if (pRecord) { pRecord->Release(); } if (pDb) { pDb->TempPool.poolReset( pvMark); fdbExit( pDb); } return( rc); } /**************************************************************************** Desc: Saves the current cursor position. ****************************************************************************/ RCODE flmCurSavePosition( CURSOR * pCursor ) { RCODE rc = FERR_OK; SUBQUERY * pSaveSubQuery; pCursor->pSaveSubQuery = pSaveSubQuery = pCursor->pCurrSubQuery; if (pSaveSubQuery) { switch (pSaveSubQuery->OptInfo.eOptType) { case QOPT_USING_INDEX: if (RC_BAD( rc = pSaveSubQuery->pFSIndexCursor->savePosition())) { goto Exit; } break; case QOPT_USING_PREDICATE: if (RC_BAD( rc = pSaveSubQuery->pPredicate->savePosition())) { goto Exit; } break; case QOPT_SINGLE_RECORD_READ: pSaveSubQuery->bSaveRecReturned = pSaveSubQuery->bRecReturned; break; case QOPT_PARTIAL_CONTAINER_SCAN: case QOPT_FULL_CONTAINER_SCAN: if (RC_BAD( rc = pSaveSubQuery->pFSDataCursor->savePosition())) { goto Exit; } break; default: break; } } Exit: return( rc); } /**************************************************************************** Desc: Restores the last cursor position that was saved. ****************************************************************************/ RCODE flmCurRestorePosition( CURSOR * pCursor ) { RCODE rc = FERR_OK; SUBQUERY * pSaveSubQuery = pCursor->pSaveSubQuery; if ((pCursor->pCurrSubQuery = pSaveSubQuery = pCursor->pSaveSubQuery) != NULL) { switch (pSaveSubQuery->OptInfo.eOptType) { case QOPT_USING_INDEX: if (RC_BAD( rc = pSaveSubQuery->pFSIndexCursor->restorePosition())) { goto Exit; } break; case QOPT_USING_PREDICATE: if (RC_BAD( rc = pSaveSubQuery->pPredicate->restorePosition())) { goto Exit; } break; case QOPT_SINGLE_RECORD_READ: pSaveSubQuery->bRecReturned = pSaveSubQuery->bSaveRecReturned; break; case QOPT_PARTIAL_CONTAINER_SCAN: case QOPT_FULL_CONTAINER_SCAN: if (RC_BAD( rc = pSaveSubQuery->pFSDataCursor->restorePosition())) { goto Exit; } break; default: break; } } Exit: return( rc); } /**************************************************************************** Desc: Sets a cursor to an absolute position. ****************************************************************************/ FSTATIC RCODE flmCurSetAbsolutePos( CURSOR * pCursor, FLMUINT uiPosition, FLMBOOL bFallForward, FLMUINT * puiPosition ) { RCODE rc = FERR_OK; FDB * pDb = NULL; FLMBOOL bAbsPositionable; SUBQUERY * pSubQuery; FSIndexCursor * pFSIndexCursor = NULL; FLMBOOL bSavedInvisTrans; FLMBOOL bPassedCriteria; FLMBOOL bDoRecMatch; FLMUINT uiDrn; // Verify that this is an absolute positionable query. if (RC_BAD( rc = flmCurAbsPositionable( pCursor, &bAbsPositionable))) { goto Exit; } if (!bAbsPositionable) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } pDb = pCursor->pDb; if (RC_BAD(rc = flmCurDbInit( pCursor))) { goto Exit; } // See if we are to position to EOF or BOF. if (uiPosition == (FLMUINT)(-1)) { if (RC_BAD( rc = flmCurPosToEOF( pCursor))) { goto Exit; } *puiPosition = (FLMUINT)(-1); } else if (uiPosition == 0) { if (RC_BAD( rc = flmCurPosToBOF( pCursor))) { goto Exit; } *puiPosition = 0; } else { // Set absolute position pSubQuery = pCursor->pSubQueryList; if (pSubQuery->OptInfo.eOptType == QOPT_USING_INDEX) { pFSIndexCursor = pSubQuery->pFSIndexCursor; if (RC_OK( rc = pFSIndexCursor->setAbsolutePosition( pDb, uiPosition))) { // Get the current key so we can test it below if (RC_BAD( rc = pFSIndexCursor->currentKey( pDb, &pSubQuery->pRec, &pSubQuery->uiDrn))) { goto Exit; } pSubQuery->bFirstReference = FALSE; // These should have been set by the call to currentKey. flmAssert( pSubQuery->pRec->getContainerID() == pCursor->uiContainer); flmAssert( pSubQuery->pRec->getID() == pSubQuery->uiDrn); pSubQuery->bRecIsAKey = TRUE; pSubQuery->uiCurrKeyMatch = FLM_UNK; } else // RC_BAD( rc) { if (rc != FERR_EOF_HIT) { flmAssert( rc != FERR_BOF_HIT); goto Exit; } // rc == FERR_EOF_HIT. If bFallForward is TRUE, we simply // set our position to EOF and return. Otherwise, we must // fall backward to find the last key that passes the query. if (bFallForward) { *puiPosition = (FLMUINT)(-1); pCursor->uiLastRecID = 0; pCursor->ReadRc = rc; rc = FERR_OK; goto Exit; } // Position to last - use CB_ENTER and CB_EXIT so that // transaction state of pDb is preserved. We need // to pass pDb into the getAbsolutePosition call below, and // if we had an invisible transaction going, it still needs // to be going below. CB_ENTER( pDb, &bSavedInvisTrans); rc = flmCurSearch( FLM_CURSOR_CONFIG, pCursor, TRUE, FALSE, NULL, NULL, NULL, &uiDrn); CB_EXIT( pDb, bSavedInvisTrans); if (RC_BAD( rc)) { if (rc == FERR_BOF_HIT) { *puiPosition = 0; pCursor->uiLastRecID = 0; pCursor->ReadRc = rc; rc = FERR_OK; } } else { pCursor->ReadRc = FERR_OK; pCursor->uiLastRecID = uiDrn; rc = pFSIndexCursor->getAbsolutePosition( pDb, puiPosition); } goto Exit; } // Need to verify that the key we are on will pass the query criteria. // If not, fall forward or backward until we find one that does. bPassedCriteria = FALSE; bDoRecMatch = TRUE; if (pSubQuery->OptInfo.bDoKeyMatch) { if (RC_BAD( rc = flmCurEvalCriteria( pCursor, pSubQuery, pSubQuery->pRec, TRUE, &pSubQuery->uiCurrKeyMatch))) { if (rc == FERR_TRUNCATED_KEY) { pSubQuery->uiCurrKeyMatch = FLM_UNK; rc = FERR_OK; } else { goto Exit; } } else if (pSubQuery->uiCurrKeyMatch == FLM_TRUE) { bDoRecMatch = FALSE; bPassedCriteria = TRUE; } else if (pSubQuery->uiCurrKeyMatch != FLM_UNK) { flmAssert( pSubQuery->uiCurrKeyMatch == FLM_FALSE); bDoRecMatch = FALSE; // If we must evaluate DRN fields, we need to go to the // next reference, so we set bFirstReference to FALSE. // Otherwise, we set it to TRUE so it will skip to the // next key below. pSubQuery->bFirstReference = !pSubQuery->bHaveDrnFlds; } } } else // eOptType == QOPT_USING_PREDICATE { FLMUINT uiSeconds; flmAssert( pSubQuery->OptInfo.eOptType == QOPT_USING_PREDICATE); if (pCursor->uiTimeLimit) { uiSeconds = FLM_TIMER_UNITS_TO_SECS( pCursor->uiTimeLimit); if (!uiSeconds) { uiSeconds = 1; } } else { uiSeconds = 0; } CB_ENTER( pDb, &bSavedInvisTrans); rc = pSubQuery->pPredicate->positionToAbs( (HFDB)pDb, uiPosition, bFallForward, uiSeconds, puiPosition, &pSubQuery->uiDrn); CB_EXIT( pDb, bSavedInvisTrans); if (RC_BAD( rc)) { goto Exit; } // If we get back EOF or BOF, set things up accordingly. if (*puiPosition == 0) { pCursor->ReadRc = RC_SET( FERR_BOF_HIT); pCursor->uiLastRecID = 0; goto Exit; } if (*puiPosition == (FLMUINT)(-1)) { pCursor->ReadRc = RC_SET( FERR_EOF_HIT); pCursor->uiLastRecID = 0; goto Exit; } // Verify that the record passes the rest of criteria. bPassedCriteria = FALSE; bDoRecMatch = TRUE; } // Need to verify that the record we positioned to // passes the query criteria, unless we were able to // pass the key up above. If we are using a predicate, // bDoRecMatch is always set to TRUE. if (bDoRecMatch) { FLMUINT uiRecMatch; // Retrieve the record if (RC_BAD( rc = flmRcaRetrieveRec( pDb, NULL, pCursor->uiContainer, pSubQuery->uiDrn, TRUE, NULL, NULL, &pSubQuery->pRec))) { if (rc == FERR_NOT_FOUND && pFSIndexCursor) { rc = RC_SET( FERR_NO_REC_FOR_KEY); } goto Exit; } if (pFSIndexCursor) { pSubQuery->bRecIsAKey = FALSE; } // Evaluate the record against the query criteria if (RC_BAD( rc = flmCurEvalCriteria( pCursor, pSubQuery, pSubQuery->pRec, FALSE, &uiRecMatch))) { goto Exit; } bPassedCriteria = (FLMBOOL)((uiRecMatch == FLM_TRUE) ? (FLMBOOL)TRUE : (FLMBOOL)FALSE); } // At this point, we know that the key passed our // query criteria. Verify that it passes the record // validator callback, if any. if (pCursor->fnRecValidator && bPassedCriteria) { CB_ENTER( pDb, &bSavedInvisTrans); bPassedCriteria = (pCursor->fnRecValidator)( FLM_CURSOR_CONFIG, (HFDB)pDb, pCursor->uiContainer, pSubQuery->pRec, NULL, pCursor->RecValData, &rc); CB_EXIT( pDb, bSavedInvisTrans); } if (bPassedCriteria) { pCursor->ReadRc = FERR_OK; pCursor->uiLastRecID = pSubQuery->uiDrn; *puiPosition = uiPosition; } else { // Position to next or previous - use CB_ENTER and CB_EXIT // so that transaction state of pDb is preserved. We need // to pass pDb into the getAbsolutePosition call below, and // if we had an invisible transaction going, it still needs // to be going below. CB_ENTER( pDb, &bSavedInvisTrans); rc = flmCurSearch( FLM_CURSOR_CONFIG, pCursor, FALSE, bFallForward, NULL, NULL, NULL, &uiDrn); CB_EXIT( pDb, bSavedInvisTrans); if (RC_BAD( rc)) { if (rc == FERR_BOF_HIT || rc == FERR_EOF_HIT) { *puiPosition = (FLMUINT)((rc == FERR_BOF_HIT) ? (FLMUINT)0 : (FLMUINT)(-1)); pCursor->uiLastRecID = 0; pCursor->ReadRc = rc; rc = FERR_OK; } goto Exit; } else { pCursor->ReadRc = FERR_OK; pCursor->uiLastRecID = uiDrn; if (pFSIndexCursor) { if (RC_BAD( rc = pFSIndexCursor->getAbsolutePosition( pDb, puiPosition))) { goto Exit; } } else { CB_ENTER( pDb, &bSavedInvisTrans); rc = pSubQuery->pPredicate->getAbsPosition( (HFDB)pDb, puiPosition); CB_EXIT( pDb, bSavedInvisTrans); if (RC_BAD( rc)) { goto Exit; } } } } } Exit: if (pDb) { flmExit( FLM_CURSOR_CONFIG, pDb, rc); } return( pCursor->rc = rc); } /**************************************************************************** Desc : Allows configuration of cursor attributes, including assignment of QuickFinder strings, indexes and search records. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmCursorConfig( HFCURSOR hCursor, eCursorConfigType eConfigType, void * Value1, void * Value2 ) { RCODE rc = FERR_OK; CURSOR * pCursor = (CURSOR *)hCursor; if (!pCursor) { flmAssert( 0); rc = RC_SET( FERR_INVALID_PARM); goto Exit; } if( RC_BAD( rc = flmCheckDatabaseState( pCursor->pDb))) { goto Exit; } switch( eConfigType) { case FCURSOR_ALLOW_DUPS: if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } if (pCursor->pDRNSet) { pCursor->pDRNSet->Release(); } pCursor->pDRNSet = NULL; pCursor->bEliminateDups = FALSE; break; case FCURSOR_ELIMINATE_DUPS: if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } if (pCursor->pDRNSet) { pCursor->pDRNSet->Release(); } pCursor->pDRNSet = NULL; pCursor->bEliminateDups = TRUE; break; case FCURSOR_CLEAR_QUERY: if ((pCursor->pCSContext) && (pCursor->uiCursorId != FCS_INVALID_ID)) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } flmCurClearSelect( pCursor); break; case FCURSOR_GEN_POS_KEYS: if ((pCursor->pCSContext) && (pCursor->uiCursorId != FCS_INVALID_ID)) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } rc = flmCurSetupPosKeyArray( pCursor); break; case FCURSOR_SET_HDB: if ((pCursor->pCSContext) && (pCursor->uiCursorId != FCS_INVALID_ID)) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } flmCurFinishTrans( pCursor); pCursor->pDb = (FDB *)Value1; break; case FCURSOR_DISCONNECT: flmCurFinishTransactions( pCursor, TRUE); break; case FCURSOR_RETURN_KEYS_OK: pCursor->bOkToReturnKeys = (FLMBOOL)((FLMBOOL)Value1 ? TRUE : FALSE); break; case FCURSOR_SET_FLM_IX: if ((pCursor->pCSContext) && (pCursor->uiCursorId != FCS_INVALID_ID)) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } if (pCursor->bOptimized) { rc = RC_SET( FERR_ILLEGAL_OP); } else { pCursor->uiIndexNum = (FLMUINT)Value1; } break; case FCURSOR_SET_OP_TIME_LIMIT: { FLMUINT uiTimeLimit; if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } uiTimeLimit = pCursor->uiTimeLimit = (FLMUINT)Value1; if (uiTimeLimit) { pCursor->uiTimeLimit = FLM_SECS_TO_TIMER_UNITS( uiTimeLimit); } break; } case FCURSOR_SET_PERCENT_POS: if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } if (RC_OK( rc = flmCurSetPercentPos( pCursor, (FLMUINT)Value1))) { pCursor->ReadRc = FERR_OK; } break; case FCURSOR_SET_ABS_POS: if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } rc = flmCurSetAbsolutePos( pCursor, *((FLMUINT *)Value1), (FLMBOOL)Value2, (FLMUINT *)Value1); break; case FCURSOR_SET_POS: { CURSOR * pPosCursor; if ((pPosCursor = (CURSOR *)Value1) == NULL) { flmAssert( 0); rc = RC_SET( FERR_INVALID_PARM); goto Exit; } if ((pCursor->pCSContext) || (pPosCursor->pCSContext)) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } if (RC_OK( rc = flmCurSetPos( pCursor, pPosCursor))) { pCursor->uiLastRecID = pPosCursor->uiLastRecID; } break; } case FCURSOR_SET_POS_FROM_DRN: rc = flmCurSetPosFromDRN( pCursor, (FLMUINT)Value1); break; case FCURSOR_SET_REC_TYPE: if ((pCursor->pCSContext) && (pCursor->uiCursorId != FCS_INVALID_ID)) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } if (pCursor->bOptimized) { rc = RC_SET( FERR_ILLEGAL_OP); } else { pCursor->uiRecType = (FLMUINT)Value1; } break; case FCURSOR_SET_REC_VALIDATOR: pCursor->fnRecValidator = (REC_VALIDATOR_HOOK)((FLMUINT)Value1); pCursor->RecValData = Value2; break; case FCURSOR_SET_STATUS_HOOK: pCursor->fnStatus = (STATUS_HOOK)((FLMUINT)Value1); pCursor->StatusData = Value2; pCursor->uiLastCBTime = FLM_GET_TIMER(); break; case FCURSOR_SAVE_POSITION: rc = flmCurSavePosition( pCursor); break; case FCURSOR_RESTORE_POSITION: rc = flmCurRestorePosition( pCursor); break; default: rc = RC_SET( FERR_NOT_IMPLEMENTED); break; } Exit: return( rc); } /************************************************************************ Desc : Returns whether or not a query is positionable. *************************************************************************/ FSTATIC RCODE flmCurPositionable( CURSOR * pCursor, FLMBOOL * pbPositionable) { RCODE rc = FERR_OK; SUBQUERY * pSubQuery; *pbPositionable = FALSE; // Optimize if necessary. if (!pCursor->bOptimized) { if( RC_BAD( rc = flmCurPrep( pCursor))) { goto Exit; } } // Must use an index and only one index. Also, the bDoRecMatch // flag must be FALSE, because we must be able to evaluate the // query entirely using only the fields in the index. Finally, // we must not need to evaluate DRN fields. Normally the // bHaveDrnFlds would not be a problem for evaluating a criteria // using only an index key, but we do not have DRNs at the higher // levels of the B-Tree, so having a DRN in the selection criteria // ruins positionability. if (((pSubQuery = pCursor->pSubQueryList) != NULL) && (!pSubQuery->pNext) && (pSubQuery->OptInfo.eOptType == QOPT_USING_INDEX) && (!pSubQuery->OptInfo.bDoRecMatch) && (!pSubQuery->bHaveDrnFlds)) { *pbPositionable = TRUE; } Exit: return( rc); } /************************************************************************ Desc : Returns whether or not a query is absolute positionable. *************************************************************************/ FSTATIC RCODE flmCurAbsPositionable( CURSOR * pCursor, FLMBOOL * pbAbsPositionable) { RCODE rc = FERR_OK; SUBQUERY * pSubQuery; FLMBOOL bSavedInvisTrans; *pbAbsPositionable = FALSE; // Optimize if necessary. if (!pCursor->bOptimized) { if( RC_BAD( rc = flmCurPrep( pCursor))) { goto Exit; } } // Must have only one subquery that either uses an index that // supports absolute positioning or uses a predicate that // supports it. if (((pSubQuery = pCursor->pSubQueryList) != NULL) && !pSubQuery->pNext) { if (pSubQuery->OptInfo.eOptType == QOPT_USING_INDEX) { *pbAbsPositionable = pSubQuery->pFSIndexCursor->isAbsolutePositionable(); } else if (pSubQuery->OptInfo.eOptType == QOPT_USING_PREDICATE) { CB_ENTER( pCursor->pDb, &bSavedInvisTrans); rc = pSubQuery->pPredicate->isAbsPositionable( (HFDB)pCursor->pDb, pbAbsPositionable); CB_EXIT( pCursor->pDb, bSavedInvisTrans); if (RC_BAD( rc)) { goto Exit; } } } Exit: return( rc); } /************************************************************************ Desc : Returns absolute position of cursor. *************************************************************************/ FSTATIC RCODE flmCurGetAbsolutePos( CURSOR * pCursor, FLMUINT * puiPosition ) { RCODE rc = FERR_OK; FDB * pDb = NULL; FLMBOOL bAbsPositionable; FLMBOOL bSavedInvisTrans; // Verify that this is an absolute positionable query. if (RC_BAD( rc = flmCurAbsPositionable( pCursor, &bAbsPositionable))) { goto Exit; } if (!bAbsPositionable) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } pDb = pCursor->pDb; if (RC_BAD(rc = flmCurDbInit( pCursor))) { goto Exit; } // See if we are positioned on EOF or BOF. Return special values // for these. if (!pCursor->uiLastRecID) { if (pCursor->ReadRc == FERR_EOF_HIT) { *puiPosition = (FLMUINT)(-1); // EOF } else { *puiPosition = 0; // BOF } } else { if (pCursor->pSubQueryList->OptInfo.eOptType == QOPT_USING_INDEX) { // Get absolute position from the first sub-query's index cursor. if (RC_BAD( rc = pCursor->pSubQueryList->pFSIndexCursor->getAbsolutePosition( pDb, puiPosition))) { goto Exit; } } else // eOptType == QOPT_USING_PREDICATE { flmAssert( pCursor->pSubQueryList->OptInfo.eOptType == QOPT_USING_PREDICATE); CB_ENTER( pDb, &bSavedInvisTrans); rc = pCursor->pSubQueryList->pPredicate->getAbsPosition( (HFDB)pDb, puiPosition); CB_EXIT( pDb, bSavedInvisTrans); } } Exit: if (pDb) { flmExit( FLM_CURSOR_GET_CONFIG, pDb, rc); } return( pCursor->rc = rc); } /************************************************************************ Desc : Returns absolute count for the cursor's index. NOTE: This is not necessarily the same thing as the number of records that will pass the filter criteria. *************************************************************************/ FSTATIC RCODE flmCurGetAbsoluteCount( CURSOR * pCursor, FLMUINT * puiCount ) { RCODE rc = FERR_OK; FDB * pDb = NULL; FLMBOOL bAbsPositionable; FLMBOOL bTotalEstimated; FLMBOOL bSavedInvisTrans; // Verify that this is an absolute positionable query. if (RC_BAD( rc = flmCurAbsPositionable( pCursor, &bAbsPositionable))) { goto Exit; } if (!bAbsPositionable) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } pDb = pCursor->pDb; if (RC_BAD(rc = flmCurDbInit( pCursor))) { goto Exit; } if (pCursor->pSubQueryList->OptInfo.eOptType == QOPT_USING_INDEX) { // Get absolute count from the first sub-query's index cursor. if (RC_BAD( rc = pCursor->pSubQueryList->pFSIndexCursor->getTotalReferences( pDb, puiCount, &bTotalEstimated))) { goto Exit; } } else // eOptType == QOPT_USING_PREDICATE { flmAssert( pCursor->pSubQueryList->OptInfo.eOptType == QOPT_USING_PREDICATE); CB_ENTER( pDb, &bSavedInvisTrans); rc = pCursor->pSubQueryList->pPredicate->getAbsCount( (HFDB)pDb, puiCount); CB_EXIT( pDb, bSavedInvisTrans); } Exit: if (pDb) { flmExit( FLM_CURSOR_GET_CONFIG, pDb, rc); } return( pCursor->rc = rc); } /**************************************************************************** Desc: Returns FLAIM cursor configuration values. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmCursorGetConfig( HFCURSOR hCursor, eCursorGetConfigType eGetConfigType, void * Value1, void * Value2) { RCODE rc = FERR_OK; CURSOR * pCursor = (CURSOR *)hCursor; if (!pCursor) { flmAssert( 0); rc = RC_SET( FERR_INVALID_PARM); goto Exit; } if( RC_BAD( rc = flmCheckDatabaseState( pCursor->pDb))) { goto Exit; } switch( eGetConfigType) { case FCURSOR_GET_PERCENT_POS: { if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); } else { rc = flmCurGetPercentPos( pCursor, (FLMUINT *)Value1); } break; } case FCURSOR_GET_ABS_POS: { if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); } else { rc = flmCurGetAbsolutePos( pCursor, (FLMUINT *)Value1); } break; } case FCURSOR_GET_ABS_COUNT: { if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); } else { rc = flmCurGetAbsoluteCount( pCursor, (FLMUINT *)Value1); } break; } case FCURSOR_GET_OPT_INFO_LIST: { if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); } else { OPT_INFO * pOptInfoArray = (OPT_INFO *)Value1; FLMUINT * puiSubQueryCnt = (FLMUINT *)Value2; FLMUINT uiSubQueryCnt = 0; SUBQUERY * pSubQuery; if (!pCursor->bOptimized) { if (RC_BAD( rc = flmCurPrep( pCursor))) { goto Exit; } } for (pSubQuery = pCursor->pSubQueryList; pSubQuery; pSubQuery = pSubQuery->pNext) { if (pOptInfoArray) { f_memcpy( &pOptInfoArray[ uiSubQueryCnt], &pSubQuery->OptInfo, sizeof( OPT_INFO)); } uiSubQueryCnt++; } *puiSubQueryCnt = uiSubQueryCnt; } break; } case FCURSOR_GET_OPT_INFO: { if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); } else { if (!pCursor->bOptimized) { if (RC_BAD( rc = flmCurPrep( pCursor))) { goto Exit; } } if (pCursor->pSubQueryList) { f_memcpy( (OPT_INFO *)Value2, &pCursor->pSubQueryList->OptInfo, sizeof( OPT_INFO)); } } break; } case FCURSOR_GET_FLM_IX: { if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); } else { FLMUINT uiIxNum = 0; FLMUINT uiIndexInfo = HAVE_NO_INDEX; FLMUINT uiSubqueryCount = 0; if (!pCursor->bOptimized) { if (RC_BAD( rc = flmCurPrep( pCursor))) { goto Exit; } } if( pCursor->pSubQueryList) { SUBQUERY * pTmpSubQuery = pCursor->pSubQueryList; FLMUINT uiSQIndex = 0; while( pTmpSubQuery) { uiSubqueryCount++; if (pTmpSubQuery->OptInfo.eOptType == QOPT_USING_INDEX) { uiSQIndex = pTmpSubQuery->OptInfo.uiIxNum; } else if (pTmpSubQuery->OptInfo.eOptType == QOPT_USING_PREDICATE) { FLMUINT uiTmpIndexInfo; uiSQIndex = pTmpSubQuery->pPredicate->getIndex( &uiTmpIndexInfo); if (uiTmpIndexInfo == HAVE_MULTIPLE_INDEXES) { if (!uiIxNum) { uiIxNum = uiSQIndex; } uiIndexInfo = HAVE_MULTIPLE_INDEXES; break; } else if (uiTmpIndexInfo == HAVE_ONE_INDEX_MULT_PARTS) { if (uiIxNum && uiSQIndex != uiIxNum) { uiIndexInfo = HAVE_MULTIPLE_INDEXES; break; } else { // NOTE: At this point we know that uiIxNum is either // zero or equal to uiSQIndex, so by // assigning uiIxNum to uiSQIndex it will cause us to // assign uiIndexInfo to HAVE_ONE_INDEX_MULT_PARTS // below. flmAssert( uiSQIndex); uiIxNum = uiSQIndex; } } } if (uiSQIndex) { if( !uiIxNum) { uiIxNum = uiSQIndex; if( uiSubqueryCount > 1) { uiIndexInfo = HAVE_ONE_INDEX_MULT_PARTS; } else { uiIndexInfo = HAVE_ONE_INDEX; } } else if( uiIxNum != uiSQIndex) { uiIndexInfo = HAVE_MULTIPLE_INDEXES; break; } else { uiIndexInfo = HAVE_ONE_INDEX_MULT_PARTS; } } else if( uiIxNum) { uiIndexInfo = HAVE_ONE_INDEX_MULT_PARTS; } pTmpSubQuery = pTmpSubQuery->pNext; } } if( Value1) { *((FLMUINT *)Value1) = uiIxNum; } if( Value2) { *((FLMUINT *)Value2) = uiIndexInfo; } } break; } case FCURSOR_GET_REC_TYPE: { *((FLMUINT *)Value1) = pCursor->uiRecType; break; } case FCURSOR_GET_FLAGS: { *((FLMUINT *)Value1) = pCursor->QTInfo.uiFlags; break; } case FCURSOR_GET_STATE: { *((FLMUINT *)Value1) = 0; if (pCursor->QTInfo.pTopNode || pCursor->QTInfo.pCurOpNode || pCursor->QTInfo.pCurAtomNode) { *((FLMUINT *)Value1) |= FCURSOR_HAVE_CRITERIA; } if (pCursor->QTInfo.uiExpecting & FLM_Q_OPERATOR) { *((FLMUINT *)Value1) |= FCURSOR_EXPECTING_OPERATOR; } if ((pCursor->QTInfo.uiNestLvl == 0) || ((pCursor->QTInfo.uiExpecting & FLM_Q_OPERATOR) && pCursor->QTInfo.pTopNode)) { *((FLMUINT *)Value1) |= FCURSOR_QUERY_COMPLETE; } if (pCursor->bOptimized) { *((FLMUINT *)Value1) |= (FCURSOR_QUERY_OPTIMIZED | FCURSOR_READ_PERFORMED); } break; } case FCURSOR_GET_POSITIONABLE: { if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); } else { rc = flmCurPositionable( pCursor, (FLMBOOL *)Value1); } break; } case FCURSOR_GET_ABS_POSITIONABLE: { if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); } else { rc = flmCurAbsPositionable( pCursor, (FLMBOOL *)Value1); } break; } case FCURSOR_AT_BOF: { if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); } else { if (!pCursor->bOptimized) { if (RC_BAD( rc = flmCurPrep( pCursor))) { goto Exit; } } *((FLMBOOL *)Value1) = (FLMBOOL)((!pCursor->uiLastRecID && pCursor->ReadRc == FERR_BOF_HIT) ? (FLMBOOL)TRUE : (FLMBOOL)FALSE); } break; } case FCURSOR_AT_EOF: { if (pCursor->pCSContext) { rc = RC_SET( FERR_NOT_IMPLEMENTED); } else { if (!pCursor->bOptimized) { if (RC_BAD( rc = flmCurPrep( pCursor))) { goto Exit; } } *((FLMBOOL *)Value1) = (FLMBOOL)((!pCursor->uiLastRecID && pCursor->ReadRc == FERR_EOF_HIT) ? (FLMBOOL)TRUE : (FLMBOOL)FALSE); } break; } default: { rc = RC_SET( FERR_NOT_IMPLEMENTED); break; } } Exit: return( rc); } /**************************************************************************** Desc: Given a IFD this function will verify that the input path matches and that it is in the same position (for compound keys) ****************************************************************************/ FSTATIC FLMBOOL flmCurMatchIndexPath( IFD * pIfd, FLMUINT * puiField, FLMUINT uiPos) { FLMUINT * puiIndexFieldPath; FLMBOOL bIsMatch; // Are both fields at the same position within a compound key if (pIfd->uiCompoundPos != uiPos) { bIsMatch = FALSE; goto Exit; } // Check in PARENT to CHILD order. puiIndexFieldPath = pIfd->pFieldPathPToC; while (*puiIndexFieldPath) { if (*puiField != *puiIndexFieldPath) { bIsMatch = FALSE; goto Exit; } puiIndexFieldPath++; puiField++; } bIsMatch = (*puiField == 0) ? TRUE : FALSE; Exit: return( bIsMatch); } /**************************************************************************** Desc: Uses the specified field path[s] to find a matching ordering index. Note: FlmCursorConfig( type == FCURSOR_SET_FLM_IX) and FlmCursorSetOrderIndex cannot both be called for the same cursor (they will override each other). Warning: The index selected from this call will be used for query optimization in addition to ordering the results. This could result in slower query performance. ****************************************************************************/ FLMEXP RCODE FLMAPI FlmCursorSetOrderIndex( HFCURSOR hCursor, FLMUINT * puiFieldPaths, /* List of field paths to match on. Each path is terminated with a single 0, and the entire list is terminated by two 0's */ FLMUINT * puiIndexRV) /* [optional] index id of matching index. A value of 0 indicates that no match was found. */ { RCODE rc = FERR_OK; CURSOR * pCursor = (CURSOR *)hCursor; FDB * pDb = NULL; IFD * pIfd; IFD * pIfd2; IXD * pIxd; FLMUINT * puiField = puiFieldPaths; FLMUINT uiPos; FLMUINT uiScore; FLMUINT uiBestScore = 0; FLMUINT uiBestIndex = 0; if (!pCursor) { flmAssert( 0); rc = RC_SET( FERR_INVALID_PARM); goto Exit; } flmAssert( puiFieldPaths != NULL); flmAssert( puiFieldPaths[ 0] != 0); // must give at least one field if (puiIndexRV) { *puiIndexRV = 0; } pDb = pCursor->pDb; if (RC_BAD( rc = flmCurDbInit( pCursor))) { goto Exit; } // Position to the last child field in the first path. while (*puiField) { puiField++; } puiField--; // Is the first field even indexed? if (RC_BAD( rc = fdictGetField( pDb->pDict, *puiField, NULL, &pIfd, NULL))) { goto Exit; } if (!pIfd) { // First field was not indexed (at all) so return a index id of ZERO. goto Exit; } // Loop through all indexes on the first field. for ( ; pIfd; pIfd = pIfd->pNextInChain) { uiScore = 0; pIxd = pIfd->pIxd; // If index is on a different container then skip this index. // NOTE: If pIxd->uiContainerNum is zero, it covers all // containers. if (pIxd->uiContainerNum && pIxd->uiContainerNum != pCursor->uiContainer) { continue; } // Verify that this index contains all requested field paths pIfd2 = pIxd->pFirstIfd; for (uiPos = 0, puiField = puiFieldPaths; *puiField; pIfd2++, uiPos++) { if (!flmCurMatchIndexPath( pIfd2, puiField, uiPos)) { goto NextIndex; } // The path matching requirements has been meet now score this field // Scoring Rules for each field path: // Value Index 4 points // SubSring/EachWord 2 points - index provides some ordering // Context Index 0 points - index provides no ordering // Scoring Boost 1 point - when # of field paths in index match // # of field paths supplied. if (pIfd2->uiFlags & IFD_CONTEXT) { ; // Context index gets no score. } else if (pIfd2->uiFlags & IFD_SUBSTRING) { uiScore += 2; } else if (pIfd2->uiFlags & IFD_EACHWORD) { uiScore += 2; } else { uiScore += 4; } // Position to the the next field path while (*puiField) { puiField++; } puiField++; // Skip single null terminator. // See if all components for index have been visited. if (pIfd2->uiFlags & IFD_LAST) { break; } } // If the number of requested fields equals the number of fields // contained within the index then add in a exact match score. if (pIfd2->uiFlags & IFD_LAST) { uiScore += 1; } // Always remember the highest index score if (uiBestScore < uiScore) { uiBestScore = uiScore; uiBestIndex = pIxd->uiIndexNum; } NextIndex: ; } Exit: if (puiIndexRV) { *puiIndexRV = uiBestIndex; if (uiBestIndex && RC_OK( rc)) { rc = FlmCursorConfig( hCursor, FCURSOR_SET_FLM_IX, (void *) uiBestIndex, (void *) 0); } } if (pDb) { (void)fdbExit( pDb); } return( rc); } /**************************************************************************** Desc: ****************************************************************************/ void flmCurFreeSQList( CURSOR * pCursor, FLMBOOL bFreeEverything) { SUBQUERY * pSubQuery; for( pSubQuery = pCursor->pSubQueryList; pSubQuery; pSubQuery = pSubQuery->pNext) { flmSQFree( pSubQuery, bFreeEverything); } if (bFreeEverything) { pCursor->SQPool.poolReset(); pCursor->pSubQueryList = NULL; } } /**************************************************************************** Desc: ****************************************************************************/ FLMUINT flmGetPathLen( FLMUINT * pFldPath) { FLMUINT uiPathLen = 0; if( pFldPath) { for( ; uiPathLen < GED_MAXLVLNUM + 1; uiPathLen++) { if( !pFldPath[ uiPathLen]) break; } } return uiPathLen; }