| Index: third_party/sqlite/sqlite-src-3100200/src/vdbesort.c
|
| diff --git a/third_party/sqlite/sqlite-src-3080704/src/vdbesort.c b/third_party/sqlite/sqlite-src-3100200/src/vdbesort.c
|
| similarity index 87%
|
| copy from third_party/sqlite/sqlite-src-3080704/src/vdbesort.c
|
| copy to third_party/sqlite/sqlite-src-3100200/src/vdbesort.c
|
| index 46c9f3789de323f2dbadda1fe902bf00f282a617..54e538fd50fda188d6d927311e486eaacf1ed25c 100644
|
| --- a/third_party/sqlite/sqlite-src-3080704/src/vdbesort.c
|
| +++ b/third_party/sqlite/sqlite-src-3100200/src/vdbesort.c
|
| @@ -100,7 +100,7 @@
|
| ** The sorter is running in multi-threaded mode if (a) the library was built
|
| ** with pre-processor symbol SQLITE_MAX_WORKER_THREADS set to a value greater
|
| ** than zero, and (b) worker threads have been enabled at runtime by calling
|
| -** sqlite3_config(SQLITE_CONFIG_WORKER_THREADS, ...).
|
| +** "PRAGMA threads=N" with some value of N greater than 0.
|
| **
|
| ** When Rewind() is called, any data remaining in memory is flushed to a
|
| ** final PMA. So at this point the data is stored in some number of sorted
|
| @@ -148,6 +148,13 @@
|
| #endif
|
|
|
| /*
|
| +** Hard-coded maximum amount of data to accumulate in memory before flushing
|
| +** to a level 0 PMA. The purpose of this limit is to prevent various integer
|
| +** overflows. 512MiB.
|
| +*/
|
| +#define SQLITE_MAX_PMASZ (1<<29)
|
| +
|
| +/*
|
| ** Private objects used by the sorter
|
| */
|
| typedef struct MergeEngine MergeEngine; /* Merge PMAs together */
|
| @@ -284,6 +291,7 @@ struct MergeEngine {
|
| ** after the thread has finished are not dire. So we don't worry about
|
| ** memory barriers and such here.
|
| */
|
| +typedef int (*SorterCompare)(SortSubtask*,int*,const void*,int,const void*,int);
|
| struct SortSubtask {
|
| SQLiteThread *pThread; /* Background thread, if any */
|
| int bDone; /* Set if thread is finished but not joined */
|
| @@ -291,10 +299,12 @@ struct SortSubtask {
|
| UnpackedRecord *pUnpacked; /* Space to unpack a record */
|
| SorterList list; /* List for thread to write to a PMA */
|
| int nPMA; /* Number of PMAs currently in file */
|
| + SorterCompare xCompare; /* Compare function to use */
|
| SorterFile file; /* Temp file for level-0 PMAs */
|
| SorterFile file2; /* Space for other PMAs */
|
| };
|
|
|
| +
|
| /*
|
| ** Main sorter structure. A single instance of this is allocated for each
|
| ** sorter cursor created by the VDBE.
|
| @@ -321,9 +331,13 @@ struct VdbeSorter {
|
| u8 bUseThreads; /* True to use background threads */
|
| u8 iPrev; /* Previous thread used to flush PMA */
|
| u8 nTask; /* Size of aTask[] array */
|
| + u8 typeMask;
|
| SortSubtask aTask[1]; /* One or more subtasks */
|
| };
|
|
|
| +#define SORTER_TYPE_INTEGER 0x01
|
| +#define SORTER_TYPE_TEXT 0x02
|
| +
|
| /*
|
| ** An instance of the following object is used to read records out of a
|
| ** PMA, in sorted order. The next key to be read is cached in nKey/aKey.
|
| @@ -441,9 +455,6 @@ struct SorterRecord {
|
| */
|
| #define SRVAL(p) ((void*)((SorterRecord*)(p) + 1))
|
|
|
| -/* The minimum PMA size is set to this value multiplied by the database
|
| -** page size in bytes. */
|
| -#define SORTER_MIN_WORKING 10
|
|
|
| /* Maximum number of PMAs that a single MergeEngine can merge */
|
| #define SORTER_MAX_MERGE_COUNT 16
|
| @@ -738,33 +749,163 @@ static int vdbePmaReaderInit(
|
| return rc;
|
| }
|
|
|
| +/*
|
| +** A version of vdbeSorterCompare() that assumes that it has already been
|
| +** determined that the first field of key1 is equal to the first field of
|
| +** key2.
|
| +*/
|
| +static int vdbeSorterCompareTail(
|
| + SortSubtask *pTask, /* Subtask context (for pKeyInfo) */
|
| + int *pbKey2Cached, /* True if pTask->pUnpacked is pKey2 */
|
| + const void *pKey1, int nKey1, /* Left side of comparison */
|
| + const void *pKey2, int nKey2 /* Right side of comparison */
|
| +){
|
| + UnpackedRecord *r2 = pTask->pUnpacked;
|
| + if( *pbKey2Cached==0 ){
|
| + sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2);
|
| + *pbKey2Cached = 1;
|
| + }
|
| + return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, r2, 1);
|
| +}
|
|
|
| /*
|
| ** Compare key1 (buffer pKey1, size nKey1 bytes) with key2 (buffer pKey2,
|
| ** size nKey2 bytes). Use (pTask->pKeyInfo) for the collation sequences
|
| ** used by the comparison. Return the result of the comparison.
|
| **
|
| -** Before returning, object (pTask->pUnpacked) is populated with the
|
| -** unpacked version of key2. Or, if pKey2 is passed a NULL pointer, then it
|
| -** is assumed that the (pTask->pUnpacked) structure already contains the
|
| -** unpacked key to use as key2.
|
| +** If IN/OUT parameter *pbKey2Cached is true when this function is called,
|
| +** it is assumed that (pTask->pUnpacked) contains the unpacked version
|
| +** of key2. If it is false, (pTask->pUnpacked) is populated with the unpacked
|
| +** version of key2 and *pbKey2Cached set to true before returning.
|
| **
|
| ** If an OOM error is encountered, (pTask->pUnpacked->error_rc) is set
|
| ** to SQLITE_NOMEM.
|
| */
|
| static int vdbeSorterCompare(
|
| SortSubtask *pTask, /* Subtask context (for pKeyInfo) */
|
| + int *pbKey2Cached, /* True if pTask->pUnpacked is pKey2 */
|
| const void *pKey1, int nKey1, /* Left side of comparison */
|
| const void *pKey2, int nKey2 /* Right side of comparison */
|
| ){
|
| UnpackedRecord *r2 = pTask->pUnpacked;
|
| - if( pKey2 ){
|
| + if( !*pbKey2Cached ){
|
| sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2);
|
| + *pbKey2Cached = 1;
|
| }
|
| return sqlite3VdbeRecordCompare(nKey1, pKey1, r2);
|
| }
|
|
|
| /*
|
| +** A specially optimized version of vdbeSorterCompare() that assumes that
|
| +** the first field of each key is a TEXT value and that the collation
|
| +** sequence to compare them with is BINARY.
|
| +*/
|
| +static int vdbeSorterCompareText(
|
| + SortSubtask *pTask, /* Subtask context (for pKeyInfo) */
|
| + int *pbKey2Cached, /* True if pTask->pUnpacked is pKey2 */
|
| + const void *pKey1, int nKey1, /* Left side of comparison */
|
| + const void *pKey2, int nKey2 /* Right side of comparison */
|
| +){
|
| + const u8 * const p1 = (const u8 * const)pKey1;
|
| + const u8 * const p2 = (const u8 * const)pKey2;
|
| + const u8 * const v1 = &p1[ p1[0] ]; /* Pointer to value 1 */
|
| + const u8 * const v2 = &p2[ p2[0] ]; /* Pointer to value 2 */
|
| +
|
| + int n1;
|
| + int n2;
|
| + int res;
|
| +
|
| + getVarint32(&p1[1], n1); n1 = (n1 - 13) / 2;
|
| + getVarint32(&p2[1], n2); n2 = (n2 - 13) / 2;
|
| + res = memcmp(v1, v2, MIN(n1, n2));
|
| + if( res==0 ){
|
| + res = n1 - n2;
|
| + }
|
| +
|
| + if( res==0 ){
|
| + if( pTask->pSorter->pKeyInfo->nField>1 ){
|
| + res = vdbeSorterCompareTail(
|
| + pTask, pbKey2Cached, pKey1, nKey1, pKey2, nKey2
|
| + );
|
| + }
|
| + }else{
|
| + if( pTask->pSorter->pKeyInfo->aSortOrder[0] ){
|
| + res = res * -1;
|
| + }
|
| + }
|
| +
|
| + return res;
|
| +}
|
| +
|
| +/*
|
| +** A specially optimized version of vdbeSorterCompare() that assumes that
|
| +** the first field of each key is an INTEGER value.
|
| +*/
|
| +static int vdbeSorterCompareInt(
|
| + SortSubtask *pTask, /* Subtask context (for pKeyInfo) */
|
| + int *pbKey2Cached, /* True if pTask->pUnpacked is pKey2 */
|
| + const void *pKey1, int nKey1, /* Left side of comparison */
|
| + const void *pKey2, int nKey2 /* Right side of comparison */
|
| +){
|
| + const u8 * const p1 = (const u8 * const)pKey1;
|
| + const u8 * const p2 = (const u8 * const)pKey2;
|
| + const int s1 = p1[1]; /* Left hand serial type */
|
| + const int s2 = p2[1]; /* Right hand serial type */
|
| + const u8 * const v1 = &p1[ p1[0] ]; /* Pointer to value 1 */
|
| + const u8 * const v2 = &p2[ p2[0] ]; /* Pointer to value 2 */
|
| + int res; /* Return value */
|
| +
|
| + assert( (s1>0 && s1<7) || s1==8 || s1==9 );
|
| + assert( (s2>0 && s2<7) || s2==8 || s2==9 );
|
| +
|
| + if( s1>7 && s2>7 ){
|
| + res = s1 - s2;
|
| + }else{
|
| + if( s1==s2 ){
|
| + if( (*v1 ^ *v2) & 0x80 ){
|
| + /* The two values have different signs */
|
| + res = (*v1 & 0x80) ? -1 : +1;
|
| + }else{
|
| + /* The two values have the same sign. Compare using memcmp(). */
|
| + static const u8 aLen[] = {0, 1, 2, 3, 4, 6, 8 };
|
| + int i;
|
| + res = 0;
|
| + for(i=0; i<aLen[s1]; i++){
|
| + if( (res = v1[i] - v2[i]) ) break;
|
| + }
|
| + }
|
| + }else{
|
| + if( s2>7 ){
|
| + res = +1;
|
| + }else if( s1>7 ){
|
| + res = -1;
|
| + }else{
|
| + res = s1 - s2;
|
| + }
|
| + assert( res!=0 );
|
| +
|
| + if( res>0 ){
|
| + if( *v1 & 0x80 ) res = -1;
|
| + }else{
|
| + if( *v2 & 0x80 ) res = +1;
|
| + }
|
| + }
|
| + }
|
| +
|
| + if( res==0 ){
|
| + if( pTask->pSorter->pKeyInfo->nField>1 ){
|
| + res = vdbeSorterCompareTail(
|
| + pTask, pbKey2Cached, pKey1, nKey1, pKey2, nKey2
|
| + );
|
| + }
|
| + }else if( pTask->pSorter->pKeyInfo->aSortOrder[0] ){
|
| + res = res * -1;
|
| + }
|
| +
|
| + return res;
|
| +}
|
| +
|
| +/*
|
| ** Initialize the temporary index cursor just opened as a sorter cursor.
|
| **
|
| ** Usually, the sorter module uses the value of (pCsr->pKeyInfo->nField)
|
| @@ -820,20 +961,25 @@ int sqlite3VdbeSorterInit(
|
| #endif
|
|
|
| assert( pCsr->pKeyInfo && pCsr->pBt==0 );
|
| + assert( pCsr->eCurType==CURTYPE_SORTER );
|
| szKeyInfo = sizeof(KeyInfo) + (pCsr->pKeyInfo->nField-1)*sizeof(CollSeq*);
|
| sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask);
|
|
|
| pSorter = (VdbeSorter*)sqlite3DbMallocZero(db, sz + szKeyInfo);
|
| - pCsr->pSorter = pSorter;
|
| + pCsr->uc.pSorter = pSorter;
|
| if( pSorter==0 ){
|
| rc = SQLITE_NOMEM;
|
| }else{
|
| pSorter->pKeyInfo = pKeyInfo = (KeyInfo*)((u8*)pSorter + sz);
|
| memcpy(pKeyInfo, pCsr->pKeyInfo, szKeyInfo);
|
| pKeyInfo->db = 0;
|
| - if( nField && nWorker==0 ) pKeyInfo->nField = nField;
|
| + if( nField && nWorker==0 ){
|
| + pKeyInfo->nXField += (pKeyInfo->nField - nField);
|
| + pKeyInfo->nField = nField;
|
| + }
|
| pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(db->aDb[0].pBt);
|
| pSorter->nTask = nWorker + 1;
|
| + pSorter->iPrev = (u8)(nWorker - 1);
|
| pSorter->bUseThreads = (pSorter->nTask>1);
|
| pSorter->db = db;
|
| for(i=0; i<pSorter->nTask; i++){
|
| @@ -842,16 +988,15 @@ int sqlite3VdbeSorterInit(
|
| }
|
|
|
| if( !sqlite3TempInMemory(db) ){
|
| - pSorter->mnPmaSize = SORTER_MIN_WORKING * pgsz;
|
| + u32 szPma = sqlite3GlobalConfig.szPma;
|
| + pSorter->mnPmaSize = szPma * pgsz;
|
| mxCache = db->aDb[0].pSchema->cache_size;
|
| - if( mxCache<SORTER_MIN_WORKING ) mxCache = SORTER_MIN_WORKING;
|
| - pSorter->mxPmaSize = mxCache * pgsz;
|
| -
|
| - /* If the application has not configure scratch memory using
|
| - ** SQLITE_CONFIG_SCRATCH then we assume it is OK to do large memory
|
| - ** allocations. If scratch memory has been configured, then assume
|
| - ** large memory allocations should be avoided to prevent heap
|
| - ** fragmentation.
|
| + if( mxCache<(int)szPma ) mxCache = (int)szPma;
|
| + pSorter->mxPmaSize = MIN((i64)mxCache*pgsz, SQLITE_MAX_PMASZ);
|
| +
|
| + /* EVIDENCE-OF: R-26747-61719 When the application provides any amount of
|
| + ** scratch memory using SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary
|
| + ** large heap allocations.
|
| */
|
| if( sqlite3GlobalConfig.pScratch==0 ){
|
| assert( pSorter->iMemory==0 );
|
| @@ -860,6 +1005,12 @@ int sqlite3VdbeSorterInit(
|
| if( !pSorter->list.aMemory ) rc = SQLITE_NOMEM;
|
| }
|
| }
|
| +
|
| + if( (pKeyInfo->nField+pKeyInfo->nXField)<13
|
| + && (pKeyInfo->aColl[0]==0 || pKeyInfo->aColl[0]==db->pDfltColl)
|
| + ){
|
| + pSorter->typeMask = SORTER_TYPE_INTEGER | SORTER_TYPE_TEXT;
|
| + }
|
| }
|
|
|
| return rc;
|
| @@ -884,30 +1035,24 @@ static void vdbeSorterRecordFree(sqlite3 *db, SorterRecord *pRecord){
|
| */
|
| static void vdbeSortSubtaskCleanup(sqlite3 *db, SortSubtask *pTask){
|
| sqlite3DbFree(db, pTask->pUnpacked);
|
| - pTask->pUnpacked = 0;
|
| #if SQLITE_MAX_WORKER_THREADS>0
|
| /* pTask->list.aMemory can only be non-zero if it was handed memory
|
| ** from the main thread. That only occurs SQLITE_MAX_WORKER_THREADS>0 */
|
| if( pTask->list.aMemory ){
|
| sqlite3_free(pTask->list.aMemory);
|
| - pTask->list.aMemory = 0;
|
| }else
|
| #endif
|
| {
|
| assert( pTask->list.aMemory==0 );
|
| vdbeSorterRecordFree(0, pTask->list.pList);
|
| }
|
| - pTask->list.pList = 0;
|
| if( pTask->file.pFd ){
|
| sqlite3OsCloseFree(pTask->file.pFd);
|
| - pTask->file.pFd = 0;
|
| - pTask->file.iEof = 0;
|
| }
|
| if( pTask->file2.pFd ){
|
| sqlite3OsCloseFree(pTask->file2.pFd);
|
| - pTask->file2.pFd = 0;
|
| - pTask->file2.iEof = 0;
|
| }
|
| + memset(pTask, 0, sizeof(SortSubtask));
|
| }
|
|
|
| #ifdef SQLITE_DEBUG_SORTER_THREADS
|
| @@ -1087,6 +1232,7 @@ void sqlite3VdbeSorterReset(sqlite3 *db, VdbeSorter *pSorter){
|
| for(i=0; i<pSorter->nTask; i++){
|
| SortSubtask *pTask = &pSorter->aTask[i];
|
| vdbeSortSubtaskCleanup(db, pTask);
|
| + pTask->pSorter = pSorter;
|
| }
|
| if( pSorter->list.aMemory==0 ){
|
| vdbeSorterRecordFree(0, pSorter->list.pList);
|
| @@ -1104,12 +1250,14 @@ void sqlite3VdbeSorterReset(sqlite3 *db, VdbeSorter *pSorter){
|
| ** Free any cursor components allocated by sqlite3VdbeSorterXXX routines.
|
| */
|
| void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){
|
| - VdbeSorter *pSorter = pCsr->pSorter;
|
| + VdbeSorter *pSorter;
|
| + assert( pCsr->eCurType==CURTYPE_SORTER );
|
| + pSorter = pCsr->uc.pSorter;
|
| if( pSorter ){
|
| sqlite3VdbeSorterReset(db, pSorter);
|
| sqlite3_free(pSorter->list.aMemory);
|
| sqlite3DbFree(db, pSorter);
|
| - pCsr->pSorter = 0;
|
| + pCsr->uc.pSorter = 0;
|
| }
|
| }
|
|
|
| @@ -1125,12 +1273,12 @@ void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){
|
| */
|
| static void vdbeSorterExtendFile(sqlite3 *db, sqlite3_file *pFd, i64 nByte){
|
| if( nByte<=(i64)(db->nMaxSorterMmap) && pFd->pMethods->iVersion>=3 ){
|
| - int rc = sqlite3OsTruncate(pFd, nByte);
|
| - if( rc==SQLITE_OK ){
|
| - void *p = 0;
|
| - sqlite3OsFetch(pFd, 0, (int)nByte, &p);
|
| - sqlite3OsUnfetch(pFd, 0, p);
|
| - }
|
| + void *p = 0;
|
| + int chunksize = 4*1024;
|
| + sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_CHUNK_SIZE, &chunksize);
|
| + sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_SIZE_HINT, &nByte);
|
| + sqlite3OsFetch(pFd, 0, (int)nByte, &p);
|
| + sqlite3OsUnfetch(pFd, 0, p);
|
| }
|
| }
|
| #else
|
| @@ -1148,6 +1296,7 @@ static int vdbeSorterOpenTempFile(
|
| sqlite3_file **ppFd
|
| ){
|
| int rc;
|
| + if( sqlite3FaultSim(202) ) return SQLITE_IOERR_ACCESS;
|
| rc = sqlite3OsOpenMalloc(db->pVfs, 0, ppFd,
|
| SQLITE_OPEN_TEMP_JOURNAL |
|
| SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
|
| @@ -1195,22 +1344,23 @@ static void vdbeSorterMerge(
|
| ){
|
| SorterRecord *pFinal = 0;
|
| SorterRecord **pp = &pFinal;
|
| - void *pVal2 = p2 ? SRVAL(p2) : 0;
|
| + int bCached = 0;
|
|
|
| while( p1 && p2 ){
|
| int res;
|
| - res = vdbeSorterCompare(pTask, SRVAL(p1), p1->nVal, pVal2, p2->nVal);
|
| + res = pTask->xCompare(
|
| + pTask, &bCached, SRVAL(p1), p1->nVal, SRVAL(p2), p2->nVal
|
| + );
|
| +
|
| if( res<=0 ){
|
| *pp = p1;
|
| pp = &p1->u.pNext;
|
| p1 = p1->u.pNext;
|
| - pVal2 = 0;
|
| }else{
|
| *pp = p2;
|
| - pp = &p2->u.pNext;
|
| + pp = &p2->u.pNext;
|
| p2 = p2->u.pNext;
|
| - if( p2==0 ) break;
|
| - pVal2 = SRVAL(p2);
|
| + bCached = 0;
|
| }
|
| }
|
| *pp = p1 ? p1 : p2;
|
| @@ -1218,6 +1368,19 @@ static void vdbeSorterMerge(
|
| }
|
|
|
| /*
|
| +** Return the SorterCompare function to compare values collected by the
|
| +** sorter object passed as the only argument.
|
| +*/
|
| +static SorterCompare vdbeSorterGetCompare(VdbeSorter *p){
|
| + if( p->typeMask==SORTER_TYPE_INTEGER ){
|
| + return vdbeSorterCompareInt;
|
| + }else if( p->typeMask==SORTER_TYPE_TEXT ){
|
| + return vdbeSorterCompareText;
|
| + }
|
| + return vdbeSorterCompare;
|
| +}
|
| +
|
| +/*
|
| ** Sort the linked list of records headed at pTask->pList. Return
|
| ** SQLITE_OK if successful, or an SQLite error code (i.e. SQLITE_NOMEM) if
|
| ** an error occurs.
|
| @@ -1231,12 +1394,14 @@ static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){
|
| rc = vdbeSortAllocUnpacked(pTask);
|
| if( rc!=SQLITE_OK ) return rc;
|
|
|
| + p = pList->pList;
|
| + pTask->xCompare = vdbeSorterGetCompare(pTask->pSorter);
|
| +
|
| aSlot = (SorterRecord **)sqlite3MallocZero(64 * sizeof(SorterRecord *));
|
| if( !aSlot ){
|
| return SQLITE_NOMEM;
|
| }
|
|
|
| - p = pList->pList;
|
| while( p ){
|
| SorterRecord *pNext;
|
| if( pList->aMemory ){
|
| @@ -1450,13 +1615,12 @@ static int vdbeMergeEngineStep(
|
| int i; /* Index of aTree[] to recalculate */
|
| PmaReader *pReadr1; /* First PmaReader to compare */
|
| PmaReader *pReadr2; /* Second PmaReader to compare */
|
| - u8 *pKey2; /* To pReadr2->aKey, or 0 if record cached */
|
| + int bCached = 0;
|
|
|
| /* Find the first two PmaReaders to compare. The one that was just
|
| ** advanced (iPrev) and the one next to it in the array. */
|
| pReadr1 = &pMerger->aReadr[(iPrev & 0xFFFE)];
|
| pReadr2 = &pMerger->aReadr[(iPrev | 0x0001)];
|
| - pKey2 = pReadr2->aKey;
|
|
|
| for(i=(pMerger->nTree+iPrev)/2; i>0; i=i/2){
|
| /* Compare pReadr1 and pReadr2. Store the result in variable iRes. */
|
| @@ -1466,8 +1630,8 @@ static int vdbeMergeEngineStep(
|
| }else if( pReadr2->pFd==0 ){
|
| iRes = -1;
|
| }else{
|
| - iRes = vdbeSorterCompare(pTask,
|
| - pReadr1->aKey, pReadr1->nKey, pKey2, pReadr2->nKey
|
| + iRes = pTask->xCompare(pTask, &bCached,
|
| + pReadr1->aKey, pReadr1->nKey, pReadr2->aKey, pReadr2->nKey
|
| );
|
| }
|
|
|
| @@ -1489,9 +1653,9 @@ static int vdbeMergeEngineStep(
|
| if( iRes<0 || (iRes==0 && pReadr1<pReadr2) ){
|
| pMerger->aTree[i] = (int)(pReadr1 - pMerger->aReadr);
|
| pReadr2 = &pMerger->aReadr[ pMerger->aTree[i ^ 0x0001] ];
|
| - pKey2 = pReadr2->aKey;
|
| + bCached = 0;
|
| }else{
|
| - if( pReadr1->pFd ) pKey2 = 0;
|
| + if( pReadr1->pFd ) bCached = 0;
|
| pMerger->aTree[i] = (int)(pReadr2 - pMerger->aReadr);
|
| pReadr1 = &pMerger->aReadr[ pMerger->aTree[i ^ 0x0001] ];
|
| }
|
| @@ -1591,13 +1755,24 @@ int sqlite3VdbeSorterWrite(
|
| const VdbeCursor *pCsr, /* Sorter cursor */
|
| Mem *pVal /* Memory cell containing record */
|
| ){
|
| - VdbeSorter *pSorter = pCsr->pSorter;
|
| + VdbeSorter *pSorter;
|
| int rc = SQLITE_OK; /* Return Code */
|
| SorterRecord *pNew; /* New list element */
|
| -
|
| int bFlush; /* True to flush contents of memory to PMA */
|
| int nReq; /* Bytes of memory required */
|
| int nPMA; /* Bytes of PMA space required */
|
| + int t; /* serial type of first record field */
|
| +
|
| + assert( pCsr->eCurType==CURTYPE_SORTER );
|
| + pSorter = pCsr->uc.pSorter;
|
| + getVarint32((const u8*)&pVal->z[1], t);
|
| + if( t>0 && t<10 && t!=7 ){
|
| + pSorter->typeMask &= SORTER_TYPE_INTEGER;
|
| + }else if( t>10 && (t & 0x01) ){
|
| + pSorter->typeMask &= SORTER_TYPE_TEXT;
|
| + }else{
|
| + pSorter->typeMask = 0;
|
| + }
|
|
|
| assert( pSorter );
|
|
|
| @@ -1863,10 +2038,12 @@ static void vdbeMergeEngineCompare(
|
| }else if( p2->pFd==0 ){
|
| iRes = i1;
|
| }else{
|
| + SortSubtask *pTask = pMerger->pTask;
|
| + int bCached = 0;
|
| int res;
|
| - assert( pMerger->pTask->pUnpacked!=0 ); /* from vdbeSortSubtaskMain() */
|
| - res = vdbeSorterCompare(
|
| - pMerger->pTask, p1->aKey, p1->nKey, p2->aKey, p2->nKey
|
| + assert( pTask->pUnpacked!=0 ); /* from vdbeSortSubtaskMain() */
|
| + res = pTask->xCompare(
|
| + pTask, &bCached, p1->aKey, p1->nKey, p2->aKey, p2->nKey
|
| );
|
| if( res<=0 ){
|
| iRes = i1;
|
| @@ -1890,11 +2067,12 @@ static void vdbeMergeEngineCompare(
|
| #define INCRINIT_TASK 1
|
| #define INCRINIT_ROOT 2
|
|
|
| -/* Forward reference.
|
| -** The vdbeIncrMergeInit() and vdbePmaReaderIncrMergeInit() routines call each
|
| -** other (when building a merge tree).
|
| +/*
|
| +** Forward reference required as the vdbeIncrMergeInit() and
|
| +** vdbePmaReaderIncrInit() routines are called mutually recursively when
|
| +** building a merge tree.
|
| */
|
| -static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode);
|
| +static int vdbePmaReaderIncrInit(PmaReader *pReadr, int eMode);
|
|
|
| /*
|
| ** Initialize the MergeEngine object passed as the second argument. Once this
|
| @@ -1941,7 +2119,7 @@ static int vdbeMergeEngineInit(
|
| ** better advantage of multi-processor hardware. */
|
| rc = vdbePmaReaderNext(&pMerger->aReadr[nTree-i-1]);
|
| }else{
|
| - rc = vdbePmaReaderIncrMergeInit(&pMerger->aReadr[i], INCRINIT_NORMAL);
|
| + rc = vdbePmaReaderIncrInit(&pMerger->aReadr[i], INCRINIT_NORMAL);
|
| }
|
| if( rc!=SQLITE_OK ) return rc;
|
| }
|
| @@ -1953,17 +2131,15 @@ static int vdbeMergeEngineInit(
|
| }
|
|
|
| /*
|
| -** Initialize the IncrMerge field of a PmaReader.
|
| -**
|
| -** If the PmaReader passed as the first argument is not an incremental-reader
|
| -** (if pReadr->pIncr==0), then this function is a no-op. Otherwise, it serves
|
| -** to open and/or initialize the temp file related fields of the IncrMerge
|
| +** The PmaReader passed as the first argument is guaranteed to be an
|
| +** incremental-reader (pReadr->pIncr!=0). This function serves to open
|
| +** and/or initialize the temp file related fields of the IncrMerge
|
| ** object at (pReadr->pIncr).
|
| **
|
| ** If argument eMode is set to INCRINIT_NORMAL, then all PmaReaders
|
| -** in the sub-tree headed by pReadr are also initialized. Data is then loaded
|
| -** into the buffers belonging to pReadr and it is set to
|
| -** point to the first key in its range.
|
| +** in the sub-tree headed by pReadr are also initialized. Data is then
|
| +** loaded into the buffers belonging to pReadr and it is set to point to
|
| +** the first key in its range.
|
| **
|
| ** If argument eMode is set to INCRINIT_TASK, then pReadr is guaranteed
|
| ** to be a multi-threaded PmaReader and this function is being called in a
|
| @@ -1990,59 +2166,62 @@ static int vdbeMergeEngineInit(
|
| static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){
|
| int rc = SQLITE_OK;
|
| IncrMerger *pIncr = pReadr->pIncr;
|
| + SortSubtask *pTask = pIncr->pTask;
|
| + sqlite3 *db = pTask->pSorter->db;
|
|
|
| /* eMode is always INCRINIT_NORMAL in single-threaded mode */
|
| assert( SQLITE_MAX_WORKER_THREADS>0 || eMode==INCRINIT_NORMAL );
|
|
|
| - if( pIncr ){
|
| - SortSubtask *pTask = pIncr->pTask;
|
| - sqlite3 *db = pTask->pSorter->db;
|
| -
|
| - rc = vdbeMergeEngineInit(pTask, pIncr->pMerger, eMode);
|
| + rc = vdbeMergeEngineInit(pTask, pIncr->pMerger, eMode);
|
|
|
| - /* Set up the required files for pIncr. A multi-theaded IncrMerge object
|
| - ** requires two temp files to itself, whereas a single-threaded object
|
| - ** only requires a region of pTask->file2. */
|
| - if( rc==SQLITE_OK ){
|
| - int mxSz = pIncr->mxSz;
|
| + /* Set up the required files for pIncr. A multi-theaded IncrMerge object
|
| + ** requires two temp files to itself, whereas a single-threaded object
|
| + ** only requires a region of pTask->file2. */
|
| + if( rc==SQLITE_OK ){
|
| + int mxSz = pIncr->mxSz;
|
| #if SQLITE_MAX_WORKER_THREADS>0
|
| - if( pIncr->bUseThread ){
|
| - rc = vdbeSorterOpenTempFile(db, mxSz, &pIncr->aFile[0].pFd);
|
| - if( rc==SQLITE_OK ){
|
| - rc = vdbeSorterOpenTempFile(db, mxSz, &pIncr->aFile[1].pFd);
|
| - }
|
| - }else
|
| + if( pIncr->bUseThread ){
|
| + rc = vdbeSorterOpenTempFile(db, mxSz, &pIncr->aFile[0].pFd);
|
| + if( rc==SQLITE_OK ){
|
| + rc = vdbeSorterOpenTempFile(db, mxSz, &pIncr->aFile[1].pFd);
|
| + }
|
| + }else
|
| #endif
|
| - /*if( !pIncr->bUseThread )*/{
|
| - if( pTask->file2.pFd==0 ){
|
| - assert( pTask->file2.iEof>0 );
|
| - rc = vdbeSorterOpenTempFile(db, pTask->file2.iEof, &pTask->file2.pFd);
|
| - pTask->file2.iEof = 0;
|
| - }
|
| - if( rc==SQLITE_OK ){
|
| - pIncr->aFile[1].pFd = pTask->file2.pFd;
|
| - pIncr->iStartOff = pTask->file2.iEof;
|
| - pTask->file2.iEof += mxSz;
|
| - }
|
| + /*if( !pIncr->bUseThread )*/{
|
| + if( pTask->file2.pFd==0 ){
|
| + assert( pTask->file2.iEof>0 );
|
| + rc = vdbeSorterOpenTempFile(db, pTask->file2.iEof, &pTask->file2.pFd);
|
| + pTask->file2.iEof = 0;
|
| + }
|
| + if( rc==SQLITE_OK ){
|
| + pIncr->aFile[1].pFd = pTask->file2.pFd;
|
| + pIncr->iStartOff = pTask->file2.iEof;
|
| + pTask->file2.iEof += mxSz;
|
| }
|
| }
|
| + }
|
|
|
| #if SQLITE_MAX_WORKER_THREADS>0
|
| - if( rc==SQLITE_OK && pIncr->bUseThread ){
|
| - /* Use the current thread to populate aFile[1], even though this
|
| - ** PmaReader is multi-threaded. The reason being that this function
|
| - ** is already running in background thread pIncr->pTask->thread. */
|
| - assert( eMode==INCRINIT_ROOT || eMode==INCRINIT_TASK );
|
| - rc = vdbeIncrPopulate(pIncr);
|
| - }
|
| + if( rc==SQLITE_OK && pIncr->bUseThread ){
|
| + /* Use the current thread to populate aFile[1], even though this
|
| + ** PmaReader is multi-threaded. If this is an INCRINIT_TASK object,
|
| + ** then this function is already running in background thread
|
| + ** pIncr->pTask->thread.
|
| + **
|
| + ** If this is the INCRINIT_ROOT object, then it is running in the
|
| + ** main VDBE thread. But that is Ok, as that thread cannot return
|
| + ** control to the VDBE or proceed with anything useful until the
|
| + ** first results are ready from this merger object anyway.
|
| + */
|
| + assert( eMode==INCRINIT_ROOT || eMode==INCRINIT_TASK );
|
| + rc = vdbeIncrPopulate(pIncr);
|
| + }
|
| #endif
|
|
|
| - if( rc==SQLITE_OK
|
| - && (SQLITE_MAX_WORKER_THREADS==0 || eMode!=INCRINIT_TASK)
|
| - ){
|
| - rc = vdbePmaReaderNext(pReadr);
|
| - }
|
| + if( rc==SQLITE_OK && (SQLITE_MAX_WORKER_THREADS==0 || eMode!=INCRINIT_TASK) ){
|
| + rc = vdbePmaReaderNext(pReadr);
|
| }
|
| +
|
| return rc;
|
| }
|
|
|
| @@ -2051,7 +2230,7 @@ static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){
|
| ** The main routine for vdbePmaReaderIncrMergeInit() operations run in
|
| ** background threads.
|
| */
|
| -static void *vdbePmaReaderBgInit(void *pCtx){
|
| +static void *vdbePmaReaderBgIncrInit(void *pCtx){
|
| PmaReader *pReader = (PmaReader*)pCtx;
|
| void *pRet = SQLITE_INT_TO_PTR(
|
| vdbePmaReaderIncrMergeInit(pReader,INCRINIT_TASK)
|
| @@ -2059,20 +2238,36 @@ static void *vdbePmaReaderBgInit(void *pCtx){
|
| pReader->pIncr->pTask->bDone = 1;
|
| return pRet;
|
| }
|
| +#endif
|
|
|
| /*
|
| -** Use a background thread to invoke vdbePmaReaderIncrMergeInit(INCRINIT_TASK)
|
| -** on the PmaReader object passed as the first argument.
|
| -**
|
| -** This call will initialize the various fields of the pReadr->pIncr
|
| -** structure and, if it is a multi-threaded IncrMerger, launch a
|
| -** background thread to populate aFile[1].
|
| -*/
|
| -static int vdbePmaReaderBgIncrInit(PmaReader *pReadr){
|
| - void *pCtx = (void*)pReadr;
|
| - return vdbeSorterCreateThread(pReadr->pIncr->pTask, vdbePmaReaderBgInit, pCtx);
|
| -}
|
| +** If the PmaReader passed as the first argument is not an incremental-reader
|
| +** (if pReadr->pIncr==0), then this function is a no-op. Otherwise, it invokes
|
| +** the vdbePmaReaderIncrMergeInit() function with the parameters passed to
|
| +** this routine to initialize the incremental merge.
|
| +**
|
| +** If the IncrMerger object is multi-threaded (IncrMerger.bUseThread==1),
|
| +** then a background thread is launched to call vdbePmaReaderIncrMergeInit().
|
| +** Or, if the IncrMerger is single threaded, the same function is called
|
| +** using the current thread.
|
| +*/
|
| +static int vdbePmaReaderIncrInit(PmaReader *pReadr, int eMode){
|
| + IncrMerger *pIncr = pReadr->pIncr; /* Incremental merger */
|
| + int rc = SQLITE_OK; /* Return code */
|
| + if( pIncr ){
|
| +#if SQLITE_MAX_WORKER_THREADS>0
|
| + assert( pIncr->bUseThread==0 || eMode==INCRINIT_TASK );
|
| + if( pIncr->bUseThread ){
|
| + void *pCtx = (void*)pReadr;
|
| + rc = vdbeSorterCreateThread(pIncr->pTask, vdbePmaReaderBgIncrInit, pCtx);
|
| + }else
|
| #endif
|
| + {
|
| + rc = vdbePmaReaderIncrMergeInit(pReadr, eMode);
|
| + }
|
| + }
|
| + return rc;
|
| +}
|
|
|
| /*
|
| ** Allocate a new MergeEngine object to merge the contents of nPMA level-0
|
| @@ -2284,6 +2479,11 @@ static int vdbeSorterSetupMerge(VdbeSorter *pSorter){
|
| MergeEngine *pMain = 0;
|
| #if SQLITE_MAX_WORKER_THREADS
|
| sqlite3 *db = pTask0->pSorter->db;
|
| + int i;
|
| + SorterCompare xCompare = vdbeSorterGetCompare(pSorter);
|
| + for(i=0; i<pSorter->nTask; i++){
|
| + pSorter->aTask[i].xCompare = xCompare;
|
| + }
|
| #endif
|
|
|
| rc = vdbeSorterMergeTreeBuild(pSorter, &pMain);
|
| @@ -2312,15 +2512,21 @@ static int vdbeSorterSetupMerge(VdbeSorter *pSorter){
|
| }
|
| }
|
| for(iTask=0; rc==SQLITE_OK && iTask<pSorter->nTask; iTask++){
|
| + /* Check that:
|
| + **
|
| + ** a) The incremental merge object is configured to use the
|
| + ** right task, and
|
| + ** b) If it is using task (nTask-1), it is configured to run
|
| + ** in single-threaded mode. This is important, as the
|
| + ** root merge (INCRINIT_ROOT) will be using the same task
|
| + ** object.
|
| + */
|
| PmaReader *p = &pMain->aReadr[iTask];
|
| - assert( p->pIncr==0 || p->pIncr->pTask==&pSorter->aTask[iTask] );
|
| - if( p->pIncr ){
|
| - if( iTask==pSorter->nTask-1 ){
|
| - rc = vdbePmaReaderIncrMergeInit(p, INCRINIT_TASK);
|
| - }else{
|
| - rc = vdbePmaReaderBgIncrInit(p);
|
| - }
|
| - }
|
| + assert( p->pIncr==0 || (
|
| + (p->pIncr->pTask==&pSorter->aTask[iTask]) /* a */
|
| + && (iTask!=pSorter->nTask-1 || p->pIncr->bUseThread==0) /* b */
|
| + ));
|
| + rc = vdbePmaReaderIncrInit(p, INCRINIT_TASK);
|
| }
|
| }
|
| pMain = 0;
|
| @@ -2350,9 +2556,11 @@ static int vdbeSorterSetupMerge(VdbeSorter *pSorter){
|
| ** in sorted order.
|
| */
|
| int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){
|
| - VdbeSorter *pSorter = pCsr->pSorter;
|
| + VdbeSorter *pSorter;
|
| int rc = SQLITE_OK; /* Return code */
|
|
|
| + assert( pCsr->eCurType==CURTYPE_SORTER );
|
| + pSorter = pCsr->uc.pSorter;
|
| assert( pSorter );
|
|
|
| /* If no data has been written to disk, then do not do so now. Instead,
|
| @@ -2396,9 +2604,11 @@ int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){
|
| ** Advance to the next element in the sorter.
|
| */
|
| int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr, int *pbEof){
|
| - VdbeSorter *pSorter = pCsr->pSorter;
|
| + VdbeSorter *pSorter;
|
| int rc; /* Return code */
|
|
|
| + assert( pCsr->eCurType==CURTYPE_SORTER );
|
| + pSorter = pCsr->uc.pSorter;
|
| assert( pSorter->bUsePMA || (pSorter->pReader==0 && pSorter->pMerger==0) );
|
| if( pSorter->bUsePMA ){
|
| assert( pSorter->pReader==0 || pSorter->pMerger==0 );
|
| @@ -2411,6 +2621,7 @@ int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr, int *pbEof){
|
| }else
|
| #endif
|
| /*if( !pSorter->bUseThreads )*/ {
|
| + assert( pSorter->pMerger!=0 );
|
| assert( pSorter->pMerger->pTask==(&pSorter->aTask[0]) );
|
| rc = vdbeMergeEngineStep(pSorter->pMerger, pbEof);
|
| }
|
| @@ -2457,9 +2668,11 @@ static void *vdbeSorterRowkey(
|
| ** Copy the current sorter key into the memory cell pOut.
|
| */
|
| int sqlite3VdbeSorterRowkey(const VdbeCursor *pCsr, Mem *pOut){
|
| - VdbeSorter *pSorter = pCsr->pSorter;
|
| + VdbeSorter *pSorter;
|
| void *pKey; int nKey; /* Sorter key to copy into pOut */
|
|
|
| + assert( pCsr->eCurType==CURTYPE_SORTER );
|
| + pSorter = pCsr->uc.pSorter;
|
| pKey = vdbeSorterRowkey(pSorter, &nKey);
|
| if( sqlite3VdbeMemClearAndResize(pOut, nKey) ){
|
| return SQLITE_NOMEM;
|
| @@ -2493,12 +2706,16 @@ int sqlite3VdbeSorterCompare(
|
| int nKeyCol, /* Compare this many columns */
|
| int *pRes /* OUT: Result of comparison */
|
| ){
|
| - VdbeSorter *pSorter = pCsr->pSorter;
|
| - UnpackedRecord *r2 = pSorter->pUnpacked;
|
| - KeyInfo *pKeyInfo = pCsr->pKeyInfo;
|
| + VdbeSorter *pSorter;
|
| + UnpackedRecord *r2;
|
| + KeyInfo *pKeyInfo;
|
| int i;
|
| void *pKey; int nKey; /* Sorter key to compare pVal with */
|
|
|
| + assert( pCsr->eCurType==CURTYPE_SORTER );
|
| + pSorter = pCsr->uc.pSorter;
|
| + r2 = pSorter->pUnpacked;
|
| + pKeyInfo = pCsr->pKeyInfo;
|
| if( r2==0 ){
|
| char *p;
|
| r2 = pSorter->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pKeyInfo,0,0,&p);
|
|
|