//------------------------------------------------------------------------- // Desc: Query positioning keys // Tabs: 3 // // Copyright (c) 1997-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: fqkeys.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $ //------------------------------------------------------------------------- #include "flaimsys.h" #define DOMAIN_TO_DRN(uiDomain) (FLMUINT)(((uiDomain) + 1) * 256 + 1) #define DRN_TO_DOMAIN(uiDrn) (FLMUINT)(((uiDrn) - 1) / 256 - 1) FSTATIC FLMINT flmPosKeyCompare( POS_KEY_p pKey1, POS_KEY_p pKey2); FSTATIC RCODE flmLoadPosKeys( CURSOR_p pCursor, POS_KEY_p pKeys, FLMUINT uiNumKeys, FLMBOOL bLeafLevel); FSTATIC RCODE flmKeyIsMatch( CURSOR_p pCursor, IXD_p pIxd, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT uiDrn, POS_KEY_p * ppKeys, FLMUINT * puiNumKeys, FLMUINT * puiKeyArrayAllocSize, FLMUINT uiKeyArrayGrowSize); FSTATIC RCODE flmExamineBlock( CURSOR_p pCursor, IXD_p pIxd, FLMBYTE * pucBlk, FSIndexCursor * pFSIndexCursor, FLMUINT ** ppuiChildBlockAddresses, FLMUINT * puiNumChildBlocks, FLMUINT * puiBlkAddressArrayAllocSize, POS_KEY_p * ppKeys, FLMUINT * puiNumKeys, FLMUINT * puiKeyArrayAllocSize, FLMBOOL * pbHighKeyInRange); FSTATIC RCODE flmGetLastKey( FDB * pDb, CURSOR_p pCursor, IXD_p pIxd, LFILE * pLFile, FLMUINT uiBlockAddress, POS_KEY_p * ppKeys, FLMUINT * puiNumKeys, FLMUINT * puiKeyArrayAllocSize); FSTATIC RCODE flmCurGetPosKeys( FDB * pDb, CURSOR_p pCursor); /**************************************************************************** Desc: Compares the contents of the key buffers for two cursor positioning keys, returning one of the following values: <0 Indicates that the first key is less than the second. 0 Indicates that the two keys are equal. >0 Indicates that the first key is greater then the second. ****************************************************************************/ FSTATIC FLMINT flmPosKeyCompare( POS_KEY_p pKey1, POS_KEY_p pKey2 ) { FLMINT iCmp; if (pKey1->uiKeyLen > pKey2->uiKeyLen) { if ((iCmp = f_memcmp( pKey1->pucKey, pKey2->pucKey, pKey2->uiKeyLen)) == 0) { iCmp = 1; } } else if( pKey1->uiKeyLen < pKey2->uiKeyLen) { if ((iCmp = f_memcmp( pKey1->pucKey, pKey2->pucKey, pKey1->uiKeyLen)) == 0) { iCmp = -1; } } else { if ((iCmp = f_memcmp( pKey1->pucKey, pKey2->pucKey, pKey2->uiKeyLen)) == 0) { // Compare DRNs if everything else is the same. NOTE: DRNs are in // reverse order in the positioning key array. if (pKey1->uiDrn && pKey2->uiDrn) { if (pKey1->uiDrn > pKey2->uiDrn) { iCmp = -1; } else if (pKey1->uiDrn < pKey2->uiDrn) { iCmp = 1; } } } } return iCmp; } /**************************************************************************** Desc: Loads a set of positioning keys into a subquery's array, allocating it if necessary. ****************************************************************************/ FSTATIC RCODE flmLoadPosKeys( CURSOR_p pCursor, POS_KEY_p pKeys, FLMUINT uiNumKeys, FLMBOOL bLeafLevel ) { RCODE rc = FERR_OK; FLMUINT uiKeyCnt; FLMUINT uiRFactor; FLMUINT uiTotCnt; // If the B-tree was empty, the key array will be left NULL. if (!pKeys || !uiNumKeys) { goto Exit; } // Allocate the array of positioning keys in the subquery. uiKeyCnt = (uiNumKeys > FLM_MAX_POS_KEYS + 1) ? FLM_MAX_POS_KEYS + 1 : uiNumKeys; if (RC_BAD( rc = f_calloc( uiKeyCnt * sizeof( POS_KEY), &pCursor->pPosKeyArray))) { goto Exit; } pCursor->uiNumPosKeys = uiKeyCnt; pCursor->bLeafLevel = bLeafLevel; // If there are less keys than the number of slots in the positioning // key array, each key must be put into multiple slots. Calculate how // many slots correspond to each key (uiSlots), and then set the keys // into their corresponding slots. NOTE: it will often be the case that // the number of keys does not divide evenly into the number of slots in // the array. In these cases thare will be a remainder, uiRFactor. If // uiRFactor = n, the first n keys will be set into (uiSlots + 1) slots. if (uiNumKeys <= FLM_MAX_POS_KEYS + 1) { for (uiTotCnt = 0; uiTotCnt < uiKeyCnt; uiTotCnt++) { f_memcpy( &pCursor->pPosKeyArray[ uiTotCnt], &pKeys[ uiTotCnt], sizeof( POS_KEY)); // NOTE: we're keeping this memory for the positioning key to which // it is being copied. pKeys [uiTotCnt].pucKey = NULL; } } // If there are more keys than the number of slots in the positioning // key array, a certain number of keys must be skipped for each key that // is set in the array. Calculate how many keys must be skipped for each // slot (uiIntervalSize), and then iterate through the passed-in set of // keys, setting the appropriate ones into their corresponding slots. // NOTE: it will often be the case that the number of slots in the array // does not divide evenly into the number of keys. In these cases there // will be a remainder (uiRFactor). Where uiRFactor = n, // (uiIntervalSize + 1) keys will be skipped before each of the first n // slots in the array are filled. else { FLMUINT uiLoopCnt; FLMUINT uiIntervalSize = (uiNumKeys - 2) / (FLM_MAX_POS_KEYS - 1) - 1; uiRFactor = (uiNumKeys - 2) % (FLM_MAX_POS_KEYS - 1); f_memcpy( &pCursor->pPosKeyArray[ 0], &pKeys[ 0], sizeof( POS_KEY)); f_memcpy( &pCursor->pPosKeyArray[ 1], &pKeys[ 1], sizeof( POS_KEY)); // NOTE: we're keeping this memory for the positioning key to which // it is being copied. pKeys [0].pucKey = NULL; pKeys [1].pucKey = NULL; uiKeyCnt = 2; for( uiTotCnt = 2; uiTotCnt < FLM_MAX_POS_KEYS; uiTotCnt++) { for( uiLoopCnt = 0; uiLoopCnt < uiIntervalSize; uiLoopCnt++) { f_free( &pKeys[ uiKeyCnt].pucKey); uiKeyCnt++; } if( uiRFactor) { f_free( &pKeys[ uiKeyCnt].pucKey); uiKeyCnt++; uiRFactor--; } f_memcpy( &pCursor->pPosKeyArray[ uiTotCnt], &pKeys[ uiKeyCnt], sizeof( POS_KEY)); // NOTE: we're keeping this memory for the positioning key to which // it is being copied. pKeys [uiKeyCnt].pucKey = NULL; uiKeyCnt++; } // Make sure the last key in the positioning key array is the last // key in the result set, then free the memory used for the pKey array. f_memcpy( &pCursor->pPosKeyArray[ FLM_MAX_POS_KEYS], &pKeys[ uiNumKeys - 1], sizeof( POS_KEY)); pKeys [uiNumKeys - 1].pucKey = NULL; while (uiKeyCnt < uiNumKeys - 1) { f_free( &pKeys[ uiKeyCnt].pucKey); uiKeyCnt++; } } Exit: return( rc); } /**************************************************************************** Desc: Evaluates an index key against selection criteria, and adds it to the passed-in key array. ****************************************************************************/ FSTATIC RCODE flmKeyIsMatch( CURSOR_p pCursor, IXD_p pIxd, FLMBYTE * pucKey, FLMUINT uiKeyLen, FLMUINT uiDrn, POS_KEY_p * ppKeys, FLMUINT * puiNumKeys, FLMUINT * puiKeyArrayAllocSize, FLMUINT uiKeyArrayGrowSize ) { RCODE rc = FERR_OK; SUBQUERY_p pSubQuery = pCursor->pSubQueryList; FlmRecord * pKey = NULL; FLMBOOL bHaveMatch = FALSE; FLMUINT uiResult; POS_KEY_p pPosKey; // If pSubQuery->bDoKeyMatch is FALSE, the selection criteria for this // query are satisfied by a contiguous set of index keys. Therefore, // there is no need to evaluate keys against the selection criteria. // We have already established that the passed-in key falls within // the range of keys that contains the result set of the query. // NOTE: bDoRecMatch cannot ever be set, otherwise, positioning is not // allowed. bHaveMatch = !pSubQuery->OptInfo.bDoKeyMatch; if (!bHaveMatch) { // Get the key in the form of a FlmRecord object. if (RC_BAD( rc = flmIxKeyOutput( pIxd, pucKey, uiKeyLen, &pKey, TRUE))) { goto Exit; } pKey->setID( uiDrn); // Evaluate the key against the subquery - there will only // be one at this point. if (RC_BAD( rc = flmCurEvalCriteria( pCursor, pSubQuery, pKey, TRUE, &uiResult))) { if (rc == FERR_TRUNCATED_KEY) { rc = FERR_OK; } else { goto Exit; } } bHaveMatch = (uiResult == FLM_TRUE) ? TRUE : FALSE; } if (bHaveMatch) { if (*puiNumKeys == *puiKeyArrayAllocSize) { if (RC_BAD( rc = f_recalloc( (*puiKeyArrayAllocSize + uiKeyArrayGrowSize) * sizeof( POS_KEY), ppKeys))) { goto Exit; } (*puiKeyArrayAllocSize) += uiKeyArrayGrowSize; } pPosKey = &((*ppKeys)[*puiNumKeys]); if (RC_BAD( rc = f_calloc( uiKeyLen, &pPosKey->pucKey))) { goto Exit; } f_memcpy( pPosKey->pucKey, pucKey, uiKeyLen); pPosKey->uiKeyLen = uiKeyLen; pPosKey->uiDrn = uiDrn; (*puiNumKeys)++; } Exit: if (pKey) { pKey->Release(); } return( rc); } /**************************************************************************** Desc: Examines an index B-tree block to find the keys in it that could be used to position within a cursor's result set. Visit:This code NEEDS to use the b-tree routines and NOT use the low level format codes to go to the next element or key. Other problems include doing the same work for each element even though you are at the same level of the b-tree. ****************************************************************************/ FSTATIC RCODE flmExamineBlock( CURSOR_p pCursor, IXD_p pIxd, FLMBYTE * pucBlk, FSIndexCursor * pFSIndexCursor, FLMUINT ** ppuiChildBlockAddresses, FLMUINT * puiNumChildBlocks, FLMUINT * puiBlkAddressArrayAllocSize, POS_KEY_p * ppKeys, FLMUINT * puiNumKeys, FLMUINT * puiKeyArrayAllocSize, FLMBOOL * pbHighKeyInRange ) { RCODE rc = FERR_OK; FLMBYTE ucFromKey [MAX_KEY_SIZ]; FLMUINT uiFromKeyLen; FLMBYTE ucUntilKey [MAX_KEY_SIZ]; FLMUINT uiUntilKeyLen; FLMUINT uiUntilDrn = 0; FLMBOOL bRangeOverlaps; FLMBOOL bUntilKeyInSet; FLMBOOL bUntilKeyPastEndOfKeys; FLMUINT uiDomain; DIN_STATE dinState; FLMBOOL bFirstRef; FLMUINT uiEndOfBlock = FB2UW( &pucBlk [BH_BLK_END]); FLMUINT uiCurrElmOffset = BH_OVHD; FLMUINT uiBlkType = (FLMUINT)BH_GET_TYPE( pucBlk); FLMUINT uiElmLength; FLMBYTE * pucElement; FLMBYTE * pucElm; FLMBYTE * pucElmKey; FLMBYTE * pucElmRecord; FLMBYTE * pucChildBlkAddr; FLMUINT uiChildBlkAddr; FLMUINT uiElmRecLen; FLMUINT uiElmKeyLen; FLMUINT uiElmPKCLen; FLMUINT uiElmOvhd; // This loop moves across a database block from the leftmost element to the // rightmost. Each contiguous pair of elements is viewed as a "key range", // where the first key in the pair is the start key and the second is the // end key. In the loop, each key range is checked to see if it overlaps // with any part of the query's result set. If it does, two things happen: // first, the down pointer from the end key is added to a passed-in list; // second, the end key is checked to see if it satisfies the query's // selection criteria. If it does, it is added to a passed-in list of // positioning keys. // NOTE: until key is given a key length of 0 so that in the first iteration, // the key range will be from FO_FIRST to the leftmost key in the block. if( uiBlkType == BHT_LEAF) { uiElmOvhd = BBE_KEY; } else if( uiBlkType == BHT_NON_LEAF_DATA) { uiElmOvhd = BNE_DATA_OVHD; } else if( uiBlkType == BHT_NON_LEAF) { uiElmOvhd = BNE_KEY_START; } else { uiElmOvhd = BNE_KEY_COUNTS_START; } uiUntilKeyLen = 0; bUntilKeyPastEndOfKeys = FALSE; bFirstRef = TRUE; while (uiCurrElmOffset < uiEndOfBlock) { // Move the until key into the start key buffer. if (uiUntilKeyLen) { f_memcpy( ucFromKey, ucUntilKey, uiUntilKeyLen); } uiFromKeyLen = uiUntilKeyLen; pucElement = &pucBlk [uiCurrElmOffset]; pucElm = pucElement; uiDomain = FSGetDomain( &pucElm, uiElmOvhd); if (uiBlkType == BHT_LEAF) { uiElmLength = (FLMUINT)(BBE_LEN( pucElement)); pucElmKey = &pucElement [BBE_KEY]; pucElmRecord = BBE_REC_PTR( pucElement); uiElmRecLen = BBE_GET_RL( pucElement); if (bFirstRef) { RESET_DINSTATE( dinState); uiUntilDrn = SENNextVal( &pucElm); bFirstRef = FALSE; } else { FLMUINT uiRefSize = uiElmRecLen - (FLMUINT)(pucElm - pucElmRecord); if (dinState.uiOffset < uiRefSize) { // Not at end, read current value. DINNextVal( pucElm, &dinState); } if (dinState.uiOffset >= uiRefSize) { uiCurrElmOffset += uiElmLength; bFirstRef = TRUE; // No need to go any further if we have run // off the end of the list of keys for the query. if (bUntilKeyPastEndOfKeys) { break; } else { continue; } } else { DIN_STATE savedState; // Don't move the dinState, stay // put and get the next DIN value savedState.uiOffset = dinState.uiOffset; savedState.uiOnes = dinState.uiOnes; uiUntilDrn -= DINNextVal( pucElm, &savedState); } } } else if (uiBlkType == BHT_NON_LEAF_DATA) { uiElmLength = uiElmOvhd; pucElmKey = pucElement; uiUntilDrn = DOMAIN_TO_DRN( uiDomain); } else { uiElmLength = BBE_GET_KL( pucElement ) + uiElmOvhd + (BNE_IS_DOMAIN(pucElement) ? BNE_DOMAIN_LEN : 0); pucElmKey = &pucElement [uiElmOvhd]; uiUntilDrn = DOMAIN_TO_DRN( uiDomain); } // See if we are on the last element. If it is a leaf block, // it does NOT represent a key. If it is a non-leaf block, // it represents the highest possible key, but there is no // data to extract fields from. if ((uiBlkType == BHT_LEAF) && (uiElmLength == uiElmOvhd)) { goto Exit; // Should return FERR_OK } if ((uiBlkType != BHT_LEAF) && (uiElmLength == uiElmOvhd)) { uiElmKeyLen = uiElmPKCLen = uiUntilKeyLen = 0; } else { // Get the element key length and previous key count (PKC). uiElmKeyLen = (FLMUINT)(BBE_GET_KL( pucElement)); uiElmPKCLen = (FLMUINT)(BBE_GET_PKC( pucElement)); // Now copy the current partial key into the EndKey key buffer. f_memcpy( &ucUntilKey [uiElmPKCLen], pucElmKey, uiElmKeyLen); uiUntilKeyLen = uiElmKeyLen + uiElmPKCLen; } // Test for Overlap of from key (exclusive) to until key (inclusive) // with search keys. bRangeOverlaps = pFSIndexCursor->compareKeyRange( ucFromKey, uiFromKeyLen, (FLMBOOL)((uiFromKeyLen) ? TRUE : FALSE), ucUntilKey, uiUntilKeyLen, FALSE, &bUntilKeyInSet, &bUntilKeyPastEndOfKeys); // Does this range overlap a range of keys? if (bRangeOverlaps) { // If we are not at the leaf level, get and save child block address. if (uiBlkType != BHT_LEAF) { // THIS CODE SHOULD BE USING A STACK!!!! if (uiElmOvhd == BNE_DATA_OVHD) { pucChildBlkAddr = &pucElement[ BNE_DATA_CHILD_BLOCK]; } else { pucChildBlkAddr = &pucElement [BNE_CHILD_BLOCK]; } uiChildBlkAddr = FB2UD( pucChildBlkAddr ); // Save uiChildBlkAddr to array of child block addresses. if (*puiNumChildBlocks == *puiBlkAddressArrayAllocSize) { if (RC_BAD( rc = f_recalloc( (*puiBlkAddressArrayAllocSize + FLM_ADDR_GROW_SIZE) * sizeof( FLMUINT), ppuiChildBlockAddresses))) { goto Exit; } (*puiBlkAddressArrayAllocSize) += FLM_ADDR_GROW_SIZE; } (*ppuiChildBlockAddresses)[ *puiNumChildBlocks] = uiChildBlkAddr; (*puiNumChildBlocks)++; } // If the last element in the block has just been processed, the key // will have a length of 0. If it is somewhere within the range of // keys that contains the query's result set, return TRUE in // pbHighKeyInRange. At a higher level, if only one more key is // needed to fill the array of positioning keys, the B-Tree will // then be traversed to the leaf level to retrieve and test the // rightmost key. if (!uiUntilKeyLen && bUntilKeyInSet) { *pbHighKeyInRange = TRUE; } // If the key falls into one of the key ranges that contain the // query's result set, see if it satisfies the selection criteria. // If so, increment the counter for the positioning key array and // put the key into the array. else if (bUntilKeyInSet) { if (RC_BAD( rc = flmKeyIsMatch( pCursor, pIxd, ucUntilKey, uiUntilKeyLen, uiUntilDrn, ppKeys, puiNumKeys, puiKeyArrayAllocSize, FLM_KEYS_GROW_SIZE))) { goto Exit; } } } // If this is not the first reference, stay inside the element and // get the next reference. if (!bFirstRef) { continue; } uiCurrElmOffset += uiElmLength; // No need to go any further if we have run off the end of the list // of keys for the query. if (bUntilKeyPastEndOfKeys) { break; } } Exit: return( rc); } /**************************************************************************** Desc: Finds the rightmost key in the leaf level of a B-tree, and evaluates it against the selection criteria of the given subquery. Visit:This routine must be rewritten to get rid of the low level BTREE definitions. The next() btree calls should have been used. ****************************************************************************/ FSTATIC RCODE flmGetLastKey( FDB * pDb, CURSOR_p pCursor, IXD_p pIxd, LFILE * pLFile, FLMUINT uiBlockAddress, POS_KEY_p * ppKeys, FLMUINT * puiNumKeys, FLMUINT * puiKeyArrayAllocSize ) { RCODE rc = FERR_OK; FLMBYTE ucEndKey [MAX_KEY_SIZ]; FLMUINT uiEndKeyLen = 0; FLMUINT uiEndDrn = 0; BTSK stack; FLMBYTE ucKeyBuf [MAX_KEY_SIZ]; BTSK_p pStack = &stack; FLMUINT uiEndOfBlock; FLMUINT uiCurrElmOffset; FLMUINT uiBlkType; FLMUINT uiElmLength; FLMBYTE * pucBlk; FLMBYTE * pucElement = NULL; FLMBYTE * pucElm; FLMBYTE * pucElmKey; FLMBYTE * pucElmRecord; FLMUINT uiElmRecLen; FLMBYTE * pucBlockAddress; FLMUINT uiElmKeyLen; FLMUINT uiElmPKCLen; FLMBOOL bHaveLastKey = FALSE; FLMUINT uiElmOvhd = 0; DIN_STATE dinState; FLMUINT uiRefSize; FSInitStackCache( pStack, 1); pStack->pKeyBuf = &ucKeyBuf [0]; // uiBlockAddress contains the address of the rightmost B-Tree block at // some unspecified level of the B-Tree (usually not the leaf level). // This loop works down the right side of the B-Tree from the passed-in // block address until it reaches the rightmost block at the leaf level. // The rightmost key is then found in that block. for( ;;) { if (RC_BAD(rc = FSGetBlock( pDb, pLFile, uiBlockAddress, pStack))) { goto Exit; } pucBlk = pStack->pBlk; uiBlkType = (FLMUINT)(BH_GET_TYPE( pucBlk)); uiEndOfBlock = (FLMUINT)pStack->uiBlkEnd; uiCurrElmOffset = BH_OVHD; // This loop works across a B-Tree block from the leftmost key to the // rightmost key. At non-leaf levels of the B-Tree, the child block // address associated with the rightmost key is then used to progress // further down the right side of the B-Tree. while (uiCurrElmOffset < uiEndOfBlock) { pucElement = &pucBlk [uiCurrElmOffset]; if (uiBlkType == BHT_LEAF) { uiElmOvhd = BBE_KEY; uiElmLength = (FLMUINT)(BBE_LEN( pucElement)); pucElmKey = &pucElement [BBE_KEY]; // See if we are on the last element. If it is a leaf block, // it does NOT represent a key; the previous element that was // processed contained the last key, which means we're finished. if (uiElmLength == uiElmOvhd) { bHaveLastKey = TRUE; break; } // Get the last DRN in the element - in case this element is // the last one before the end. pucElmRecord = BBE_REC_PTR( pucElement); uiElmRecLen = BBE_GET_RL( pucElement); pucElm = pucElement; (void)FSGetDomain( &pucElm, uiElmOvhd); RESET_DINSTATE( dinState); uiEndDrn = SENNextVal( &pucElm); uiRefSize = uiElmRecLen - (FLMUINT)(pucElm - pucElmRecord); for (;;) { if (dinState.uiOffset < uiRefSize) { // Not at end, read current value. DINNextVal( pucElm, &dinState); } if (dinState.uiOffset >= uiRefSize) { break; } else { DIN_STATE savedState; // Don't move the dinState, stay // put and get the next DIN value savedState.uiOffset = dinState.uiOffset; savedState.uiOnes = dinState.uiOnes; uiEndDrn -= DINNextVal( pucElm, &savedState); } } } else if( uiBlkType == BHT_NON_LEAF_DATA) { uiElmOvhd = uiElmLength = BNE_DATA_OVHD; pucElmKey = pucElement; } else { uiElmOvhd = pStack->uiElmOvhd; uiElmLength = BBE_GET_KL( pucElement ) + uiElmOvhd + (BNE_IS_DOMAIN(pucElement) ? BNE_DOMAIN_LEN : 0); pucElmKey = &pucElement [uiElmOvhd]; } if ((uiBlkType != BHT_LEAF) && (uiElmLength == uiElmOvhd)) { uiElmKeyLen = uiElmPKCLen = uiEndKeyLen = 0; } else if (uiBlkType == BHT_NON_LEAF_DATA) { uiElmLength = BNE_DATA_OVHD; f_memcpy( ucEndKey, pucElmKey, DIN_KEY_SIZ); } else { /* Get the element key length and previous key count (PKC). */ uiElmKeyLen = (FLMUINT)(BBE_GET_KL( pucElement)); uiElmPKCLen = (FLMUINT)(BBE_GET_PKC( pucElement)); f_memcpy( &ucEndKey [uiElmPKCLen], pucElmKey, uiElmKeyLen); uiEndKeyLen = (FLMUINT)(uiElmKeyLen + uiElmPKCLen); } uiCurrElmOffset += uiElmLength; } if (!bHaveLastKey) { // Get and save child block address. pucBlockAddress = (FLMBYTE *)((uiElmOvhd == BNE_DATA_OVHD) ? &pucElement [BNE_DATA_CHILD_BLOCK] : &pucElement [BNE_CHILD_BLOCK]); uiBlockAddress = FB2UD( pucBlockAddress ); } else { // We have reached the leaf level of the B-Tree, and we have the // rightmost key. See if it satisfies the selection criteria for // the query. If so, put it into the passed-in array of positioning // keys. Then break out of the loop; we're finished. if (RC_BAD( rc = flmKeyIsMatch( pCursor, pIxd, ucEndKey, uiEndKeyLen, uiEndDrn, ppKeys, puiNumKeys, puiKeyArrayAllocSize, 1))) { goto Exit; } break; } } Exit: FSReleaseBlock( pStack, FALSE); return( rc); } /**************************************************************************** Desc: Frees the allocations associated with a subquery's array. ****************************************************************************/ void flmCurFreePosKeys( CURSOR_p pCursor ) { FLMUINT uiLoopCnt; if (pCursor->pPosKeyArray) { for (uiLoopCnt = 0; uiLoopCnt < pCursor->uiNumPosKeys; uiLoopCnt++) { f_free( &pCursor->pPosKeyArray[ uiLoopCnt].pucKey); } f_free( &pCursor->pPosKeyArray); pCursor->uiNumPosKeys = 0; } pCursor->uiLastPrcntPos = 0; pCursor->uiLastPrcntOffs = 0; pCursor->bUsePrcntPos = FALSE; } /**************************************************************************** Desc: Gets a set of positioning keys for a particular subquery. ****************************************************************************/ FSTATIC RCODE flmCurGetPosKeys( FDB * pDb, CURSOR_p pCursor ) { RCODE rc = FERR_OK; BTSK stack [BH_MAX_LEVELS]; FLMBYTE ucKeyBuf [MAX_KEY_SIZ]; BTSK_p pStack = stack; LFILE * pLFile; LFILE TmpLFile; IXD_p pIxd; SUBQUERY_p pSubQuery; FLMUINT * puiChildBlockAddresses = NULL; FLMUINT * puiTmpBlocks = NULL; FLMUINT uiNumChildBlocks = 0; FLMUINT uiNumTmpBlks; FLMUINT uiBlkAddressArrayAllocSize = 0; POS_KEY_p pKeys = NULL; FLMUINT uiNumKeys = 0; FLMUINT uiKeyArrayAllocSize = 0; FLMBOOL bHighKeyInRange = FALSE; FSInitStackCache( &stack[ 0], BH_MAX_LEVELS); // Check to verify that it is possible to set up an array of positioning keys // for this query. The following conditions must be met: // 1) The query must use one and only one index // 2) The criteria must be solvable using only the index keys // 3) The selection criteria cannot include DRNs. if (((pSubQuery = pCursor->pSubQueryList) == NULL) || pSubQuery->pNext || pSubQuery->OptInfo.eOptType != QOPT_USING_INDEX || pSubQuery->OptInfo.bDoRecMatch || pSubQuery->bHaveDrnFlds) { rc = RC_SET( FERR_NOT_IMPLEMENTED); goto Exit; } // Free the existing key array, if there is one if (pCursor->pPosKeyArray) { flmCurFreePosKeys( pCursor); } // Get the necessary LFILE and IXD information from the subquery index. if (RC_BAD( rc = fdictGetIndex( pDb->pDict, pDb->pFile->bInLimitedMode, pSubQuery->OptInfo.uiIxNum, &pLFile, &pIxd))) { goto Exit; } // Set up a B-tree stack structure and get the root block in the index // B-tree. pStack->pKeyBuf = &ucKeyBuf [0]; // If no root block returned from FSGetRootBlock, the array will be // returned empty, with rc set to success. if (RC_BAD( rc = FSGetRootBlock( pDb, &pLFile, &TmpLFile, pStack))) { if (rc == FERR_NO_ROOT_BLOCK) { flmAssert( pLFile->uiRootBlk == BT_END); rc = FERR_OK; } goto Exit; } uiNumTmpBlks = 1; // Extract the array of positioning keys by working down the B-tree // from the root block. This loop will terminate when all levels of // the B-Tree have been processed, or when enough keys have been // found to populate the array. // NOTE: pSubQuery->pPosKeyPool has been initialized at a higher level. for(;;) { FLMUINT uiBlkCnt = 0; // Work across the present level of the B-Tree from right to left. for(;;) { // This function moves across a database block from the leftmost // element to the rightmost, checking each key to see if it is // found in the query's result set. If it is, it is added to a // list of possible positioning keys, and its pointers to child // blocks in the B-Tree are also kept. In the event that not // enough keys are found at a given level in the B-Tree, the list // of child block pointers is used to work through the next level // of the B-Tree. if (RC_BAD( rc = flmExamineBlock( pCursor, pIxd, pStack->pBlk, pSubQuery->pFSIndexCursor, &puiChildBlockAddresses, &uiNumChildBlocks, &uiBlkAddressArrayAllocSize, &pKeys, &uiNumKeys, &uiKeyArrayAllocSize, &bHighKeyInRange))) { goto Exit; } uiBlkCnt++; // uiNumTmpBlks has the number of blocks to be processed at the // current level of the B-Tree. When those have been processed, // break out of this loop and go to the next level of the B-Tree. if (uiBlkCnt == uiNumTmpBlks) { break; } if (RC_BAD( rc = FSGetBlock( pDb, pLFile, puiTmpBlocks[ uiBlkCnt], pStack ))) { goto Exit; } } // If we're not on the leaf level, and we have at least // FLM_MIN_POS_KEYS - 1 keys, we need to go out and evaluate // the last key at the leaf level. if (uiNumKeys >= FLM_MIN_POS_KEYS - 1 && bHighKeyInRange && uiNumChildBlocks) { if (RC_BAD( rc = flmGetLastKey( pDb, pCursor, pIxd, pLFile, puiChildBlockAddresses [uiNumChildBlocks - 1], &pKeys, &uiNumKeys, &uiKeyArrayAllocSize))) { goto Exit; } } // If we have enough keys, or if we have reached the last level of the // B-tree, load up the subquery key array and quit. if ((uiNumKeys >= FLM_MIN_POS_KEYS) || !uiNumChildBlocks) { rc = flmLoadPosKeys( pCursor, pKeys, uiNumKeys, (FLMBOOL)((uiNumChildBlocks == 0) ? TRUE : FALSE)); goto Exit; } // If not enough keys, go to the next level of the B-tree and traverse // it to find keys. This should be done down to the last level. else { FLMUINT uiKeyCnt; f_free( &puiTmpBlocks); puiTmpBlocks = puiChildBlockAddresses; uiNumTmpBlks = uiNumChildBlocks; puiChildBlockAddresses = NULL; uiNumChildBlocks = uiBlkAddressArrayAllocSize = 0; for (uiKeyCnt = 0; uiKeyCnt < uiNumKeys; uiKeyCnt++) { f_free( &pKeys[ uiKeyCnt].pucKey); } f_free( &pKeys); pKeys = NULL; uiNumKeys = 0; uiKeyArrayAllocSize = 0; if (RC_BAD( rc = FSGetBlock( pDb, pLFile, puiTmpBlocks[ 0], pStack ))) { goto Exit; } bHighKeyInRange = FALSE; } } Exit: if ( pKeys) { if (RC_BAD( rc)) { for ( FLMUINT uiKeyCnt = 0; uiKeyCnt < uiNumKeys; uiKeyCnt++) { f_free( &pKeys[ uiKeyCnt].pucKey); } } f_free( &pKeys); } f_free( &puiChildBlockAddresses); f_free( &puiTmpBlocks); FSReleaseStackCache( stack, BH_MAX_LEVELS, FALSE); return( rc); } /**************************************************************************** Desc: Gets a set of positioning keys for a particular subquery. ****************************************************************************/ RCODE flmCurSetupPosKeyArray( CURSOR_p pCursor ) { RCODE rc = FERR_OK; FDB_p pDb = NULL; // Optimize the subqueries as necessary if (!pCursor->bOptimized) { if (RC_BAD( rc = flmCurPrep( pCursor))) { goto Exit; } } // Set up the pDb pDb = pCursor->pDb; if (RC_BAD(rc = flmCurDbInit( pCursor))) { goto Exit; } // Set up array of positioning keys. if (RC_BAD( rc = flmCurGetPosKeys( pDb, pCursor))) { goto Exit; } Exit: if (pDb) { flmExit( FLM_CURSOR_CONFIG, pDb, rc); } return( rc); } /**************************************************************************** Desc: Gets the approximate percentage position of a passed-in key within a cursor's result set. ****************************************************************************/ RCODE flmCurGetPercentPos( CURSOR_p pCursor, FLMUINT * puiPrcntPos ) { RCODE rc = FERR_OK; FDB_p pDb = NULL; IXD_p pIxd; POS_KEY_p pPosKeyArray; POS_KEY CompKey; FLMUINT uiLowOffset; FLMUINT uiMidOffset; FLMUINT uiHighOffset; FLMUINT uiIntervalSize; FLMUINT uiRFactor; FLMINT iCmp; FLMUINT uiContainer; // Optimize the subqueries as necessary if (!pCursor->bOptimized) { if (RC_BAD( rc = flmCurPrep( pCursor))) { goto Exit; } } pDb = pCursor->pDb; if (RC_BAD(rc = flmCurDbInit( pCursor))) { goto Exit; } // If no array of positioning keys exists in the subquery, set one up. if (!pCursor->uiNumPosKeys) { if (RC_BAD( rc = flmCurGetPosKeys( pDb, pCursor))) { goto Exit; } // If no positioning keys exist, either the index or the result set // is empty. Return NOT_FOUND. if (!pCursor->uiNumPosKeys) { rc = RC_SET( FERR_NOT_FOUND); goto Exit; } } // If the number of positioning keys is 1, the position is 0%. if (pCursor->uiNumPosKeys == 1) { *puiPrcntPos = 0; goto Exit; } pPosKeyArray = pCursor->pPosKeyArray; if (pCursor->uiNumPosKeys == 2) { uiIntervalSize = FLM_MAX_POS_KEYS; uiRFactor = 0; } else { uiIntervalSize = FLM_MAX_POS_KEYS / (pCursor->uiNumPosKeys - 1); uiRFactor = FLM_MAX_POS_KEYS % (pCursor->uiNumPosKeys - 1); } // DEFECT 84741 -- only want to return a position of 1 for the second key // if the positioning key array is full. // Get an IXD, then convert the passed-in key from GEDCOM format to a // buffer containing the key in the FLAIM internal format. if (RC_BAD( rc = fdictGetIndex( pDb->pDict, pDb->pFile->bInLimitedMode, pCursor->pSubQueryList->OptInfo.uiIxNum, NULL, &pIxd))) { goto Exit; } if (pCursor->ReadRc == FERR_BOF_HIT) { *puiPrcntPos = 0; goto Exit; } if (pCursor->ReadRc == FERR_EOF_HIT) { *puiPrcntPos = FLM_MAX_POS_KEYS; rc = RC_SET( FERR_EOF_HIT); goto Exit; } if (RC_BAD( rc = pCursor->pSubQueryList->pFSIndexCursor->currentKeyBuf( pDb, &pDb->TempPool, &CompKey.pucKey, &CompKey.uiKeyLen, &CompKey.uiDrn, &uiContainer))) { if (rc == FERR_EOF_HIT || rc == FERR_BOF_HIT || rc == FERR_NOT_FOUND) { rc = FERR_OK; *puiPrcntPos = 0; } goto Exit; } flmAssert( uiContainer == pCursor->uiContainer); // If a set position call has been performed, and no reposisioning has // been done since, check the passed-in key to see if it matches the // key returned from the set position call. If so, return the percent // passed in on the set position call. This is to create some symmetry // where the user calls set position, then takes the resulting key and // passes it back into a get position call. if (pCursor->bUsePrcntPos && pCursor->uiLastPrcntPos <= FLM_MAX_POS_KEYS) { if (flmPosKeyCompare( &pPosKeyArray[ pCursor->uiLastPrcntOffs], &CompKey) == 0) { *puiPrcntPos = pCursor->uiLastPrcntPos; goto Exit; } pCursor->bUsePrcntPos = FALSE; } // Do a binary search in the array of positioning keys for the passed-in // key. NOTE: the point of this search is to find the closest key <= to // the passed- in key. The range of values returned is // 0 to FLM_MAX_POS_KEYS (currently defined to be 1000), where 0 and // FLM_MAX_POS_KEYS represent the first and last keys in the query's // result set, respectively. Numbers between these two endpoints represent // intervals between two keys that are adjacent in the array, but which // may have any number of intervening keys in the index. uiLowOffset = 0; uiHighOffset = pCursor->uiNumPosKeys - 1; for(;;) { if (uiLowOffset == uiHighOffset) { uiMidOffset = uiLowOffset; // Defect #84741 (fix after failing regression test - // zeroeth object was always returning position 1). // Must do final comparison to determine which side of // the positioning key our key falls on. Remember, // the positioning key represents all keys that are // LESS THAN OR EQUAL to it. Thus, if this key is // greater than it, we should use the next positioning // key. if ((flmPosKeyCompare( &pPosKeyArray[ uiMidOffset], &CompKey) < 0) && (uiMidOffset < pCursor->uiNumPosKeys - 1)) { uiMidOffset++; } break; } uiMidOffset = (FLMUINT)((uiHighOffset + uiLowOffset) / 2); iCmp = flmPosKeyCompare( &pPosKeyArray[ uiMidOffset], &CompKey); if( iCmp < 0) { uiLowOffset = uiMidOffset + 1; } else if( iCmp > 0) { if( uiMidOffset == uiLowOffset) { break; } else { uiHighOffset = uiMidOffset - 1; } } else { break; } } // DEFECT 84741 -- the first object should only return a position of 1 // if there are FLM_MAX_POS_KEYS positioning keys in the array. if (uiMidOffset == 0 || (uiMidOffset == 1 && pCursor->uiNumPosKeys == FLM_MAX_POS_KEYS + 1)) { *puiPrcntPos = uiMidOffset; } else if (uiMidOffset == pCursor->uiNumPosKeys - 1) { *puiPrcntPos = FLM_MAX_POS_KEYS; } else if (uiMidOffset <= uiRFactor) { *puiPrcntPos = uiMidOffset * (uiIntervalSize + 1); } else if (uiRFactor) { *puiPrcntPos = uiRFactor * (uiIntervalSize + 1) + (uiMidOffset - uiRFactor) * uiIntervalSize; } else { *puiPrcntPos = uiMidOffset * uiIntervalSize; } Exit: if (pDb) { flmExit( FLM_CURSOR_GET_CONFIG, pDb, rc); } return( rc); } /**************************************************************************** Desc: Sets a query's position to a percentage represented by one of an array of positioning keys. ****************************************************************************/ RCODE flmCurSetPercentPos( CURSOR_p pCursor, FLMUINT uiPrcntPos ) { RCODE rc = FERR_OK; FDB_p pDb = NULL; FLMUINT uiPrcntOffs; FLMUINT uiIntervalSize; FLMUINT uiRFactor; SUBQUERY_p pSubQuery = NULL; POS_KEY_p pPosKey; // Optimize the subqueries as necessary if (!pCursor->bOptimized) { if (RC_BAD( rc = flmCurPrep( pCursor))) { goto Exit; } } // Check the value for the percentage position. Should be between // 0 and FLM_MAX_POS_KEYS. flmAssert( uiPrcntPos <= FLM_MAX_POS_KEYS); // Initialize some variables pCursor->uiLastRecID = 0; pDb = pCursor->pDb; if (RC_BAD(rc = flmCurDbInit( pCursor))) { goto Exit; } // If no array of positioning keys exists in the subquery, set one up. if (!pCursor->uiNumPosKeys) { if (RC_BAD( rc = flmCurGetPosKeys( pDb, pCursor))) { goto Exit; } // If no positioning keys exist, either the index or the result set // is empty. Return BOF or EOF. if (!pCursor->uiNumPosKeys) { rc = RC_SET( FERR_EOF_HIT); goto Exit; } } pSubQuery = pCursor->pSubQueryList; Retry: // Calculate the percent position using the following rules: // 1) If the number of positioning keys is 1, the position is 0%. // 2) If the number of positioning keys is 2, the position is either 0% or // FLM_MAX_POS_KEYS. // 3) If there are more than 2 positioning keys, calculate the interval into // which the percentage position falls. if (pCursor->uiNumPosKeys == 1) { uiPrcntOffs = 0; } else { if (pCursor->uiNumPosKeys == 2) { uiIntervalSize = FLM_MAX_POS_KEYS; uiRFactor = 0; } else { uiIntervalSize = FLM_MAX_POS_KEYS / (pCursor->uiNumPosKeys - 1); uiRFactor = FLM_MAX_POS_KEYS % (pCursor->uiNumPosKeys - 1); } // Convert passed-in number to an array offset. if (uiPrcntPos) { if (uiPrcntPos == 0 || pCursor->uiNumPosKeys == FLM_MAX_POS_KEYS + 1) { uiPrcntOffs = uiPrcntPos; } else if( uiPrcntPos == FLM_MAX_POS_KEYS) { uiPrcntOffs = pCursor->uiNumPosKeys - 1; } else if( uiPrcntPos <= uiRFactor * (uiIntervalSize + 1)) { uiPrcntOffs = uiPrcntPos / (uiIntervalSize + 1); } else { uiPrcntOffs = uiRFactor + (uiPrcntPos - (uiIntervalSize + 1) * uiRFactor) / uiIntervalSize; } } else { uiPrcntOffs = 0; } } pPosKey = &pCursor->pPosKeyArray [uiPrcntOffs]; // If the keys were generated from the leaf level, we can // position directly to them. If not, we must call the // positionToDomain routine. if (pCursor->bLeafLevel) { rc = pSubQuery->pFSIndexCursor->positionTo( pDb, pPosKey->pucKey, pPosKey->uiKeyLen, pPosKey->uiDrn); } else { rc = pSubQuery->pFSIndexCursor->positionToDomain( pDb, pPosKey->pucKey, pPosKey->uiKeyLen, DRN_TO_DOMAIN( pPosKey->uiDrn)); } if (RC_BAD( rc)) { RCODE saveRc; if (rc != FERR_BOF_HIT && rc != FERR_EOF_HIT && rc != FERR_NOT_FOUND) { goto Exit; } // If the positioning key was not found, the database has undergone // significant change since the array of positioning keys was generated. // Try to regenerate the array and reposition. saveRc = rc; if (RC_BAD( rc = flmCurGetPosKeys( pDb, pCursor))) { goto Exit; } if (pCursor->pPosKeyArray [0].pucKey == NULL) { rc = saveRc; goto Exit; } goto Retry; } // Retrieve the current key and DRN from the index cursor. if (RC_BAD( rc = pSubQuery->pFSIndexCursor->currentKey( pDb, &pSubQuery->pRec, &pSubQuery->uiDrn))) { goto Exit; } pSubQuery->bFirstReference = FALSE; pSubQuery->uiCurrKeyMatch = FLM_TRUE; // These should have already been set by the call to currentKey. flmAssert( pSubQuery->pRec->getContainerID() == pCursor->uiContainer); flmAssert( pSubQuery->pRec->getID() == pSubQuery->uiDrn); pSubQuery->bRecIsAKey = TRUE; // If we got this far, the positioning operation was a success. Set // the query return code to success so it doesn't mess up subsequent // read operations. pCursor->uiLastRecID = pSubQuery->uiDrn; pCursor->rc = FERR_OK; pCursor->uiLastPrcntPos = uiPrcntPos; pCursor->uiLastPrcntOffs = uiPrcntOffs; pCursor->bUsePrcntPos = TRUE; Exit: if (pDb) { flmExit( FLM_CURSOR_CONFIG, pDb, rc); } return( rc); }