SQLite4
Check-in [d30de7f821]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Range-delete related SEEK_GE and SEEK_LE fixes.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | range-delete
Files: files | file ages | folders
SHA1: d30de7f821ba18345c1958c4c226de08991326ca
User & Date: dan 2012-10-14 09:41:35
Context
2012-10-15
14:26
Fix a problem with lsm_mt2 in lsmtest_tdb3.c. check-in: c025a26642 user: dan tags: range-delete
2012-10-14
09:41
Range-delete related SEEK_GE and SEEK_LE fixes. check-in: d30de7f821 user: dan tags: range-delete
2012-10-11
19:36
Fix cases involving iteration through split levels where the first part of a range-delete has been merged or annihilated but the second has not. check-in: 45d5b7570e user: dan tags: range-delete
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to lsm-test/lsmtest1.c.

   356    356     int *pRc
   357    357   ){
   358    358     int i;
   359    359   
   360    360     testScanCompare(pControl, pDb, 0, 0, 0,         0, 0,         pRc);
   361    361     testScanCompare(pControl, pDb, 1, 0, 0,         0, 0,         pRc);
   362    362   
   363         -#if 0
   364    363     if( *pRc==0 ){
   365    364       int iKey1;
   366    365       int iKey2;
   367    366       void *pKey1; int nKey1;       /* Start key */
   368    367       void *pKey2; int nKey2;       /* Final key */
   369    368   
   370    369       iKey1 = testPrngValue(iSeed) % nData;
................................................................................
   378    377       testScanCompare(pControl, pDb, 0, pKey1, nKey1, 0, 0,         pRc);
   379    378       testScanCompare(pControl, pDb, 0, pKey1, nKey1, pKey2, nKey2, pRc);
   380    379       testScanCompare(pControl, pDb, 1, 0, 0,         pKey2, nKey2, pRc);
   381    380       testScanCompare(pControl, pDb, 1, pKey1, nKey1, 0, 0,         pRc);
   382    381       testScanCompare(pControl, pDb, 1, pKey1, nKey1, pKey2, nKey2, pRc);
   383    382       testFree(pKey1);
   384    383     }
   385         -#endif
   386    384   
   387    385     for(i=0; i<nData && *pRc==0; i++){
   388    386       void *pKey; int nKey;
   389    387       testDatasourceEntry(pData, i, &pKey, &nKey, 0, 0);
   390    388       testFetchCompare(pControl, pDb, pKey, nKey, pRc);
   391    389     }
   392    390   }
................................................................................
   424    422       pKey1 = testMallocCopy(pKey1, nKey1);
   425    423       testDatasourceEntry(pData, i+2000000, &pKey2, &nKey2, 0, 0);
   426    424   
   427    425       testDeleteRange(pDb, pKey1, nKey1, pKey2, nKey2, &rc);
   428    426       testDeleteRange(pControl, pKey1, nKey1, pKey2, nKey2, &rc);
   429    427       testFree(pKey1);
   430    428   
   431         -#if 0
   432    429       testCompareDb(pData, (p->nIter*p->nWrite), i, pControl, pDb, &rc);
   433         -#endif
   434    430       testReopen(&pDb, &rc);
   435    431       testCompareDb(pData, (p->nIter*p->nWrite), i, pControl, pDb, &rc);
   436    432   
   437    433       /* Update the progress dots... */
   438    434       testCaseProgress(i, p->nIter, testCaseNDot(), &iDot);
   439    435     }
   440    436   

Changes to src/lsm_sorted.c.

  1310   1310       if( eSeek==LSM_SEEK_GE ) return (res<=0);
  1311   1311     }
  1312   1312   
  1313   1313     return 1;
  1314   1314   }
  1315   1315   #endif
  1316   1316   
  1317         -int segmentPtrSeek(
         1317  +static int segmentPtrSearchOversized(
  1318   1318     MultiCursor *pCsr,              /* Cursor context */
  1319   1319     SegmentPtr *pPtr,               /* Pointer to seek */
  1320         -  void *pKey, int nKey,           /* Key to seek to */
  1321         -  int eSeek,                      /* Search bias - see above */
  1322         -  int *piPtr,                     /* OUT: FC pointer */
  1323         -  int *pbStop
         1320  +  void *pKey, int nKey            /* Key to seek to */
  1324   1321   ){
  1325   1322     int (*xCmp)(void *, int, void *, int) = pCsr->pDb->xCmp;
  1326         -  int res;                        /* Result of comparison operation */
  1327   1323     int rc = LSM_OK;
  1328         -  int iMin;
  1329         -  int iMax;
  1330         -  int iPtrOut = 0;
  1331         -
  1332         -  const int iTopic = 0;
  1333         -
  1334         -#if 0
  1335         -static int nCall = 0;
  1336         -nCall++;
  1337         -printf("in call %d\n", nCall);
  1338         -fflush(stdout);
  1339         -#endif
  1340   1324   
  1341   1325     /* If the OVERSIZED flag is set, then there is no pointer in the
  1342   1326     ** upper level to the next page in the segment that contains at least
  1343   1327     ** one key. So compare the largest key on the current page with the
  1344   1328     ** key being sought (pKey/nKey). If (pKey/nKey) is larger, advance
  1345   1329     ** to the next page in the segment that contains at least one key. 
  1346   1330     */
................................................................................
  1356   1340           pPtr->pPg, pPtr->nCell-1, &iLastTopic, &nLastKey, &pPtr->blob1
  1357   1341       );
  1358   1342   
  1359   1343       /* If the loaded key is >= than (pKey/nKey), break out of the loop.
  1360   1344       ** If (pKey/nKey) is present in this array, it must be on the current 
  1361   1345       ** page.  */
  1362   1346       res = sortedKeyCompare(
  1363         -        xCmp, iLastTopic, pLastKey, nLastKey, iTopic, pKey, nKey
         1347  +        xCmp, iLastTopic, pLastKey, nLastKey, 0, pKey, nKey
  1364   1348       );
  1365   1349       if( res>=0 ) break;
  1366   1350   
  1367   1351       /* Advance to the next page that contains at least one key. */
  1368   1352       pNext = pPtr->pPg;
  1369   1353       lsmFsPageRef(pNext);
  1370   1354       while( 1 ){
................................................................................
  1387   1371       if( pNext==0 ) break;
  1388   1372       segmentPtrSetPage(pPtr, pNext);
  1389   1373   
  1390   1374       /* This should probably be an LSM_CORRUPT error. */
  1391   1375       assert( rc!=LSM_OK || (pPtr->flags & PGFTR_SKIP_THIS_FLAG) );
  1392   1376     }
  1393   1377   
         1378  +  return rc;
         1379  +}
         1380  +
         1381  +static int ptrFwdPointer(
         1382  +  Page *pPage,
         1383  +  int iCell,
         1384  +  Segment *pSeg,
         1385  +  Pgno *piPtr,
         1386  +  int *pbFound
         1387  +){
         1388  +  Page *pPg = pPage;
         1389  +  int iFirst = iCell;
         1390  +  int rc = LSM_OK;
         1391  +
         1392  +  do {
         1393  +    Page *pNext = 0;
         1394  +    u8 *aData;
         1395  +    int nData;
         1396  +
         1397  +    aData = lsmFsPageData(pPg, &nData);
         1398  +    if( (pageGetFlags(aData, nData) & SEGMENT_BTREE_FLAG)==0 ){
         1399  +      int i;
         1400  +      int nCell = pageGetNRec(aData, nData);
         1401  +      for(i=iFirst; i<nCell; i++){
         1402  +        u8 eType = *pageGetCell(aData, nData, i);
         1403  +        if( (eType & LSM_START_DELETE)==0 ){
         1404  +          *pbFound = 1;
         1405  +          *piPtr = pageGetRecordPtr(aData, nData, i) + pageGetPtr(aData, nData);
         1406  +          lsmFsPageRelease(pPg);
         1407  +          return LSM_OK;
         1408  +        }
         1409  +      }
         1410  +    }
         1411  +
         1412  +    rc = lsmFsDbPageNext(pSeg, pPg, 1, &pNext);
         1413  +    lsmFsPageRelease(pPg);
         1414  +    pPg = pNext;
         1415  +    iFirst = 0;
         1416  +  }while( pPg && rc==LSM_OK );
         1417  +  lsmFsPageRelease(pPg);
         1418  +
         1419  +  *pbFound = 0;
         1420  +  return rc;
         1421  +}
         1422  +
         1423  +static int sortedRhsFirst(MultiCursor *pCsr, Level *pLvl, SegmentPtr *pPtr){
         1424  +  int rc;
         1425  +  rc = segmentPtrEnd(pCsr, pPtr, 0);
         1426  +  while( pPtr->pPg && rc==LSM_OK ){
         1427  +    int res = sortedKeyCompare(pCsr->pDb->xCmp,
         1428  +        pLvl->iSplitTopic, pLvl->pSplitKey, pLvl->nSplitKey,
         1429  +        rtTopic(pPtr->eType), pPtr->pKey, pPtr->nKey
         1430  +    );
         1431  +    if( res<=0 ) break;
         1432  +    rc = segmentPtrAdvance(pCsr, pPtr, 0);
         1433  +  }
         1434  +  return rc;
         1435  +}
         1436  +
         1437  +
         1438  +/*
         1439  +** This function is called as part of a SEEK_GE op on a multi-cursor if the 
         1440  +** FC pointer read from segment *pPtr comes from an entry with the 
         1441  +** LSM_START_DELETE flag set. In this case the pointer value cannot be 
         1442  +** trusted. Instead, the pointer that should be followed is that associated
         1443  +** with the next entry in *pPtr that does not have LSM_START_DELETE set.
         1444  +**
         1445  +** Why the pointers can't be trusted:
         1446  +**
         1447  +**
         1448  +**
         1449  +** TODO: This is a stop-gap solution:
         1450  +** 
         1451  +**   At the moment, this function is called from within segmentPtrSeek(), 
         1452  +**   as part of the initial lsmMCursorSeek() call. However, consider a 
         1453  +**   database where the following has occurred:
         1454  +**
         1455  +**      1. A range delete removes keys 1..9999 using a range delete.
         1456  +**      2. Keys 1 through 9999 are reinserted.
         1457  +**      3. The levels containing the ops in 1. and 2. above are merged. Call
         1458  +**         this level N. Level N contains FC pointers to level N+1.
         1459  +**
         1460  +**   Then, if the user attempts to query for (key>=2 LIMIT 10), the 
         1461  +**   lsmMCursorSeek() call will iterate through 9998 entries searching for a 
         1462  +**   pointer down to the level N+1 that is never actually used. It would be
         1463  +**   much better if the multi-cursor could do this lazily - only seek to the
         1464  +**   level (N+1) page after the user has moved the cursor on level N passed
         1465  +**   the big range-delete.
         1466  +*/
         1467  +static int segmentPtrFwdPointer(
         1468  +  MultiCursor *pCsr,              /* Multi-cursor pPtr belongs to */
         1469  +  SegmentPtr *pPtr,               /* Segment-pointer to extract FC ptr from */
         1470  +  Pgno *piPtr                     /* OUT: FC pointer value */
         1471  +){
         1472  +  Level *pLvl = pPtr->pLevel;
         1473  +  Level *pNext = pLvl->pNext;
         1474  +  Page *pPg = pPtr->pPg;
         1475  +  int rc;
         1476  +  int bFound;
         1477  +  Pgno iOut = 0;
         1478  +
         1479  +  if( pPtr->pSeg==&pLvl->lhs || pPtr->pSeg==&pLvl->aRhs[pLvl->nRight-1] ){
         1480  +    if( pNext==0 
         1481  +        || (pNext->nRight==0 && pNext->lhs.iRoot)
         1482  +        || (pNext->nRight!=0 && pNext->aRhs[0].iRoot)
         1483  +      ){
         1484  +      /* Do nothing. The pointer will not be used anyway. */
         1485  +      return LSM_OK;
         1486  +    }
         1487  +  }else{
         1488  +    if( pPtr[1].pSeg->iRoot ){
         1489  +      return LSM_OK;
         1490  +    }
         1491  +  }
         1492  +
         1493  +  /* Search for a pointer within the current segment. */
         1494  +  lsmFsPageRef(pPg);
         1495  +  rc = ptrFwdPointer(pPg, pPtr->iCell, pPtr->pSeg, &iOut, &bFound);
         1496  +
         1497  +  if( rc==LSM_OK && bFound==0 ){
         1498  +    /* This case happens when pPtr points to the left-hand-side of a segment
         1499  +    ** currently undergoing an incremental merge. In this case, jump to the
         1500  +    ** oldest segment in the right-hand-side of the same level and continue
         1501  +    ** searching. But - do not consider any keys smaller than the levels
         1502  +    ** split-key. */
         1503  +    SegmentPtr ptr;
         1504  +
         1505  +    if( pPtr->pLevel->nRight==0 || pPtr->pSeg!=&pPtr->pLevel->lhs ){
         1506  +      return LSM_CORRUPT_BKPT;
         1507  +    }
         1508  +
         1509  +    memset(&ptr, 0, sizeof(SegmentPtr));
         1510  +    ptr.pLevel = pPtr->pLevel;
         1511  +    ptr.pSeg = &ptr.pLevel->aRhs[ptr.pLevel->nRight-1];
         1512  +    rc = sortedRhsFirst(pCsr, ptr.pLevel, &ptr);
         1513  +    if( rc==LSM_OK ){
         1514  +      rc = ptrFwdPointer(ptr.pPg, ptr.iCell, ptr.pSeg, &iOut, &bFound);
         1515  +      ptr.pPg = 0;
         1516  +    }
         1517  +    segmentPtrReset(&ptr);
         1518  +  }
         1519  +
         1520  +  *piPtr = iOut;
         1521  +  return rc;
         1522  +}
         1523  +
         1524  +static int segmentPtrSeek(
         1525  +  MultiCursor *pCsr,              /* Cursor context */
         1526  +  SegmentPtr *pPtr,               /* Pointer to seek */
         1527  +  void *pKey, int nKey,           /* Key to seek to */
         1528  +  int eSeek,                      /* Search bias - see above */
         1529  +  int *piPtr,                     /* OUT: FC pointer */
         1530  +  int *pbStop
         1531  +){
         1532  +  int (*xCmp)(void *, int, void *, int) = pCsr->pDb->xCmp;
         1533  +  int res;                        /* Result of comparison operation */
         1534  +  int rc = LSM_OK;
         1535  +  int iMin;
         1536  +  int iMax;
         1537  +  int iPtrOut = 0;
         1538  +  const int iTopic = 0;
         1539  +
         1540  +  /* If the current page contains an oversized entry, then there are no
         1541  +  ** pointers to one or more of the subsequent pages in the sorted run.
         1542  +  ** The following call ensures that the segment-ptr points to the correct 
         1543  +  ** page in this case.  */
         1544  +  rc = segmentPtrSearchOversized(pCsr, pPtr, pKey, nKey);
  1394   1545     iPtrOut = pPtr->iPtr;
  1395   1546   
  1396   1547     /* Assert that this page is the right page of this segment for the key
  1397   1548     ** that we are searching for. Do this by loading page (iPg-1) and testing
  1398   1549     ** that pKey/nKey is greater than all keys on that page, and then by 
  1399   1550     ** loading (iPg+1) and testing that pKey/nKey is smaller than all
  1400         -  ** the keys it houses.  */
         1551  +  ** the keys it houses.  
         1552  +  **
         1553  +  ** TODO: With range-deletes in the tree, the test described above may fail.
         1554  +  */
  1401   1555   #if 0
  1402   1556     assert( assertKeyLocation(pCsr, pPtr, pKey, nKey) );
  1403   1557   #endif
  1404   1558   
  1405   1559     assert( pPtr->nCell>0 
  1406   1560          || pPtr->pSeg->nSize==1 
  1407   1561          || lsmFsPageNumber(pPtr->pPg)==pPtr->pSeg->iLast
................................................................................
  1440   1594       }
  1441   1595   
  1442   1596       if( rc==LSM_OK ){
  1443   1597         assert( res==0 || (iMin==iMax && iMin>=0 && iMin<pPtr->nCell) );
  1444   1598         if( res ){
  1445   1599           rc = segmentPtrLoadCell(pPtr, iMin);
  1446   1600         }
         1601  +      assert( rc!=LSM_OK || res>0 || iPtrOut==(pPtr->iPtr + pPtr->iPgPtr) );
  1447   1602   
  1448   1603         if( rc==LSM_OK ){
  1449   1604           switch( eSeek ){
  1450   1605             case LSM_SEEK_EQ: {
  1451   1606               int eType = pPtr->eType;
  1452   1607               if( (res<0 && (eType & LSM_START_DELETE))
  1453   1608                || (res>0 && (eType & LSM_END_DELETE))
................................................................................
  1466   1621               }
  1467   1622               segmentPtrReset(pPtr);
  1468   1623               break;
  1469   1624             }
  1470   1625             case LSM_SEEK_LE:
  1471   1626               if( res>0 ) rc = segmentPtrAdvance(pCsr, pPtr, 1);
  1472   1627               break;
  1473         -          case LSM_SEEK_GE:
  1474         -            if( res<0 ) rc = segmentPtrAdvance(pCsr, pPtr, 0);
         1628  +          case LSM_SEEK_GE: {
         1629  +            /* Figure out if we need to 'skip' the pointer forward or not */
         1630  +            if( (res<=0 && (pPtr->eType & LSM_START_DELETE)) 
         1631  +             || (res>0  && (pPtr->eType & LSM_END_DELETE)) 
         1632  +            ){
         1633  +              rc = segmentPtrFwdPointer(pCsr, pPtr, &iPtrOut);
         1634  +            }
         1635  +            if( res<0 && rc==LSM_OK ){
         1636  +              rc = segmentPtrAdvance(pCsr, pPtr, 0);
         1637  +            }
  1475   1638               break;
         1639  +          }
  1476   1640           }
  1477   1641         }
  1478   1642       }
  1479   1643   
  1480   1644       /* If the cursor seek has found a separator key, and this cursor is
  1481   1645       ** supposed to ignore separators keys, advance to the next entry.  */
  1482   1646       if( rc==LSM_OK && pPtr->pPg
................................................................................
  1808   1972         iRes = i2;
  1809   1973       }
  1810   1974     }
  1811   1975   
  1812   1976     pCsr->aTree[iOut] = iRes;
  1813   1977   }
  1814   1978   
  1815         -static int sortedRhsFirst(MultiCursor *pCsr, Level *pLvl, SegmentPtr *pPtr){
  1816         -  int rc;
  1817         -  rc = segmentPtrEnd(pCsr, pPtr, 0);
  1818         -  while( pPtr->pPg && rc==LSM_OK ){
  1819         -    int res = sortedKeyCompare(pCsr->pDb->xCmp,
  1820         -        pLvl->iSplitTopic, pLvl->pSplitKey, pLvl->nSplitKey,
  1821         -        rtTopic(pPtr->eType), pPtr->pKey, pPtr->nKey
  1822         -    );
  1823         -    if( res<=0 ) break;
  1824         -    rc = segmentPtrAdvance(pCsr, pPtr, 0);
  1825         -  }
  1826         -  return rc;
  1827         -}
  1828         -
  1829   1979   /*
  1830   1980   ** This function advances segment pointer iPtr belonging to multi-cursor
  1831   1981   ** pCsr forward (bReverse==0) or backward (bReverse!=0).
  1832   1982   **
  1833   1983   ** If the segment pointer points to a segment that is part of a composite
  1834   1984   ** level, then the following special cases are handled.
  1835   1985   **
................................................................................
  2557   2707     if( lsmMCursorValid(pCsr) ){
  2558   2708       do {
  2559   2709         int iKey = pCsr->aTree[1];
  2560   2710   
  2561   2711         /* If this multi-cursor is advancing forwards, and the sub-cursor
  2562   2712         ** being advanced is the one that separator keys may be being read
  2563   2713         ** from, record the current absolute pointer value.  */
  2564         -      if( pCsr->pPrevMergePtr 
  2565         -       && iKey>=CURSOR_DATA_SEGMENT
  2566         -       && iKey==(CURSOR_DATA_SEGMENT+pCsr->nPtr-(!pCsr->pBtCsr)) 
  2567         -      ){
         2714  +      if( pCsr->pPrevMergePtr ){
  2568   2715           if( iKey==(CURSOR_DATA_SEGMENT+pCsr->nPtr) ){
         2716  +          assert( pCsr->pBtCsr );
  2569   2717             *pCsr->pPrevMergePtr = pCsr->pBtCsr->iPtr;
  2570         -        }else{
         2718  +        }else if( pCsr->pBtCsr==0 && pCsr->nPtr>0
         2719  +               && iKey==(CURSOR_DATA_SEGMENT+pCsr->nPtr-1) 
         2720  +        ){
  2571   2721             SegmentPtr *pPtr = &pCsr->aPtr[iKey-CURSOR_DATA_SEGMENT];
  2572   2722             *pCsr->pPrevMergePtr = pPtr->iPtr+pPtr->iPgPtr;
  2573   2723           }
  2574   2724         }
  2575   2725   
  2576   2726         if( iKey==CURSOR_DATA_TREE0 || iKey==CURSOR_DATA_TREE1 ){
  2577   2727           TreeCursor *pTreeCsr = pCsr->apTreeCsr[iKey-CURSOR_DATA_TREE0];
................................................................................
  3165   3315     pSeg = &pMW->pLevel->lhs;
  3166   3316   
  3167   3317     pPg = pMW->pPage;
  3168   3318     aData = fsPageData(pPg, &nData);
  3169   3319     nRec = pageGetNRec(aData, nData);
  3170   3320     iFPtr = pageGetPtr(aData, nData);
  3171   3321   
  3172         -  /* If iPtr is 0, set it to the same value as the absolute pointer 
  3173         -  ** stored as part of the previous record.  */
  3174         -  if( 0 && iPtr==0 ){
  3175         -    iPtr = iFPtr;
  3176         -    if( nRec ) iPtr += pageGetRecordPtr(aData, nData, nRec-1);
  3177         -  }
  3178         -
  3179   3322     /* Calculate the relative pointer value to write to this record */
  3180   3323     iRPtr = iPtr - iFPtr;
  3181   3324     /* assert( iRPtr>=0 ); */
  3182   3325        
  3183   3326     /* Figure out how much space is required by the new record. The space
  3184   3327     ** required is divided into two sections: the header and the body. The
  3185   3328     ** header consists of the intial varint fields. The body are the blobs 
................................................................................
  3554   3697     int *pnWrite                    /* OUT: Number of database pages written */
  3555   3698   ){
  3556   3699     int rc = LSM_OK;                /* Return Code */
  3557   3700     MultiCursor *pCsr = 0;
  3558   3701     Level *pNext = 0;               /* The current top level */
  3559   3702     Level *pNew;                    /* The new level itself */
  3560   3703     Segment *pDel = 0;              /* Delete separators from this segment */
  3561         -  int iLeftPtr = 0;
         3704  +  Pgno iLeftPtr = 0;
  3562   3705     int nWrite = 0;                 /* Number of database pages written */
  3563   3706   
  3564   3707     assert( pnOvfl );
  3565   3708   
  3566   3709     /* Allocate the new level structure to write to. */
  3567   3710     pNext = lsmDbSnapshotLevel(pDb->pWorker);
  3568   3711     pNew = (Level *)lsmMallocZeroRc(pDb->pEnv, sizeof(Level), &rc);
................................................................................
  3585   3728         iLeftPtr = pNext->lhs.iFirst;
  3586   3729       }
  3587   3730     }
  3588   3731   
  3589   3732     if( rc!=LSM_OK ){
  3590   3733       lsmMCursorClose(pCsr);
  3591   3734     }else{
  3592         -    Pgno iCurrentPtr = 0;
  3593   3735       Merge merge;                  /* Merge object used to create new level */
  3594   3736       MergeWorker mergeworker;      /* MergeWorker object for the same purpose */
  3595   3737   
  3596   3738       memset(&merge, 0, sizeof(Merge));
  3597   3739       memset(&mergeworker, 0, sizeof(MergeWorker));
  3598   3740   
  3599   3741       pNew->pMerge = &merge;
  3600   3742       mergeworker.pDb = pDb;
  3601   3743       mergeworker.pLevel = pNew;
  3602   3744       mergeworker.pCsr = pCsr;
  3603         -    pCsr->pPrevMergePtr = &iCurrentPtr;
         3745  +    pCsr->pPrevMergePtr = &iLeftPtr;
  3604   3746   
  3605   3747       /* Mark the separators array for the new level as a "phantom". */
  3606   3748       mergeworker.bFlush = 1;
  3607   3749   
  3608   3750       /* Allocate the first page of the output segment. */
  3609   3751       rc = mergeWorkerNextPage(&mergeworker, iLeftPtr);
  3610   3752