Fix further code and documentation issues in vdbesort.c.
FossilOrigin-Name: d03f5b8622d304f029f73c7cd0bee3182a81d081
This commit is contained in:
+303
-237
@@ -10,7 +10,7 @@
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains code for the VdbeSorter object, used in concert with
|
||||
** a VdbeCursor to sort large numbers of keys for CREATE TABLE statements
|
||||
** a VdbeCursor to sort large numbers of keys for CREATE INDEX statements
|
||||
** or by SELECT statements with ORDER BY clauses that cannot be satisfied
|
||||
** using indexes and without LIMIT clauses.
|
||||
**
|
||||
@@ -57,33 +57,84 @@
|
||||
**
|
||||
** The interfaces above must be called in a particular order. Write() can
|
||||
** only occur in between Init()/Reset() and Rewind(). Next(), Rowkey(), and
|
||||
** Compare() can only occur in between Rewind() and Close()/Reset().
|
||||
** Compare() can only occur in between Rewind() and Close()/Reset(). i.e.
|
||||
**
|
||||
** Init()
|
||||
** for each record: Write()
|
||||
** Rewind()
|
||||
** Rowkey()/Compare()
|
||||
** Next()
|
||||
** Close()
|
||||
**
|
||||
** Algorithm:
|
||||
**
|
||||
** Records to be sorted are initially held in memory, in the order in
|
||||
** which they arrive from Write(). When the amount of memory needed exceeds
|
||||
** a threshold, all in-memory records are sorted and then appended to
|
||||
** a temporary file as a "Packed-Memory-Array" or "PMA" and the memory is
|
||||
** reset. There is a single temporary file used for all PMAs. The PMAs
|
||||
** are packed one after another in the file. The VdbeSorter object keeps
|
||||
** track of the number of PMAs written.
|
||||
** Records passed to the sorter via calls to Write() are initially held
|
||||
** unsorted in main memory. Assuming the amount of memory used never exceeds
|
||||
** a threshold, when Rewind() is called the set of records is sorted using
|
||||
** an in-memory merge sort. In this case, no temporary files are required
|
||||
** and subsequent calls to Rowkey(), Next() and Compare() read records
|
||||
** directly from main memory.
|
||||
**
|
||||
** When the Rewind() is seen, any records still held in memory are sorted.
|
||||
** If no PMAs have been written (if all records are still held in memory)
|
||||
** then subsequent Rowkey(), Next(), and Compare() operations work directly
|
||||
** from memory. But if PMAs have been written things get a little more
|
||||
** complicated.
|
||||
** If the amount of space used to store records in main memory exceeds the
|
||||
** threshold, then the set of records currently in memory are sorted and
|
||||
** written to a temporary file in "Packed Memory Array" (PMA) format.
|
||||
** A PMA created at this point is known as a "level-0 PMA". Higher levels
|
||||
** of PMAs may be created by merging existing PMAs together - for example
|
||||
** merging two or more level-0 PMAs together creates a level-1 PMA.
|
||||
**
|
||||
** When Rewind() is seen after PMAs have been written, any records still
|
||||
** in memory are sorted and written as a final PMA. Then all the PMAs
|
||||
** are merged together into a single massive PMA that Next(), Rowkey(),
|
||||
** and Compare() walk to extract the records in sorted order.
|
||||
** The threshold for the amount of main memory to use before flushing
|
||||
** records to a PMA is roughly the same as the limit configured for the
|
||||
** page-cache of the main database. Specifically, the threshold is set to
|
||||
** the value returned multiplied by "PRAGMA main.page_size" multipled by
|
||||
** that returned by "PRAGMA main.cache_size", in bytes.
|
||||
**
|
||||
** If SQLITE_MAX_WORKER_THREADS is non-zero, various steps of the above
|
||||
** algorithm might be performed in parallel by separate threads. Threads
|
||||
** are only used when one or more PMA spill to disk. If the sort is small
|
||||
** enough to fit entirely in memory, everything happens on the main thread.
|
||||
** If the sorter is running in single-threaded mode, then all PMAs generated
|
||||
** are appended to a single temporary file. Or, if the sorter is running in
|
||||
** multi-threaded mode then up to (N+1) temporary files may be opened, where
|
||||
** N is the configured number of worker threads. In this case, instead of
|
||||
** sorting the records and writing the PMA to a temporary file itself, the
|
||||
** calling thread usually launches a worker thread to do so. Except, if
|
||||
** there are already N worker threads running, the main thread does the work
|
||||
** itself.
|
||||
**
|
||||
** 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, ...).
|
||||
**
|
||||
** 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
|
||||
** PMAs within temporary files on disk. Within a single file sorter is
|
||||
** running in single threaded mode, or distributed between one or more files
|
||||
** for multi-threaded sorters.
|
||||
**
|
||||
** If there are fewer than SORTER_MAX_MERGE_COUNT PMAs in total and the
|
||||
** sorter is running in single-threaded mode, then these PMAs are merged
|
||||
** incrementally as keys are retreived from the sorter by the VDBE. See
|
||||
** comments above object MergeEngine below for details.
|
||||
**
|
||||
** Or, if running in multi-threaded mode, then a background thread is
|
||||
** launched to merge the existing PMAs. Once the background thread has
|
||||
** merged T bytes of data into a single sorted PMA, the main thread
|
||||
** begins reading keys from that PMA while the background thread proceeds
|
||||
** with merging the next T bytes of data. And so on.
|
||||
**
|
||||
** Parameter T is set to half the value of the memory threshold used
|
||||
** by Write() above to determine when to create a new PMA.
|
||||
**
|
||||
** If there are more than SORTER_MAX_MERGE_COUNT PMAs in total when
|
||||
** Rewind() is called, then a hierarchy of incremental-merges is used.
|
||||
** First, T bytes of data from the first SORTER_MAX_MERGE_COUNT PMAs on
|
||||
** disk are merged together. Then T bytes of data from the second set, and
|
||||
** so on, such that no operation ever merges more than SORTER_MAX_MERGE_COUNT
|
||||
** PMAs at a time. This done is to improve locality.
|
||||
**
|
||||
** If running in multi-threaded mode and there are more than
|
||||
** SORTER_MAX_MERGE_COUNT PMAs on disk when Rewind() is called, then more
|
||||
** than one background thread may be created. Specifically, there may be
|
||||
** one background thread for each temporary file on disk, and one background
|
||||
** thread to merge the output of each of the others to a single PMA for
|
||||
** the main thread to read from.
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include "vdbeInt.h"
|
||||
@@ -102,12 +153,11 @@
|
||||
*/
|
||||
typedef struct MergeEngine MergeEngine; /* Merge PMAs together */
|
||||
typedef struct PmaReader PmaReader; /* Incrementally read one PMA */
|
||||
typedef struct PmaWriter PmaWriter; /* Incrementally write on PMA */
|
||||
typedef struct PmaWriter PmaWriter; /* Incrementally write one PMA */
|
||||
typedef struct SorterRecord SorterRecord; /* A record being sorted */
|
||||
typedef struct SortSubtask SortSubtask; /* A sub-task in the sort process */
|
||||
typedef struct SorterFile SorterFile;
|
||||
typedef struct SorterThread SorterThread;
|
||||
typedef struct SorterList SorterList;
|
||||
typedef struct SorterFile SorterFile; /* Temporary file object wrapper */
|
||||
typedef struct SorterList SorterList; /* In-memory list of records */
|
||||
typedef struct IncrMerger IncrMerger;
|
||||
|
||||
/*
|
||||
@@ -120,85 +170,14 @@ struct SorterFile {
|
||||
};
|
||||
|
||||
/*
|
||||
** An object of this type is used to store the thread handle for each
|
||||
** background thread launched by the sorter. Before the thread is launched,
|
||||
** variable bDone is set to 0. Then, right before it exits, the thread
|
||||
** itself sets bDone to 1.
|
||||
**
|
||||
** This is then used for two purposes:
|
||||
**
|
||||
** 1. When flushing the contents of memory to a level-0 PMA on disk, to
|
||||
** attempt to select a SortSubtask for which there is not already an
|
||||
** active background thread (since doing so causes the main thread
|
||||
** to block until it finishes).
|
||||
**
|
||||
** 2. If SQLITE_DEBUG_SORTER_THREADS is defined, to determine if a call
|
||||
** to sqlite3ThreadJoin() is likely to block.
|
||||
**
|
||||
** In both cases, the effects of the main thread seeing (bDone==0) even
|
||||
** after the thread has finished are not dire. So we don't worry about
|
||||
** memory barriers and such here.
|
||||
** In memory linked list of records.
|
||||
*/
|
||||
struct SorterThread {
|
||||
SQLiteThread *pThread;
|
||||
int bDone;
|
||||
};
|
||||
|
||||
struct SorterList {
|
||||
SorterRecord *pList; /* Linked list of records */
|
||||
u8 *aMemory; /* If non-NULL, blob of memory for pList */
|
||||
int szPMA; /* Size of pList as PMA in bytes */
|
||||
};
|
||||
|
||||
/*
|
||||
** Sorting is divided up into smaller subtasks. Each subtask is controlled
|
||||
** by an instance of this object. A Subtask might run in either the main thread
|
||||
** or in a background thread.
|
||||
**
|
||||
** Exactly VdbeSorter.nTask instances of this object are allocated
|
||||
** as part of each VdbeSorter object. Instances are never allocated any other
|
||||
** way. VdbeSorter.nTask is set to the number of worker threads allowed
|
||||
** (see SQLITE_CONFIG_WORKER_THREADS) plus one (the main thread).
|
||||
**
|
||||
** When a background thread is launched to perform work, SortSubtask.bDone
|
||||
** is set to 0 and the SortSubtask.pTask variable set to point to the
|
||||
** thread handle. SortSubtask.bDone is set to 1 (to indicate to the main
|
||||
** thread that joining SortSubtask.pTask will not block) before the thread
|
||||
** exits. SortSubtask.pTask and bDone are always cleared after the
|
||||
** background thread has been joined.
|
||||
**
|
||||
** One object (specifically, VdbeSorter.aTask[VdbeSorter.nTask-1])
|
||||
** is reserved for the foreground thread.
|
||||
**
|
||||
** The nature of the work performed is determined by SortSubtask.eWork,
|
||||
** as follows:
|
||||
**
|
||||
** SORT_SUBTASK_SORT:
|
||||
** Sort the linked list of records at SortSubtask.pList.
|
||||
**
|
||||
** SORT_SUBTASK_TO_PMA:
|
||||
** Sort the linked list of records at SortSubtask.pList, and write
|
||||
** the results to a new PMA in temp file SortSubtask.pTemp1. Open
|
||||
** the temp file if it is not already open.
|
||||
**
|
||||
** SORT_SUBTASK_CONS:
|
||||
** Merge existing PMAs until SortSubtask.nConsolidate or fewer
|
||||
** remain in temp file SortSubtask.pTemp1.
|
||||
*/
|
||||
struct SortSubtask {
|
||||
SorterThread thread;
|
||||
sqlite3 *db; /* Database connection */
|
||||
VdbeSorter *pSorter; /* Sorter */
|
||||
KeyInfo *pKeyInfo; /* How to compare records */
|
||||
UnpackedRecord *pUnpacked; /* Space to unpack a record */
|
||||
int pgsz; /* Main database page size */
|
||||
SorterList list; /* List for thread to write to a PMA */
|
||||
int nPMA; /* Number of PMAs currently in file */
|
||||
SorterFile file; /* Temp file for level-0 PMAs */
|
||||
SorterFile file2; /* Space for other PMAs */
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** The MergeEngine object is used to combine two or more smaller PMAs into
|
||||
** one big PMA using a merge operation. Separate PMAs all need to be
|
||||
@@ -268,6 +247,45 @@ struct MergeEngine {
|
||||
PmaReader *aIter; /* Array of iterators to merge data from */
|
||||
};
|
||||
|
||||
/*
|
||||
** Exactly VdbeSorter.nTask instances of this object are allocated
|
||||
** as part of each VdbeSorter object. Instances are never allocated any
|
||||
** other way. VdbeSorter.nTask is set to the number of worker threads allowed
|
||||
** (see SQLITE_CONFIG_WORKER_THREADS) plus one (the main thread).
|
||||
**
|
||||
** Essentially, this structure contains all those fields of the VdbeSorter
|
||||
** structure for which each thread requires a separate instance. For example,
|
||||
** each thread requries its own UnpackedRecord object to unpack records in
|
||||
** as part of comparison operations.
|
||||
**
|
||||
** Before a background thread is launched, variable bDone is set to 0. Then,
|
||||
** right before it exits, the thread itself sets bDone to 1. This is used for
|
||||
** two purposes:
|
||||
**
|
||||
** 1. When flushing the contents of memory to a level-0 PMA on disk, to
|
||||
** attempt to select a SortSubtask for which there is not already an
|
||||
** active background thread (since doing so causes the main thread
|
||||
** to block until it finishes).
|
||||
**
|
||||
** 2. If SQLITE_DEBUG_SORTER_THREADS is defined, to determine if a call
|
||||
** to sqlite3ThreadJoin() is likely to block. Cases that are likely to
|
||||
** block provoke debugging output.
|
||||
**
|
||||
** In both cases, the effects of the main thread seeing (bDone==0) even
|
||||
** after the thread has finished are not dire. So we don't worry about
|
||||
** memory barriers and such here.
|
||||
*/
|
||||
struct SortSubtask {
|
||||
SQLiteThread *pThread; /* Background thread, if any */
|
||||
int bDone; /* Set if thread is finished but not joined */
|
||||
VdbeSorter *pSorter; /* Sorter that owns this sub-task */
|
||||
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 */
|
||||
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.
|
||||
@@ -280,9 +298,12 @@ struct MergeEngine {
|
||||
struct VdbeSorter {
|
||||
int mnPmaSize; /* Minimum PMA size, in bytes */
|
||||
int mxPmaSize; /* Maximum PMA size, in bytes. 0==no limit */
|
||||
int mxKeysize; /* Largest serialized key seen so far */
|
||||
int pgsz; /* Main database page size */
|
||||
PmaReader *pReader; /* Read data from here after Rewind() */
|
||||
MergeEngine *pMerger; /* Or here, if bUseThreads==0 */
|
||||
int mxKeysize; /* Largest serialized key seen so far */
|
||||
sqlite3 *db; /* Database connection */
|
||||
KeyInfo *pKeyInfo; /* How to compare records */
|
||||
UnpackedRecord *pUnpacked; /* Used by VdbeSorterCompare() */
|
||||
SorterList list; /* List of in-memory records */
|
||||
int iMemory; /* Offset of free space in list.aMemory */
|
||||
@@ -318,10 +339,35 @@ struct PmaReader {
|
||||
** within a temp file. However, if the PmaReader.pIncr variable points to
|
||||
** an object of the following type, it may be used to iterate/merge through
|
||||
** multiple PMAs simultaneously.
|
||||
**
|
||||
** There are two types of IncrMerger object - single (bUseThread==0) and
|
||||
** multi-threaded (bUseThread==1).
|
||||
**
|
||||
** A multi-threaded IncrMerger object uses two temporary files - aFile[0]
|
||||
** and aFile[1]. Neither file is allowed to grow to more than mxSz bytes in
|
||||
** size. When the IncrMerger is initialized, it reads enough data from
|
||||
** pMerger to populate aFile[0]. It then sets variables within the
|
||||
** corresponding PmaReader object to read from that file and kicks off
|
||||
** a background thread to populate aFile[1] with the next mxSz bytes of
|
||||
** sorted record data from pMerger.
|
||||
**
|
||||
** When the PmaReader reaches the end of aFile[0], it blocks until the
|
||||
** background thread has finished populating aFile[1]. It then exchanges
|
||||
** the contents of the aFile[0] and aFile[1] variables within this structure,
|
||||
** sets the PmaReader fields to read from the new aFile[0] and kicks off
|
||||
** another background thread to populate the new aFile[1]. And so on, until
|
||||
** the contents of pMerger are exhausted.
|
||||
**
|
||||
** A single-threaded IncrMerger does not open any temporary files of its
|
||||
** own. Instead, it has exclusive access to mxSz bytes of space beginning
|
||||
** at offset iStartOff of file pTask->file2. And instead of using a
|
||||
** background thread to prepare data for the PmaReader, with a single
|
||||
** threaded IncrMerger the allocate part of pTask->file2 is "refilled" with
|
||||
** keys from pMerger by the calling thread whenever the PmaReader runs out
|
||||
** of data.
|
||||
*/
|
||||
struct IncrMerger {
|
||||
SortSubtask *pTask; /* Task that owns this merger */
|
||||
SorterThread thread; /* Thread for populating aFile[1] */
|
||||
MergeEngine *pMerger; /* Merge engine thread reads data from */
|
||||
i64 iStartOff; /* Offset to start writing file at */
|
||||
int mxSz; /* Maximum bytes of data to store */
|
||||
@@ -354,10 +400,10 @@ struct PmaWriter {
|
||||
**
|
||||
** How the linked list is connected depends on how memory is being managed
|
||||
** by this module. If using a separate allocation for each in-memory record
|
||||
** (VdbeSorter.aMemory==0), then the list is always connected using the
|
||||
** (VdbeSorter.list.aMemory==0), then the list is always connected using the
|
||||
** SorterRecord.u.pNext pointers.
|
||||
**
|
||||
** Or, if using the single large allocation method (VdbeSorter.aMemory!=0),
|
||||
** Or, if using the single large allocation method (VdbeSorter.list.aMemory!=0),
|
||||
** then while records are being accumulated the list is linked using the
|
||||
** SorterRecord.u.iNext offset. This is because the aMemory[] array may
|
||||
** be sqlite3Realloc()ed while records are being accumulated. Once the VM
|
||||
@@ -390,7 +436,7 @@ struct SorterRecord {
|
||||
#define SORTER_MAX_MERGE_COUNT 16
|
||||
|
||||
static int vdbeIncrSwap(IncrMerger*);
|
||||
static void vdbeIncrFree(IncrMerger*);
|
||||
static void vdbeIncrFree(IncrMerger *);
|
||||
|
||||
/*
|
||||
** Free all memory belonging to the PmaReader object passed as the second
|
||||
@@ -531,56 +577,69 @@ static int vdbePmaReadVarint(PmaReader *p, u64 *pnOut){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Attempt to memory map file pFile. If successful, set *pp to point to the
|
||||
** new mapping and return SQLITE_OK. If the mapping is not attempted
|
||||
** (because the file is too large or the VFS layer is configured not to use
|
||||
** mmap), return SQLITE_OK and set *pp to NULL.
|
||||
**
|
||||
** Or, if an error occurs, return an SQLite error code. The final value of
|
||||
** *pp is undefined in this case.
|
||||
*/
|
||||
static int vdbeSorterMapFile(SortSubtask *pTask, SorterFile *pFile, u8 **pp){
|
||||
int rc = SQLITE_OK;
|
||||
if( pFile->iEof<=(i64)(pTask->db->nMaxSorterMmap) ){
|
||||
if( pFile->iEof<=(i64)(pTask->pSorter->db->nMaxSorterMmap) ){
|
||||
rc = sqlite3OsFetch(pFile->pFd, 0, pFile->iEof, (void**)pp);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int vdbePmaReaderReinit(PmaReader *pIter){
|
||||
IncrMerger *pIncr = pIter->pIncr;
|
||||
SortSubtask *pTask = pIncr->pTask;
|
||||
/*
|
||||
** Seek iterator pIter to offset iOff within file pFile. Return SQLITE_OK
|
||||
** if successful, or an SQLite error code if an error occurs.
|
||||
*/
|
||||
static int vdbePmaReaderSeek(
|
||||
SortSubtask *pTask, /* Task context */
|
||||
PmaReader *pIter, /* Iterate to populate */
|
||||
SorterFile *pFile, /* Sorter file to read from */
|
||||
i64 iOff /* Offset in pFile */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
assert( pIncr->bEof==0 );
|
||||
assert( pIter->pIncr==0 || pIter->pIncr->bEof==0 );
|
||||
|
||||
if( pIter->aMap ){
|
||||
sqlite3OsUnfetch(pIter->pFile, 0, pIter->aMap);
|
||||
pIter->aMap = 0;
|
||||
}
|
||||
pIter->iReadOff = pIncr->iStartOff;
|
||||
pIter->iEof = pIncr->aFile[0].iEof;
|
||||
pIter->pFile = pIncr->aFile[0].pFd;
|
||||
pIter->iReadOff = iOff;
|
||||
pIter->iEof = pFile->iEof;
|
||||
pIter->pFile = pFile->pFd;
|
||||
|
||||
rc = vdbeSorterMapFile(pTask, &pIncr->aFile[0], &pIter->aMap);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( pIter->aMap==0 ){
|
||||
/* TODO: Combine this code with similar code in vdbePmaReaderInit() */
|
||||
int iBuf = pIter->iReadOff % pTask->pgsz;
|
||||
if( pIter->aBuffer==0 ){
|
||||
pIter->aBuffer = (u8*)sqlite3Malloc(pTask->pgsz);
|
||||
if( pIter->aBuffer==0 ) rc = SQLITE_NOMEM;
|
||||
pIter->nBuffer = pTask->pgsz;
|
||||
}
|
||||
if( iBuf ){
|
||||
int nRead = pTask->pgsz - iBuf;
|
||||
if( (pIter->iReadOff + nRead) > pIter->iEof ){
|
||||
nRead = (int)(pIter->iEof - pIter->iReadOff);
|
||||
}
|
||||
rc = sqlite3OsRead(
|
||||
pIter->pFile, &pIter->aBuffer[iBuf], nRead, pIter->iReadOff
|
||||
);
|
||||
assert( rc!=SQLITE_IOERR_SHORT_READ );
|
||||
rc = vdbeSorterMapFile(pTask, pFile, &pIter->aMap);
|
||||
if( rc==SQLITE_OK && pIter->aMap==0 ){
|
||||
int pgsz = pTask->pSorter->pgsz;
|
||||
int iBuf = pIter->iReadOff % pgsz;
|
||||
if( pIter->aBuffer==0 ){
|
||||
pIter->aBuffer = (u8*)sqlite3Malloc(pgsz);
|
||||
if( pIter->aBuffer==0 ) rc = SQLITE_NOMEM;
|
||||
pIter->nBuffer = pgsz;
|
||||
}
|
||||
if( iBuf ){
|
||||
int nRead = pgsz - iBuf;
|
||||
if( (pIter->iReadOff + nRead) > pIter->iEof ){
|
||||
nRead = (int)(pIter->iEof - pIter->iReadOff);
|
||||
}
|
||||
rc = sqlite3OsRead(
|
||||
pIter->pFile, &pIter->aBuffer[iBuf], nRead, pIter->iReadOff
|
||||
);
|
||||
assert( rc!=SQLITE_IOERR_SHORT_READ );
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Advance iterator pIter to the next key in its PMA. Return SQLITE_OK if
|
||||
** no error occurs, or an SQLite error code if one does.
|
||||
@@ -590,11 +649,14 @@ static int vdbePmaReaderNext(PmaReader *pIter){
|
||||
u64 nRec = 0; /* Size of record in bytes */
|
||||
|
||||
if( pIter->iReadOff>=pIter->iEof ){
|
||||
IncrMerger *pIncr = pIter->pIncr;
|
||||
int bEof = 1;
|
||||
if( pIter->pIncr ){
|
||||
rc = vdbeIncrSwap(pIter->pIncr);
|
||||
if( rc==SQLITE_OK && pIter->pIncr->bEof==0 ){
|
||||
rc = vdbePmaReaderReinit(pIter);
|
||||
if( pIncr ){
|
||||
rc = vdbeIncrSwap(pIncr);
|
||||
if( rc==SQLITE_OK && pIncr->bEof==0 ){
|
||||
rc = vdbePmaReaderSeek(
|
||||
pIncr->pTask, pIter, &pIncr->aFile[0], pIncr->iStartOff
|
||||
);
|
||||
bEof = 0;
|
||||
}
|
||||
}
|
||||
@@ -633,47 +695,16 @@ static int vdbePmaReaderInit(
|
||||
PmaReader *pIter, /* Iterator to populate */
|
||||
i64 *pnByte /* IN/OUT: Increment this value by PMA size */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
int nBuf = pTask->pgsz;
|
||||
int rc;
|
||||
|
||||
assert( pFile->iEof>iStart );
|
||||
assert( pIter->aAlloc==0 );
|
||||
assert( pIter->aAlloc==0 && pIter->nAlloc==0 );
|
||||
assert( pIter->aBuffer==0 );
|
||||
pIter->pFile = pFile->pFd;
|
||||
pIter->iReadOff = iStart;
|
||||
pIter->nAlloc = 128;
|
||||
pIter->aAlloc = (u8*)sqlite3Malloc(pIter->nAlloc);
|
||||
if( pIter->aAlloc ){
|
||||
/* Try to xFetch() a mapping of the entire temp file. If this is possible,
|
||||
** the PMA will be read via the mapping. Otherwise, use xRead(). */
|
||||
rc = vdbeSorterMapFile(pTask, pFile, &pIter->aMap);
|
||||
}else{
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && pIter->aMap==0 ){
|
||||
pIter->nBuffer = nBuf;
|
||||
pIter->aBuffer = (u8*)sqlite3Malloc(nBuf);
|
||||
if( !pIter->aBuffer ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
int iBuf = iStart % nBuf;
|
||||
if( iBuf ){
|
||||
int nRead = nBuf - iBuf;
|
||||
if( (iStart + nRead) > pFile->iEof ){
|
||||
nRead = (int)(pFile->iEof - iStart);
|
||||
}
|
||||
rc = sqlite3OsRead(
|
||||
pIter->pFile, &pIter->aBuffer[iBuf], nRead, iStart
|
||||
);
|
||||
assert( rc!=SQLITE_IOERR_SHORT_READ );
|
||||
}
|
||||
}
|
||||
}
|
||||
assert( pIter->aMap==0 );
|
||||
|
||||
rc = vdbePmaReaderSeek(pTask, pIter, pFile, iStart);
|
||||
if( rc==SQLITE_OK ){
|
||||
u64 nByte; /* Size of PMA in bytes */
|
||||
pIter->iEof = pFile->iEof;
|
||||
rc = vdbePmaReadVarint(pIter, &nByte);
|
||||
pIter->iEof = pIter->iReadOff + nByte;
|
||||
*pnByte += nByte;
|
||||
@@ -706,7 +737,7 @@ static int vdbeSorterCompare(
|
||||
){
|
||||
UnpackedRecord *r2 = pTask->pUnpacked;
|
||||
if( pKey2 ){
|
||||
sqlite3VdbeRecordUnpack(pTask->pKeyInfo, nKey2, pKey2, r2);
|
||||
sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2);
|
||||
}
|
||||
return sqlite3VdbeRecordCompare(nKey1, pKey1, r2, 0);
|
||||
}
|
||||
@@ -788,19 +819,16 @@ int sqlite3VdbeSorterInit(
|
||||
if( pSorter==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
pKeyInfo = (KeyInfo*)((u8*)pSorter + sz);
|
||||
pSorter->pKeyInfo = pKeyInfo = (KeyInfo*)((u8*)pSorter + sz);
|
||||
memcpy(pKeyInfo, pCsr->pKeyInfo, szKeyInfo);
|
||||
pKeyInfo->db = 0;
|
||||
if( nField && nWorker==0 ) pKeyInfo->nField = nField;
|
||||
pgsz = sqlite3BtreeGetPageSize(db->aDb[0].pBt);
|
||||
|
||||
pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(db->aDb[0].pBt);
|
||||
pSorter->nTask = nWorker + 1;
|
||||
pSorter->bUseThreads = (pSorter->nTask>1);
|
||||
pSorter->db = db;
|
||||
for(i=0; i<pSorter->nTask; i++){
|
||||
SortSubtask *pTask = &pSorter->aTask[i];
|
||||
pTask->pKeyInfo = pKeyInfo;
|
||||
pTask->pgsz = pgsz;
|
||||
pTask->db = db;
|
||||
pTask->pSorter = pSorter;
|
||||
}
|
||||
|
||||
@@ -904,22 +932,22 @@ static void vdbeSorterBlockDebug(
|
||||
|
||||
#if SQLITE_MAX_WORKER_THREADS>0
|
||||
/*
|
||||
** Join thread p.
|
||||
** Join thread pTask->thread.
|
||||
*/
|
||||
static int vdbeSorterJoinThread(SortSubtask *pTask, SorterThread *p){
|
||||
static int vdbeSorterJoinThread(SortSubtask *pTask){
|
||||
int rc = SQLITE_OK;
|
||||
if( p->pThread ){
|
||||
if( pTask->pThread ){
|
||||
#ifdef SQLITE_DEBUG_SORTER_THREADS
|
||||
int bDone = p->bDone;
|
||||
int bDone = pTask->bDone;
|
||||
#endif
|
||||
void *pRet;
|
||||
vdbeSorterBlockDebug(pTask, !bDone, "enter");
|
||||
rc = sqlite3ThreadJoin(p->pThread, &pRet);
|
||||
rc = sqlite3ThreadJoin(pTask->pThread, &pRet);
|
||||
vdbeSorterBlockDebug(pTask, !bDone, "exit");
|
||||
if( rc==SQLITE_OK ) rc = SQLITE_PTR_TO_INT(pRet);
|
||||
assert( p->bDone==1 );
|
||||
p->bDone = 0;
|
||||
p->pThread = 0;
|
||||
assert( pTask->bDone==1 );
|
||||
pTask->bDone = 0;
|
||||
pTask->pThread = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -928,12 +956,12 @@ static int vdbeSorterJoinThread(SortSubtask *pTask, SorterThread *p){
|
||||
** Launch a background thread to run xTask(pIn).
|
||||
*/
|
||||
static int vdbeSorterCreateThread(
|
||||
SorterThread *p, /* Thread object to populate */
|
||||
SortSubtask *pTask, /* Thread will use this task object */
|
||||
void *(*xTask)(void*), /* Routine to run in a separate thread */
|
||||
void *pIn /* Argument passed into xTask() */
|
||||
){
|
||||
assert( p->pThread==0 && p->bDone==0 );
|
||||
return sqlite3ThreadCreate(&p->pThread, xTask, pIn);
|
||||
assert( pTask->pThread==0 && pTask->bDone==0 );
|
||||
return sqlite3ThreadCreate(&pTask->pThread, xTask, pIn);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -945,14 +973,14 @@ static int vdbeSorterJoinAll(VdbeSorter *pSorter, int rcin){
|
||||
int i;
|
||||
for(i=0; i<pSorter->nTask; i++){
|
||||
SortSubtask *pTask = &pSorter->aTask[i];
|
||||
int rc2 = vdbeSorterJoinThread(pTask, &pTask->thread);
|
||||
int rc2 = vdbeSorterJoinThread(pTask);
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
# define vdbeSorterJoinAll(x,rcin) (rcin)
|
||||
# define vdbeSorterJoinThread(pTask,p) SQLITE_OK
|
||||
# define vdbeSorterJoinThread(pTask) SQLITE_OK
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -990,6 +1018,24 @@ static void vdbeMergeEngineFree(MergeEngine *pMerger){
|
||||
sqlite3_free(pMerger);
|
||||
}
|
||||
|
||||
/*
|
||||
** Free all resources associated with the IncrMerger object indicated by
|
||||
** the first argument.
|
||||
*/
|
||||
static void vdbeIncrFree(IncrMerger *pIncr){
|
||||
if( pIncr ){
|
||||
#if SQLITE_MAX_WORKER_THREADS>0
|
||||
if( pIncr->bUseThread ){
|
||||
vdbeSorterJoinThread(pIncr->pTask);
|
||||
if( pIncr->aFile[0].pFd ) sqlite3OsCloseFree(pIncr->aFile[0].pFd);
|
||||
if( pIncr->aFile[1].pFd ) sqlite3OsCloseFree(pIncr->aFile[1].pFd);
|
||||
}
|
||||
#endif
|
||||
vdbeMergeEngineFree(pIncr->pMerger);
|
||||
sqlite3_free(pIncr);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Reset a sorting cursor back to its original empty state.
|
||||
*/
|
||||
@@ -1051,15 +1097,20 @@ static int vdbeSorterOpenTempFile(sqlite3_vfs *pVfs, sqlite3_file **ppFile){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** If it has not already been allocated, allocate the UnpackedRecord
|
||||
** structure at pTask->pUnpacked. Return SQLITE_OK if successful (or
|
||||
** if no allocation was required), or SQLITE_NOMEM otherwise.
|
||||
*/
|
||||
static int vdbeSortAllocUnpacked(SortSubtask *pTask){
|
||||
if( pTask->pUnpacked==0 ){
|
||||
char *pFree;
|
||||
pTask->pUnpacked = sqlite3VdbeAllocUnpackedRecord(
|
||||
pTask->pKeyInfo, 0, 0, &pFree
|
||||
pTask->pSorter->pKeyInfo, 0, 0, &pFree
|
||||
);
|
||||
assert( pTask->pUnpacked==(UnpackedRecord*)pFree );
|
||||
if( pFree==0 ) return SQLITE_NOMEM;
|
||||
pTask->pUnpacked->nField = pTask->pKeyInfo->nField;
|
||||
pTask->pUnpacked->nField = pTask->pSorter->pKeyInfo->nField;
|
||||
pTask->pUnpacked->errCode = 0;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
@@ -1280,6 +1331,7 @@ static void vdbeSorterExtendFile(sqlite3 *db, sqlite3_file *pFile, i64 nByte){
|
||||
** key). The varint is the number of bytes in the blob of data.
|
||||
*/
|
||||
static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){
|
||||
sqlite3 *db = pTask->pSorter->db;
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
PmaWriter writer; /* Object used to write to the file */
|
||||
|
||||
@@ -1295,7 +1347,7 @@ static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){
|
||||
|
||||
/* If the first temporary PMA file has not been opened, open it now. */
|
||||
if( pTask->file.pFd==0 ){
|
||||
rc = vdbeSorterOpenTempFile(pTask->db->pVfs, &pTask->file.pFd);
|
||||
rc = vdbeSorterOpenTempFile(db->pVfs, &pTask->file.pFd);
|
||||
assert( rc!=SQLITE_OK || pTask->file.pFd );
|
||||
assert( pTask->file.iEof==0 );
|
||||
assert( pTask->nPMA==0 );
|
||||
@@ -1303,9 +1355,7 @@ static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){
|
||||
|
||||
/* Try to get the file to memory map */
|
||||
if( rc==SQLITE_OK ){
|
||||
vdbeSorterExtendFile(pTask->db,
|
||||
pTask->file.pFd, pTask->file.iEof + pList->szPMA + 9
|
||||
);
|
||||
vdbeSorterExtendFile(db, pTask->file.pFd, pTask->file.iEof+pList->szPMA+9);
|
||||
}
|
||||
|
||||
/* Sort the list */
|
||||
@@ -1317,7 +1367,7 @@ static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){
|
||||
SorterRecord *p;
|
||||
SorterRecord *pNext = 0;
|
||||
|
||||
vdbePmaWriterInit(pTask->file.pFd, &writer, pTask->pgsz,
|
||||
vdbePmaWriterInit(pTask->file.pFd, &writer, pTask->pSorter->pgsz,
|
||||
pTask->file.iEof);
|
||||
pTask->nPMA++;
|
||||
vdbePmaWriteVarint(&writer, pList->szPMA);
|
||||
@@ -1413,14 +1463,14 @@ static int vdbeSorterNext(
|
||||
}
|
||||
|
||||
/*
|
||||
** The main routine for sorter-thread operations.
|
||||
** The main routine for background threads that write level-0 PMAs.
|
||||
*/
|
||||
static void *vdbeSorterFlushThread(void *pCtx){
|
||||
SortSubtask *pTask = (SortSubtask*)pCtx;
|
||||
int rc; /* Return code */
|
||||
assert( pTask->thread.bDone==0 );
|
||||
assert( pTask->bDone==0 );
|
||||
rc = vdbeSorterListToPMA(pTask, &pTask->list);
|
||||
pTask->thread.bDone = 1;
|
||||
pTask->bDone = 1;
|
||||
return SQLITE_INT_TO_PTR(rc);
|
||||
}
|
||||
|
||||
@@ -1453,10 +1503,10 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){
|
||||
for(i=0; i<nWorker; i++){
|
||||
int iTest = (pSorter->iPrev + i + 1) % nWorker;
|
||||
pTask = &pSorter->aTask[iTest];
|
||||
if( pTask->thread.bDone ){
|
||||
rc = vdbeSorterJoinThread(pTask, &pTask->thread);
|
||||
if( pTask->bDone ){
|
||||
rc = vdbeSorterJoinThread(pTask);
|
||||
}
|
||||
if( pTask->thread.pThread==0 || rc!=SQLITE_OK ) break;
|
||||
if( pTask->pThread==0 || rc!=SQLITE_OK ) break;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -1468,7 +1518,7 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){
|
||||
u8 *aMem = pTask->list.aMemory;
|
||||
void *pCtx = (void*)pTask;
|
||||
|
||||
assert( pTask->thread.pThread==0 && pTask->thread.bDone==0 );
|
||||
assert( pTask->pThread==0 && pTask->bDone==0 );
|
||||
assert( pTask->list.pList==0 );
|
||||
assert( pTask->list.aMemory==0 || pSorter->list.aMemory!=0 );
|
||||
|
||||
@@ -1484,7 +1534,7 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){
|
||||
if( !pSorter->list.aMemory ) return SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
rc = vdbeSorterCreateThread(&pTask->thread, vdbeSorterFlushThread, pCtx);
|
||||
rc = vdbeSorterCreateThread(pTask, vdbeSorterFlushThread, pCtx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1597,13 +1647,14 @@ static int vdbeIncrPopulate(IncrMerger *pIncr){
|
||||
int rc2;
|
||||
i64 iStart = pIncr->iStartOff;
|
||||
SorterFile *pOut = &pIncr->aFile[1];
|
||||
SortSubtask *pTask = pIncr->pTask;
|
||||
MergeEngine *pMerger = pIncr->pMerger;
|
||||
PmaWriter writer;
|
||||
assert( pIncr->bEof==0 );
|
||||
|
||||
vdbeSorterPopulateDebug(pIncr->pTask, "enter");
|
||||
vdbeSorterPopulateDebug(pTask, "enter");
|
||||
|
||||
vdbePmaWriterInit(pOut->pFd, &writer, pIncr->pTask->pgsz, iStart);
|
||||
vdbePmaWriterInit(pOut->pFd, &writer, pTask->pSorter->pgsz, iStart);
|
||||
while( rc==SQLITE_OK ){
|
||||
int dummy;
|
||||
PmaReader *pReader = &pMerger->aIter[ pMerger->aTree[1] ];
|
||||
@@ -1618,36 +1669,60 @@ static int vdbeIncrPopulate(IncrMerger *pIncr){
|
||||
/* Write the next key to the output. */
|
||||
vdbePmaWriteVarint(&writer, nKey);
|
||||
vdbePmaWriteBlob(&writer, pReader->aKey, nKey);
|
||||
rc = vdbeSorterNext(pIncr->pTask, pIncr->pMerger, &dummy);
|
||||
rc = vdbeSorterNext(pTask, pIncr->pMerger, &dummy);
|
||||
}
|
||||
|
||||
rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof);
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
vdbeSorterPopulateDebug(pIncr->pTask, "exit");
|
||||
vdbeSorterPopulateDebug(pTask, "exit");
|
||||
return rc;
|
||||
}
|
||||
|
||||
#if SQLITE_MAX_WORKER_THREADS>0
|
||||
/*
|
||||
** The main routine for background threads that populate aFile[1] of
|
||||
** multi-threaded IncrMerger objects.
|
||||
*/
|
||||
static void *vdbeIncrPopulateThread(void *pCtx){
|
||||
IncrMerger *pIncr = (IncrMerger*)pCtx;
|
||||
void *pRet = SQLITE_INT_TO_PTR( vdbeIncrPopulate(pIncr) );
|
||||
pIncr->thread.bDone = 1;
|
||||
pIncr->pTask->bDone = 1;
|
||||
return pRet;
|
||||
}
|
||||
|
||||
#if SQLITE_MAX_WORKER_THREADS>0
|
||||
/*
|
||||
** Launch a background thread to populate aFile[1] of pIncr.
|
||||
*/
|
||||
static int vdbeIncrBgPopulate(IncrMerger *pIncr){
|
||||
void *pCtx = (void*)pIncr;
|
||||
void *p = (void*)pIncr;
|
||||
assert( pIncr->bUseThread );
|
||||
return vdbeSorterCreateThread(&pIncr->thread, vdbeIncrPopulateThread, pCtx);
|
||||
return vdbeSorterCreateThread(pIncr->pTask, vdbeIncrPopulateThread, p);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** This function is called when the PmaReader corresponding to pIncr has
|
||||
** finished reading the contents of aFile[0]. Its purpose is to "refill"
|
||||
** aFile[0] such that the iterator should start rereading it from the
|
||||
** beginning.
|
||||
**
|
||||
** For single-threaded objects, this is accomplished by literally reading
|
||||
** keys from pIncr->pMerger and repopulating aFile[0].
|
||||
**
|
||||
** For multi-threaded objects, all that is required is to wait until the
|
||||
** background thread is finished (if it is not already) and then swap
|
||||
** aFile[0] and aFile[1] in place. If the contents of pMerger have not
|
||||
** been exhausted, this function also launches a new background thread
|
||||
** to populate the new aFile[1].
|
||||
**
|
||||
** SQLITE_OK is returned on success, or an SQLite error code otherwise.
|
||||
*/
|
||||
static int vdbeIncrSwap(IncrMerger *pIncr){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
#if SQLITE_MAX_WORKER_THREADS>0
|
||||
if( pIncr->bUseThread ){
|
||||
rc = vdbeSorterJoinThread(pIncr->pTask, &pIncr->thread);
|
||||
rc = vdbeSorterJoinThread(pIncr->pTask);
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
SorterFile f0 = pIncr->aFile[0];
|
||||
@@ -1675,20 +1750,9 @@ static int vdbeIncrSwap(IncrMerger *pIncr){
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void vdbeIncrFree(IncrMerger *pIncr){
|
||||
if( pIncr ){
|
||||
#if SQLITE_MAX_WORKER_THREADS>0
|
||||
vdbeSorterJoinThread(pIncr->pTask, &pIncr->thread);
|
||||
if( pIncr->bUseThread ){
|
||||
if( pIncr->aFile[0].pFd ) sqlite3OsCloseFree(pIncr->aFile[0].pFd);
|
||||
if( pIncr->aFile[1].pFd ) sqlite3OsCloseFree(pIncr->aFile[1].pFd);
|
||||
}
|
||||
#endif
|
||||
vdbeMergeEngineFree(pIncr->pMerger);
|
||||
sqlite3_free(pIncr);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Allocate and return a new IncrMerger object to read data from pMerger.
|
||||
*/
|
||||
static IncrMerger *vdbeIncrNew(SortSubtask *pTask, MergeEngine *pMerger){
|
||||
IncrMerger *pIncr = sqlite3_malloc(sizeof(IncrMerger));
|
||||
if( pIncr ){
|
||||
@@ -1701,6 +1765,9 @@ static IncrMerger *vdbeIncrNew(SortSubtask *pTask, MergeEngine *pMerger){
|
||||
return pIncr;
|
||||
}
|
||||
|
||||
/*
|
||||
** Set the "use-threads" flag on object pIncr.
|
||||
*/
|
||||
static void vdbeIncrSetThreads(IncrMerger *pIncr, int bUseThread){
|
||||
if( bUseThread ){
|
||||
pIncr->bUseThread = 1;
|
||||
@@ -1742,6 +1809,7 @@ static int vdbeIncrInit2(PmaReader *pIter, int eMode){
|
||||
IncrMerger *pIncr = pIter->pIncr;
|
||||
if( pIncr ){
|
||||
SortSubtask *pTask = pIncr->pTask;
|
||||
sqlite3 *db = pTask->pSorter->db;
|
||||
|
||||
rc = vdbeIncrInitMerger(pTask, pIncr->pMerger, eMode);
|
||||
|
||||
@@ -1749,10 +1817,10 @@ static int vdbeIncrInit2(PmaReader *pIter, int eMode){
|
||||
if( rc==SQLITE_OK ){
|
||||
if( pIncr->bUseThread==0 ){
|
||||
if( pTask->file2.pFd==0 ){
|
||||
rc = vdbeSorterOpenTempFile(pTask->db->pVfs, &pTask->file2.pFd);
|
||||
rc = vdbeSorterOpenTempFile(db->pVfs, &pTask->file2.pFd);
|
||||
assert( pTask->file2.iEof>0 );
|
||||
if( rc==SQLITE_OK ){
|
||||
vdbeSorterExtendFile(pTask->db,pTask->file2.pFd,pTask->file2.iEof);
|
||||
vdbeSorterExtendFile(db, pTask->file2.pFd, pTask->file2.iEof);
|
||||
pTask->file2.iEof = 0;
|
||||
}
|
||||
}
|
||||
@@ -1762,9 +1830,9 @@ static int vdbeIncrInit2(PmaReader *pIter, int eMode){
|
||||
pTask->file2.iEof += pIncr->mxSz;
|
||||
}
|
||||
}else{
|
||||
rc = vdbeSorterOpenTempFile(pTask->db->pVfs, &pIncr->aFile[0].pFd);
|
||||
rc = vdbeSorterOpenTempFile(db->pVfs, &pIncr->aFile[0].pFd);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = vdbeSorterOpenTempFile(pTask->db->pVfs, &pIncr->aFile[1].pFd);
|
||||
rc = vdbeSorterOpenTempFile(db->pVfs, &pIncr->aFile[1].pFd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1786,15 +1854,13 @@ static int vdbeIncrInit2(PmaReader *pIter, int eMode){
|
||||
static void *vdbeIncrInit2Thread(void *pCtx){
|
||||
PmaReader *pReader = (PmaReader*)pCtx;
|
||||
void *pRet = SQLITE_INT_TO_PTR( vdbeIncrInit2(pReader, INCRINIT2_TASK) );
|
||||
pReader->pIncr->thread.bDone = 1;
|
||||
pReader->pIncr->pTask->bDone = 1;
|
||||
return pRet;
|
||||
}
|
||||
|
||||
static int vdbeIncrBgInit2(PmaReader *pIter){
|
||||
void *pCtx = (void*)pIter;
|
||||
return vdbeSorterCreateThread(
|
||||
&pIter->pIncr->thread, vdbeIncrInit2Thread, pCtx
|
||||
);
|
||||
return vdbeSorterCreateThread(pIter->pIncr->pTask, vdbeIncrInit2Thread, pCtx);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1885,7 +1951,7 @@ static int vdbeAddToBuilder(
|
||||
static int vdbePmaReaderIncrInit(VdbeSorter *pSorter){
|
||||
SortSubtask *pTask0 = &pSorter->aTask[0];
|
||||
MergeEngine *pMain = 0;
|
||||
sqlite3 *db = pTask0->db;
|
||||
sqlite3 *db = pTask0->pSorter->db;
|
||||
int rc = SQLITE_OK;
|
||||
int iTask;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user