Index: third_party/sqlite/src/src/vdbesort.c |
diff --git a/third_party/sqlite/src/src/vdbesort.c b/third_party/sqlite/src/src/vdbesort.c |
index 46c9f3789de323f2dbadda1fe902bf00f282a617..54e538fd50fda188d6d927311e486eaacf1ed25c 100644 |
--- a/third_party/sqlite/src/src/vdbesort.c |
+++ b/third_party/sqlite/src/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); |