summaryrefslogtreecommitdiff
path: root/chromium/third_party/sqlite/src/ext
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/sqlite/src/ext')
-rw-r--r--chromium/third_party/sqlite/src/ext/expert/expert1.test26
-rw-r--r--chromium/third_party/sqlite/src/ext/expert/sqlite3expert.c10
-rw-r--r--chromium/third_party/sqlite/src/ext/fts3/fts3.c4
-rw-r--r--chromium/third_party/sqlite/src/ext/fts3/fts3_expr.c5
-rw-r--r--chromium/third_party/sqlite/src/ext/fts3/fts3_unicode.c1
-rw-r--r--chromium/third_party/sqlite/src/ext/fts3/fts3_write.c9
-rw-r--r--chromium/third_party/sqlite/src/ext/fts5/fts5_expr.c4
-rw-r--r--chromium/third_party/sqlite/src/ext/fts5/fts5_index.c530
-rw-r--r--chromium/third_party/sqlite/src/ext/fts5/fts5_main.c11
-rw-r--r--chromium/third_party/sqlite/src/ext/fts5/fts5_test_tok.c2
-rw-r--r--chromium/third_party/sqlite/src/ext/misc/appendvfs.c330
-rw-r--r--chromium/third_party/sqlite/src/ext/misc/cksumvfs.c52
-rw-r--r--chromium/third_party/sqlite/src/ext/misc/ieee754.c8
-rw-r--r--chromium/third_party/sqlite/src/ext/misc/series.c16
-rw-r--r--chromium/third_party/sqlite/src/ext/misc/shathree.c11
-rw-r--r--chromium/third_party/sqlite/src/ext/misc/wholenumber.c2
-rw-r--r--chromium/third_party/sqlite/src/ext/rbu/rbu.c7
-rw-r--r--chromium/third_party/sqlite/src/ext/rbu/rbudiff.test8
-rw-r--r--chromium/third_party/sqlite/src/ext/rbu/sqlite3rbu.c16
-rw-r--r--chromium/third_party/sqlite/src/ext/session/session2.test58
-rw-r--r--chromium/third_party/sqlite/src/ext/session/sessionmem.test57
-rw-r--r--chromium/third_party/sqlite/src/ext/session/sessionnoop.test187
-rw-r--r--chromium/third_party/sqlite/src/ext/session/sessionwor.test25
-rw-r--r--chromium/third_party/sqlite/src/ext/session/sqlite3session.c489
-rw-r--r--chromium/third_party/sqlite/src/ext/session/sqlite3session.h33
-rw-r--r--chromium/third_party/sqlite/src/ext/session/test_session.c12
26 files changed, 1345 insertions, 568 deletions
diff --git a/chromium/third_party/sqlite/src/ext/expert/expert1.test b/chromium/third_party/sqlite/src/ext/expert/expert1.test
index f49f1f5e5c0..0e6fc826049 100644
--- a/chromium/third_party/sqlite/src/ext/expert/expert1.test
+++ b/chromium/third_party/sqlite/src/ext/expert/expert1.test
@@ -28,6 +28,7 @@ if {[info commands sqlite3_expert_new]==""} {
return
}
+
set CLI [test_binary_name sqlite3]
set CMD [test_binary_name sqlite3_expert]
@@ -367,6 +368,29 @@ do_setup_rec_test $tn.17.5 {
SEARCH TABLE example USING INDEX example_idx_0000cb3f (B=? AND A>?)
}
+do_setup_rec_test $tn.18.0 {
+ CREATE TABLE SomeObject (
+ a INTEGER PRIMARY KEY,
+ x TEXT GENERATED ALWAYS AS(HEX(a)) VIRTUAL
+ );
+} {
+ SELECT x FROM SomeObject;
+} {
+ (no new indexes)
+ SCAN TABLE SomeObject
+}
+do_setup_rec_test $tn.18.1 {
+ CREATE TABLE SomeObject (
+ a INTEGER PRIMARY KEY,
+ x TEXT GENERATED ALWAYS AS(HEX(a)) VIRTUAL
+ );
+} {
+ SELECT * FROM SomeObject WHERE x=?;
+} {
+ CREATE INDEX SomeObject_idx_00000078 ON SomeObject(x);
+ SEARCH TABLE SomeObject USING COVERING INDEX SomeObject_idx_00000078 (x=?)
+}
+
}
proc do_candidates_test {tn sql res} {
@@ -430,5 +454,5 @@ do_execsql_test 5.3 {
t2 t2_idx_0001295b {100 20 5}
}
-
finish_test
+
diff --git a/chromium/third_party/sqlite/src/ext/expert/sqlite3expert.c b/chromium/third_party/sqlite/src/ext/expert/sqlite3expert.c
index c2a6fe3ba9f..863c6a34098 100644
--- a/chromium/third_party/sqlite/src/ext/expert/sqlite3expert.c
+++ b/chromium/third_party/sqlite/src/ext/expert/sqlite3expert.c
@@ -687,7 +687,7 @@ static int idxGetTableInfo(
char *pCsr = 0;
int nPk = 0;
- rc = idxPrintfPrepareStmt(db, &p1, pzErrmsg, "PRAGMA table_info=%Q", zTab);
+ rc = idxPrintfPrepareStmt(db, &p1, pzErrmsg, "PRAGMA table_xinfo=%Q", zTab);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(p1) ){
const char *zCol = (const char*)sqlite3_column_text(p1, 1);
nByte += 1 + STRLEN(zCol);
@@ -1721,10 +1721,12 @@ static int idxPopulateStat1(sqlite3expert *p, char **pzErr){
idxFinalize(&rc, pIndexXInfo);
idxFinalize(&rc, pWrite);
- for(i=0; i<pCtx->nSlot; i++){
- sqlite3_free(pCtx->aSlot[i].z);
+ if( pCtx ){
+ for(i=0; i<pCtx->nSlot; i++){
+ sqlite3_free(pCtx->aSlot[i].z);
+ }
+ sqlite3_free(pCtx);
}
- sqlite3_free(pCtx);
if( rc==SQLITE_OK ){
rc = sqlite3_exec(p->dbm, "ANALYZE sqlite_schema", 0, 0, 0);
diff --git a/chromium/third_party/sqlite/src/ext/fts3/fts3.c b/chromium/third_party/sqlite/src/ext/fts3/fts3.c
index 79dc5c88cea..50fa88db8ab 100644
--- a/chromium/third_party/sqlite/src/ext/fts3/fts3.c
+++ b/chromium/third_party/sqlite/src/ext/fts3/fts3.c
@@ -5212,9 +5212,9 @@ static int fts3EvalNearTrim(
);
if( res ){
nNew = (int)(pOut - pPhrase->doclist.pList) - 1;
- if( nNew>=0 ){
+ assert_fts3_nc( nNew<=pPhrase->doclist.nList && nNew>0 );
+ if( nNew>=0 && nNew<=pPhrase->doclist.nList ){
assert( pPhrase->doclist.pList[nNew]=='\0' );
- assert( nNew<=pPhrase->doclist.nList && nNew>0 );
memset(&pPhrase->doclist.pList[nNew], 0, pPhrase->doclist.nList - nNew);
pPhrase->doclist.nList = nNew;
}
diff --git a/chromium/third_party/sqlite/src/ext/fts3/fts3_expr.c b/chromium/third_party/sqlite/src/ext/fts3/fts3_expr.c
index e19137a03db..7a69a935f0c 100644
--- a/chromium/third_party/sqlite/src/ext/fts3/fts3_expr.c
+++ b/chromium/third_party/sqlite/src/ext/fts3/fts3_expr.c
@@ -493,6 +493,11 @@ static int getNextNode(
if( *zInput=='(' ){
int nConsumed = 0;
pParse->nNest++;
+#if !defined(SQLITE_MAX_EXPR_DEPTH)
+ if( pParse->nNest>1000 ) return SQLITE_ERROR;
+#elif SQLITE_MAX_EXPR_DEPTH>0
+ if( pParse->nNest>SQLITE_MAX_EXPR_DEPTH ) return SQLITE_ERROR;
+#endif
rc = fts3ExprParse(pParse, zInput+1, nInput-1, ppExpr, &nConsumed);
*pnConsumed = (int)(zInput - z) + 1 + nConsumed;
return rc;
diff --git a/chromium/third_party/sqlite/src/ext/fts3/fts3_unicode.c b/chromium/third_party/sqlite/src/ext/fts3/fts3_unicode.c
index b06d9921447..2fd4b39fb77 100644
--- a/chromium/third_party/sqlite/src/ext/fts3/fts3_unicode.c
+++ b/chromium/third_party/sqlite/src/ext/fts3/fts3_unicode.c
@@ -285,6 +285,7 @@ static int unicodeOpen(
pCsr->aInput = (const unsigned char *)aInput;
if( aInput==0 ){
pCsr->nInput = 0;
+ pCsr->aInput = (const unsigned char*)"";
}else if( nInput<0 ){
pCsr->nInput = (int)strlen(aInput);
}else{
diff --git a/chromium/third_party/sqlite/src/ext/fts3/fts3_write.c b/chromium/third_party/sqlite/src/ext/fts3/fts3_write.c
index 092cad9ac5f..bc42fc3d639 100644
--- a/chromium/third_party/sqlite/src/ext/fts3/fts3_write.c
+++ b/chromium/third_party/sqlite/src/ext/fts3/fts3_write.c
@@ -4335,17 +4335,20 @@ static int fts3IncrmergeLoad(
while( reader.aNode && rc==SQLITE_OK ) rc = nodeReaderNext(&reader);
blobGrowBuffer(&pNode->key, reader.term.n, &rc);
if( rc==SQLITE_OK ){
- memcpy(pNode->key.a, reader.term.a, reader.term.n);
+ assert_fts3_nc( reader.term.n>0 || reader.aNode==0 );
+ if( reader.term.n>0 ){
+ memcpy(pNode->key.a, reader.term.a, reader.term.n);
+ }
pNode->key.n = reader.term.n;
if( i>0 ){
char *aBlock = 0;
int nBlock = 0;
pNode = &pWriter->aNodeWriter[i-1];
pNode->iBlock = reader.iChild;
- rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock, 0);
+ rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock,0);
blobGrowBuffer(&pNode->block,
MAX(nBlock, p->nNodeSize)+FTS3_NODE_PADDING, &rc
- );
+ );
if( rc==SQLITE_OK ){
memcpy(pNode->block.a, aBlock, nBlock);
pNode->block.n = nBlock;
diff --git a/chromium/third_party/sqlite/src/ext/fts5/fts5_expr.c b/chromium/third_party/sqlite/src/ext/fts5/fts5_expr.c
index 392dde3ab49..088d8b8caec 100644
--- a/chromium/third_party/sqlite/src/ext/fts5/fts5_expr.c
+++ b/chromium/third_party/sqlite/src/ext/fts5/fts5_expr.c
@@ -1500,8 +1500,8 @@ int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){
}
/* If the iterator is not at a real match, skip forward until it is. */
- while( pRoot->bNomatch ){
- assert( pRoot->bEof==0 && rc==SQLITE_OK );
+ while( pRoot->bNomatch && rc==SQLITE_OK ){
+ assert( pRoot->bEof==0 );
rc = fts5ExprNodeNext(p, pRoot, 0, 0);
}
return rc;
diff --git a/chromium/third_party/sqlite/src/ext/fts5/fts5_index.c b/chromium/third_party/sqlite/src/ext/fts5/fts5_index.c
index f83488e2f69..7a9ad5d713f 100644
--- a/chromium/third_party/sqlite/src/ext/fts5/fts5_index.c
+++ b/chromium/third_party/sqlite/src/ext/fts5/fts5_index.c
@@ -2070,14 +2070,10 @@ static void fts5SegIterNext(
}else{
/* The following could be done by calling fts5SegIterLoadNPos(). But
** this block is particularly performance critical, so equivalent
- ** code is inlined.
- **
- ** Later: Switched back to fts5SegIterLoadNPos() because it supports
- ** detail=none mode. Not ideal.
- */
+ ** code is inlined. */
int nSz;
assert( p->rc==SQLITE_OK );
- assert( pIter->iLeafOffset<=pIter->pLeaf->nn );
+ assert_nc( pIter->iLeafOffset<=pIter->pLeaf->nn );
fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz);
pIter->bDel = (nSz & 0x0001);
pIter->nPos = nSz>>1;
@@ -3069,7 +3065,7 @@ static void fts5ChunkIterate(
int pgno = pSeg->iLeafPgno;
int pgnoSave = 0;
- /* This function does notmwork with detail=none databases. */
+ /* This function does not work with detail=none databases. */
assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE );
if( (pSeg->flags & FTS5_SEGITER_REVERSE)==0 ){
@@ -3082,6 +3078,9 @@ static void fts5ChunkIterate(
fts5DataRelease(pData);
if( nRem<=0 ){
break;
+ }else if( pSeg->pSeg==0 ){
+ p->rc = FTS5_CORRUPT;
+ return;
}else{
pgno++;
pData = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno));
@@ -3133,66 +3132,72 @@ static void fts5SegiterPoslist(
}
/*
-** IN/OUT parameter (*pa) points to a position list n bytes in size. If
-** the position list contains entries for column iCol, then (*pa) is set
-** to point to the sub-position-list for that column and the number of
-** bytes in it returned. Or, if the argument position list does not
-** contain any entries for column iCol, return 0.
+** Parameter pPos points to a buffer containing a position list, size nPos.
+** This function filters it according to pColset (which must be non-NULL)
+** and sets pIter->base.pData/nData to point to the new position list.
+** If memory is required for the new position list, use buffer pIter->poslist.
+** Or, if the new position list is a contiguous subset of the input, set
+** pIter->base.pData/nData to point directly to it.
+**
+** This function is a no-op if *pRc is other than SQLITE_OK when it is
+** called. If an OOM error is encountered, *pRc is set to SQLITE_NOMEM
+** before returning.
*/
-static int fts5IndexExtractCol(
- const u8 **pa, /* IN/OUT: Pointer to poslist */
- int n, /* IN: Size of poslist in bytes */
- int iCol /* Column to extract from poslist */
-){
- int iCurrent = 0; /* Anything before the first 0x01 is col 0 */
- const u8 *p = *pa;
- const u8 *pEnd = &p[n]; /* One byte past end of position list */
-
- while( iCol>iCurrent ){
- /* Advance pointer p until it points to pEnd or an 0x01 byte that is
- ** not part of a varint. Note that it is not possible for a negative
- ** or extremely large varint to occur within an uncorrupted position
- ** list. So the last byte of each varint may be assumed to have a clear
- ** 0x80 bit. */
- while( *p!=0x01 ){
- while( *p++ & 0x80 );
- if( p>=pEnd ) return 0;
- }
- *pa = p++;
- iCurrent = *p++;
- if( iCurrent & 0x80 ){
- p--;
- p += fts5GetVarint32(p, iCurrent);
- }
- }
- if( iCol!=iCurrent ) return 0;
-
- /* Advance pointer p until it points to pEnd or an 0x01 byte that is
- ** not part of a varint */
- while( p<pEnd && *p!=0x01 ){
- while( *p++ & 0x80 );
- }
-
- return p - (*pa);
-}
-
static void fts5IndexExtractColset(
int *pRc,
Fts5Colset *pColset, /* Colset to filter on */
const u8 *pPos, int nPos, /* Position list */
- Fts5Buffer *pBuf /* Output buffer */
+ Fts5Iter *pIter
){
if( *pRc==SQLITE_OK ){
- int i;
- fts5BufferZero(pBuf);
- for(i=0; i<pColset->nCol; i++){
- const u8 *pSub = pPos;
- int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]);
- if( nSub ){
- fts5BufferAppendBlob(pRc, pBuf, nSub, pSub);
+ const u8 *p = pPos;
+ const u8 *aCopy = p;
+ const u8 *pEnd = &p[nPos]; /* One byte past end of position list */
+ int i = 0;
+ int iCurrent = 0;
+
+ if( pColset->nCol>1 && sqlite3Fts5BufferSize(pRc, &pIter->poslist, nPos) ){
+ return;
+ }
+
+ while( 1 ){
+ while( pColset->aiCol[i]<iCurrent ){
+ i++;
+ if( i==pColset->nCol ){
+ pIter->base.pData = pIter->poslist.p;
+ pIter->base.nData = pIter->poslist.n;
+ return;
+ }
+ }
+
+ /* Advance pointer p until it points to pEnd or an 0x01 byte that is
+ ** not part of a varint */
+ while( p<pEnd && *p!=0x01 ){
+ while( *p++ & 0x80 );
+ }
+
+ if( pColset->aiCol[i]==iCurrent ){
+ if( pColset->nCol==1 ){
+ pIter->base.pData = aCopy;
+ pIter->base.nData = p-aCopy;
+ return;
+ }
+ fts5BufferSafeAppendBlob(&pIter->poslist, aCopy, p-aCopy);
+ }
+ if( p==pEnd ){
+ pIter->base.pData = pIter->poslist.p;
+ pIter->base.nData = pIter->poslist.n;
+ return;
+ }
+ aCopy = p++;
+ iCurrent = *p++;
+ if( iCurrent & 0x80 ){
+ p--;
+ p += fts5GetVarint32(p, iCurrent);
}
}
}
+
}
/*
@@ -3312,16 +3317,9 @@ static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){
/* All data is stored on the current page. Populate the output
** variables to point into the body of the page object. */
const u8 *a = &pSeg->pLeaf->p[pSeg->iLeafOffset];
- if( pColset->nCol==1 ){
- pIter->base.nData = fts5IndexExtractCol(&a, pSeg->nPos,pColset->aiCol[0]);
- pIter->base.pData = a;
- }else{
- int *pRc = &pIter->pIndex->rc;
- fts5BufferZero(&pIter->poslist);
- fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, &pIter->poslist);
- pIter->base.pData = pIter->poslist.p;
- pIter->base.nData = pIter->poslist.n;
- }
+ int *pRc = &pIter->pIndex->rc;
+ fts5BufferZero(&pIter->poslist);
+ fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, pIter);
}else{
/* The data is distributed over two or more pages. Copy it into the
** Fts5Iter.poslist buffer and then set the output pointer to point
@@ -4804,7 +4802,7 @@ static void fts5AppendPoslist(
static void fts5DoclistIterNext(Fts5DoclistIter *pIter){
u8 *p = pIter->aPoslist + pIter->nSize + pIter->nPoslist;
- assert( pIter->aPoslist );
+ assert( pIter->aPoslist || (p==0 && pIter->aPoslist==0) );
if( p>=pIter->aEof ){
pIter->aPoslist = 0;
}else{
@@ -4824,6 +4822,9 @@ static void fts5DoclistIterNext(Fts5DoclistIter *pIter){
}
pIter->aPoslist = p;
+ if( &pIter->aPoslist[pIter->nPoslist]>pIter->aEof ){
+ pIter->aPoslist = 0;
+ }
}
}
@@ -4832,9 +4833,11 @@ static void fts5DoclistIterInit(
Fts5DoclistIter *pIter
){
memset(pIter, 0, sizeof(*pIter));
- pIter->aPoslist = pBuf->p;
- pIter->aEof = &pBuf->p[pBuf->n];
- fts5DoclistIterNext(pIter);
+ if( pBuf->n>0 ){
+ pIter->aPoslist = pBuf->p;
+ pIter->aEof = &pBuf->p[pBuf->n];
+ fts5DoclistIterNext(pIter);
+ }
}
#if 0
@@ -4888,16 +4891,20 @@ static void fts5NextRowid(Fts5Buffer *pBuf, int *piOff, i64 *piRowid){
static void fts5MergeRowidLists(
Fts5Index *p, /* FTS5 backend object */
Fts5Buffer *p1, /* First list to merge */
- Fts5Buffer *p2 /* Second list to merge */
+ int nBuf, /* Number of entries in apBuf[] */
+ Fts5Buffer *aBuf /* Array of other lists to merge into p1 */
){
int i1 = 0;
int i2 = 0;
i64 iRowid1 = 0;
i64 iRowid2 = 0;
i64 iOut = 0;
-
+ Fts5Buffer *p2 = &aBuf[0];
Fts5Buffer out;
+
+ (void)nBuf;
memset(&out, 0, sizeof(out));
+ assert( nBuf==1 );
sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n);
if( p->rc ) return;
@@ -4924,180 +4931,213 @@ static void fts5MergeRowidLists(
fts5BufferFree(&out);
}
+typedef struct PrefixMerger PrefixMerger;
+struct PrefixMerger {
+ Fts5DoclistIter iter; /* Doclist iterator */
+ i64 iPos; /* For iterating through a position list */
+ int iOff;
+ u8 *aPos;
+ PrefixMerger *pNext; /* Next in docid/poslist order */
+};
+
+static void fts5PrefixMergerInsertByRowid(
+ PrefixMerger **ppHead,
+ PrefixMerger *p
+){
+ if( p->iter.aPoslist ){
+ PrefixMerger **pp = ppHead;
+ while( *pp && p->iter.iRowid>(*pp)->iter.iRowid ){
+ pp = &(*pp)->pNext;
+ }
+ p->pNext = *pp;
+ *pp = p;
+ }
+}
+
+static void fts5PrefixMergerInsertByPosition(
+ PrefixMerger **ppHead,
+ PrefixMerger *p
+){
+ if( p->iPos>=0 ){
+ PrefixMerger **pp = ppHead;
+ while( *pp && p->iPos>(*pp)->iPos ){
+ pp = &(*pp)->pNext;
+ }
+ p->pNext = *pp;
+ *pp = p;
+ }
+}
+
+
/*
-** Buffers p1 and p2 contain doclists. This function merges the content
-** of the two doclists together and sets buffer p1 to the result before
-** returning.
-**
-** If an error occurs, an error code is left in p->rc. If an error has
-** already occurred, this function is a no-op.
+** Array aBuf[] contains nBuf doclists. These are all merged in with the
+** doclist in buffer p1.
*/
static void fts5MergePrefixLists(
Fts5Index *p, /* FTS5 backend object */
Fts5Buffer *p1, /* First list to merge */
- Fts5Buffer *p2 /* Second list to merge */
+ int nBuf, /* Number of buffers in array aBuf[] */
+ Fts5Buffer *aBuf /* Other lists to merge in */
){
- if( p2->n ){
- i64 iLastRowid = 0;
- Fts5DoclistIter i1;
- Fts5DoclistIter i2;
- Fts5Buffer out = {0, 0, 0};
- Fts5Buffer tmp = {0, 0, 0};
-
- /* The maximum size of the output is equal to the sum of the two
- ** input sizes + 1 varint (9 bytes). The extra varint is because if the
- ** first rowid in one input is a large negative number, and the first in
- ** the other a non-negative number, the delta for the non-negative
- ** number will be larger on disk than the literal integer value
- ** was.
- **
- ** Or, if the input position-lists are corrupt, then the output might
- ** include up to 2 extra 10-byte positions created by interpreting -1
- ** (the value PoslistNext64() uses for EOF) as a position and appending
- ** it to the output. This can happen at most once for each input
- ** position-list, hence two 10 byte paddings. */
- if( sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n + 9+10+10) ) return;
- fts5DoclistIterInit(p1, &i1);
- fts5DoclistIterInit(p2, &i2);
-
- while( 1 ){
- if( i1.iRowid<i2.iRowid ){
- /* Copy entry from i1 */
- fts5MergeAppendDocid(&out, iLastRowid, i1.iRowid);
- fts5BufferSafeAppendBlob(&out, i1.aPoslist, i1.nPoslist+i1.nSize);
- fts5DoclistIterNext(&i1);
- if( i1.aPoslist==0 ) break;
- assert( out.n<=((i1.aPoslist-p1->p) + (i2.aPoslist-p2->p)+9+10+10) );
- }
- else if( i2.iRowid!=i1.iRowid ){
- /* Copy entry from i2 */
- fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid);
- fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.nPoslist+i2.nSize);
- fts5DoclistIterNext(&i2);
- if( i2.aPoslist==0 ) break;
- assert( out.n<=((i1.aPoslist-p1->p) + (i2.aPoslist-p2->p)+9+10+10) );
+#define fts5PrefixMergerNextPosition(p) \
+ sqlite3Fts5PoslistNext64((p)->aPos,(p)->iter.nPoslist,&(p)->iOff,&(p)->iPos);
+#define FTS5_MERGE_NLIST 16
+ PrefixMerger aMerger[FTS5_MERGE_NLIST];
+ PrefixMerger *pHead = 0;
+ int i;
+ int nOut = 0;
+ Fts5Buffer out = {0, 0, 0};
+ Fts5Buffer tmp = {0, 0, 0};
+ i64 iLastRowid = 0;
+
+ /* Initialize a doclist-iterator for each input buffer. Arrange them in
+ ** a linked-list starting at pHead in ascending order of rowid. Avoid
+ ** linking any iterators already at EOF into the linked list at all. */
+ assert( nBuf+1<=sizeof(aMerger)/sizeof(aMerger[0]) );
+ memset(aMerger, 0, sizeof(PrefixMerger)*(nBuf+1));
+ pHead = &aMerger[nBuf];
+ fts5DoclistIterInit(p1, &pHead->iter);
+ for(i=0; i<nBuf; i++){
+ fts5DoclistIterInit(&aBuf[i], &aMerger[i].iter);
+ fts5PrefixMergerInsertByRowid(&pHead, &aMerger[i]);
+ nOut += aBuf[i].n;
+ }
+ if( nOut==0 ) return;
+ nOut += p1->n + 9 + 10*nBuf;
+
+ /* The maximum size of the output is equal to the sum of the
+ ** input sizes + 1 varint (9 bytes). The extra varint is because if the
+ ** first rowid in one input is a large negative number, and the first in
+ ** the other a non-negative number, the delta for the non-negative
+ ** number will be larger on disk than the literal integer value
+ ** was.
+ **
+ ** Or, if the input position-lists are corrupt, then the output might
+ ** include up to (nBuf+1) extra 10-byte positions created by interpreting -1
+ ** (the value PoslistNext64() uses for EOF) as a position and appending
+ ** it to the output. This can happen at most once for each input
+ ** position-list, hence (nBuf+1) 10 byte paddings. */
+ if( sqlite3Fts5BufferSize(&p->rc, &out, nOut) ) return;
+
+ while( pHead ){
+ fts5MergeAppendDocid(&out, iLastRowid, pHead->iter.iRowid);
+
+ if( pHead->pNext && iLastRowid==pHead->pNext->iter.iRowid ){
+ /* Merge data from two or more poslists */
+ i64 iPrev = 0;
+ int nTmp = FTS5_DATA_ZERO_PADDING;
+ int nMerge = 0;
+ PrefixMerger *pSave = pHead;
+ PrefixMerger *pThis = 0;
+ int nTail = 0;
+
+ pHead = 0;
+ while( pSave && pSave->iter.iRowid==iLastRowid ){
+ PrefixMerger *pNext = pSave->pNext;
+ pSave->iOff = 0;
+ pSave->iPos = 0;
+ pSave->aPos = &pSave->iter.aPoslist[pSave->iter.nSize];
+ fts5PrefixMergerNextPosition(pSave);
+ nTmp += pSave->iter.nPoslist + 10;
+ nMerge++;
+ fts5PrefixMergerInsertByPosition(&pHead, pSave);
+ pSave = pNext;
}
- else{
- /* Merge the two position lists. */
- i64 iPos1 = 0;
- i64 iPos2 = 0;
- int iOff1 = 0;
- int iOff2 = 0;
- u8 *a1 = &i1.aPoslist[i1.nSize];
- u8 *a2 = &i2.aPoslist[i2.nSize];
- int nCopy;
- u8 *aCopy;
-
- i64 iPrev = 0;
- Fts5PoslistWriter writer;
- memset(&writer, 0, sizeof(writer));
-
- /* See the earlier comment in this function for an explanation of why
- ** corrupt input position lists might cause the output to consume
- ** at most 20 bytes of unexpected space. */
- fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid);
- fts5BufferZero(&tmp);
- sqlite3Fts5BufferSize(&p->rc, &tmp,
- i1.nPoslist + i2.nPoslist + 10 + 10 + FTS5_DATA_ZERO_PADDING
- );
- if( p->rc ) break;
- sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
- sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
- assert_nc( iPos1>=0 && iPos2>=0 );
+ if( pHead==0 || pHead->pNext==0 ){
+ p->rc = FTS5_CORRUPT;
+ break;
+ }
- if( iPos1<iPos2 ){
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
- sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
- }else{
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
- sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
- }
- if( iPos1>=0 && iPos2>=0 ){
- while( 1 ){
- if( iPos1<iPos2 ){
- if( iPos1!=iPrev ){
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
- }
- sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
- if( iPos1<0 ) break;
- }else{
- assert_nc( iPos2!=iPrev );
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
- sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
- if( iPos2<0 ) break;
- }
- }
+ /* See the earlier comment in this function for an explanation of why
+ ** corrupt input position lists might cause the output to consume
+ ** at most nMerge*10 bytes of unexpected space. */
+ if( sqlite3Fts5BufferSize(&p->rc, &tmp, nTmp+nMerge*10) ){
+ break;
+ }
+ fts5BufferZero(&tmp);
+
+ pThis = pHead;
+ pHead = pThis->pNext;
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos);
+ fts5PrefixMergerNextPosition(pThis);
+ fts5PrefixMergerInsertByPosition(&pHead, pThis);
+
+ while( pHead->pNext ){
+ pThis = pHead;
+ if( pThis->iPos!=iPrev ){
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos);
}
+ fts5PrefixMergerNextPosition(pThis);
+ pHead = pThis->pNext;
+ fts5PrefixMergerInsertByPosition(&pHead, pThis);
+ }
- if( iPos1>=0 ){
- if( iPos1!=iPrev ){
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
- }
- aCopy = &a1[iOff1];
- nCopy = i1.nPoslist - iOff1;
- }else{
- assert_nc( iPos2>=0 && iPos2!=iPrev );
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
- aCopy = &a2[iOff2];
- nCopy = i2.nPoslist - iOff2;
- }
- if( nCopy>0 ){
- fts5BufferSafeAppendBlob(&tmp, aCopy, nCopy);
- }
+ if( pHead->iPos!=iPrev ){
+ sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pHead->iPos);
+ }
+ nTail = pHead->iter.nPoslist - pHead->iOff;
- /* WRITEPOSLISTSIZE */
- assert_nc( tmp.n<=i1.nPoslist+i2.nPoslist );
- assert( tmp.n<=i1.nPoslist+i2.nPoslist+10+10 );
- if( tmp.n>i1.nPoslist+i2.nPoslist ){
- if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
- break;
+ /* WRITEPOSLISTSIZE */
+ assert( tmp.n+nTail<=nTmp );
+ if( tmp.n+nTail>nTmp-FTS5_DATA_ZERO_PADDING ){
+ if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
+ break;
+ }
+ fts5BufferSafeAppendVarint(&out, (tmp.n+nTail) * 2);
+ fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n);
+ if( nTail>0 ){
+ fts5BufferSafeAppendBlob(&out, &pHead->aPos[pHead->iOff], nTail);
+ }
+
+ pHead = pSave;
+ for(i=0; i<nBuf+1; i++){
+ PrefixMerger *pX = &aMerger[i];
+ if( pX->iter.aPoslist && pX->iter.iRowid==iLastRowid ){
+ fts5DoclistIterNext(&pX->iter);
+ fts5PrefixMergerInsertByRowid(&pHead, pX);
}
- fts5BufferSafeAppendVarint(&out, tmp.n * 2);
- fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n);
- fts5DoclistIterNext(&i1);
- fts5DoclistIterNext(&i2);
- assert_nc( out.n<=(p1->n+p2->n+9) );
- if( i1.aPoslist==0 || i2.aPoslist==0 ) break;
- assert( out.n<=((i1.aPoslist-p1->p) + (i2.aPoslist-p2->p)+9+10+10) );
}
- }
- if( i1.aPoslist ){
- fts5MergeAppendDocid(&out, iLastRowid, i1.iRowid);
- fts5BufferSafeAppendBlob(&out, i1.aPoslist, i1.aEof - i1.aPoslist);
- }
- else if( i2.aPoslist ){
- fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid);
- fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.aEof - i2.aPoslist);
+ }else{
+ /* Copy poslist from pHead to output */
+ PrefixMerger *pThis = pHead;
+ Fts5DoclistIter *pI = &pThis->iter;
+ fts5BufferSafeAppendBlob(&out, pI->aPoslist, pI->nPoslist+pI->nSize);
+ fts5DoclistIterNext(pI);
+ pHead = pThis->pNext;
+ fts5PrefixMergerInsertByRowid(&pHead, pThis);
}
- assert_nc( out.n<=(p1->n+p2->n+9) );
-
- fts5BufferFree(p1);
- fts5BufferFree(&tmp);
- memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING);
- *p1 = out;
}
+
+ fts5BufferFree(p1);
+ fts5BufferFree(&tmp);
+ memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING);
+ *p1 = out;
}
static void fts5SetupPrefixIter(
Fts5Index *p, /* Index to read from */
int bDesc, /* True for "ORDER BY rowid DESC" */
- const u8 *pToken, /* Buffer containing prefix to match */
+ int iIdx, /* Index to scan for data */
+ u8 *pToken, /* Buffer containing prefix to match */
int nToken, /* Size of buffer pToken in bytes */
Fts5Colset *pColset, /* Restrict matches to these columns */
Fts5Iter **ppIter /* OUT: New iterator */
){
Fts5Structure *pStruct;
Fts5Buffer *aBuf;
- const int nBuf = 32;
+ int nBuf = 32;
+ int nMerge = 1;
- void (*xMerge)(Fts5Index*, Fts5Buffer*, Fts5Buffer*);
+ void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*);
void (*xAppend)(Fts5Index*, i64, Fts5Iter*, Fts5Buffer*);
if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){
xMerge = fts5MergeRowidLists;
xAppend = fts5AppendRowid;
}else{
+ nMerge = FTS5_MERGE_NLIST-1;
+ nBuf = nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */
xMerge = fts5MergePrefixLists;
xAppend = fts5AppendPoslist;
}
@@ -5117,6 +5157,27 @@ static void fts5SetupPrefixIter(
int bNewTerm = 1;
memset(&doclist, 0, sizeof(doclist));
+ if( iIdx!=0 ){
+ int dummy = 0;
+ const int f2 = FTS5INDEX_QUERY_SKIPEMPTY|FTS5INDEX_QUERY_NOOUTPUT;
+ pToken[0] = FTS5_MAIN_PREFIX;
+ fts5MultiIterNew(p, pStruct, f2, pColset, pToken, nToken, -1, 0, &p1);
+ fts5IterSetOutputCb(&p->rc, p1);
+ for(;
+ fts5MultiIterEof(p, p1)==0;
+ fts5MultiIterNext2(p, p1, &dummy)
+ ){
+ Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ];
+ p1->xSetOutputs(p1, pSeg);
+ if( p1->base.nData ){
+ xAppend(p, p1->base.iRowid-iLastRowid, p1, &doclist);
+ iLastRowid = p1->base.iRowid;
+ }
+ }
+ fts5MultiIterFree(p1);
+ }
+
+ pToken[0] = FTS5_MAIN_PREFIX + iIdx;
fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1);
fts5IterSetOutputCb(&p->rc, p1);
for( /* no-op */ ;
@@ -5137,13 +5198,21 @@ static void fts5SetupPrefixIter(
if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){
for(i=0; p->rc==SQLITE_OK && doclist.n; i++){
- assert( i<nBuf );
- if( aBuf[i].n==0 ){
- fts5BufferSwap(&doclist, &aBuf[i]);
- fts5BufferZero(&doclist);
- }else{
- xMerge(p, &doclist, &aBuf[i]);
- fts5BufferZero(&aBuf[i]);
+ int i1 = i*nMerge;
+ int iStore;
+ assert( i1+nMerge<=nBuf );
+ for(iStore=i1; iStore<i1+nMerge; iStore++){
+ if( aBuf[iStore].n==0 ){
+ fts5BufferSwap(&doclist, &aBuf[iStore]);
+ fts5BufferZero(&doclist);
+ break;
+ }
+ }
+ if( iStore==i1+nMerge ){
+ xMerge(p, &doclist, nMerge, &aBuf[i1]);
+ for(iStore=i1; iStore<i1+nMerge; iStore++){
+ fts5BufferZero(&aBuf[iStore]);
+ }
}
}
iLastRowid = 0;
@@ -5153,11 +5222,15 @@ static void fts5SetupPrefixIter(
iLastRowid = p1->base.iRowid;
}
- for(i=0; i<nBuf; i++){
+ assert( (nBuf%nMerge)==0 );
+ for(i=0; i<nBuf; i+=nMerge){
+ int iFree;
if( p->rc==SQLITE_OK ){
- xMerge(p, &doclist, &aBuf[i]);
+ xMerge(p, &doclist, nMerge, &aBuf[i]);
+ }
+ for(iFree=i; iFree<i+nMerge; iFree++){
+ fts5BufferFree(&aBuf[iFree]);
}
- fts5BufferFree(&aBuf[i]);
}
fts5MultiIterFree(p1);
@@ -5412,6 +5485,7 @@ int sqlite3Fts5IndexQuery(
if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){
int iIdx = 0; /* Index to search */
+ int iPrefixIdx = 0; /* +1 prefix index */
if( nToken ) memcpy(&buf.p[1], pToken, nToken);
/* Figure out which index to search and set iIdx accordingly. If this
@@ -5433,7 +5507,9 @@ int sqlite3Fts5IndexQuery(
if( flags & FTS5INDEX_QUERY_PREFIX ){
int nChar = fts5IndexCharlen(pToken, nToken);
for(iIdx=1; iIdx<=pConfig->nPrefix; iIdx++){
- if( pConfig->aPrefix[iIdx-1]==nChar ) break;
+ int nIdxChar = pConfig->aPrefix[iIdx-1];
+ if( nIdxChar==nChar ) break;
+ if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx;
}
}
@@ -5450,8 +5526,7 @@ int sqlite3Fts5IndexQuery(
}else{
/* Scan multiple terms in the main index */
int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0;
- buf.p[0] = FTS5_MAIN_PREFIX;
- fts5SetupPrefixIter(p, bDesc, buf.p, nToken+1, pColset, &pRet);
+ fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet);
assert( p->rc!=SQLITE_OK || pRet->pColset==0 );
fts5IterSetOutputCb(&p->rc, pRet);
if( p->rc==SQLITE_OK ){
@@ -5524,8 +5599,9 @@ int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){
const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){
int n;
const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n);
+ assert_nc( z || n<=1 );
*pn = n-1;
- return &z[1];
+ return (z ? &z[1] : 0);
}
/*
diff --git a/chromium/third_party/sqlite/src/ext/fts5/fts5_main.c b/chromium/third_party/sqlite/src/ext/fts5/fts5_main.c
index 788821e6b82..8a7af1894a5 100644
--- a/chromium/third_party/sqlite/src/ext/fts5/fts5_main.c
+++ b/chromium/third_party/sqlite/src/ext/fts5/fts5_main.c
@@ -2177,7 +2177,8 @@ static int fts5ApiPhraseFirst(
int n;
int rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n);
if( rc==SQLITE_OK ){
- pIter->b = &pIter->a[n];
+ assert( pIter->a || n==0 );
+ pIter->b = (pIter->a ? &pIter->a[n] : 0);
*piCol = 0;
*piOff = 0;
fts5ApiPhraseNext(pCtx, pIter, piCol, piOff);
@@ -2236,7 +2237,8 @@ static int fts5ApiPhraseFirstColumn(
rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n);
}
if( rc==SQLITE_OK ){
- pIter->b = &pIter->a[n];
+ assert( pIter->a || n==0 );
+ pIter->b = (pIter->a ? &pIter->a[n] : 0);
*piCol = 0;
fts5ApiPhraseNextColumn(pCtx, pIter, piCol);
}
@@ -2244,7 +2246,8 @@ static int fts5ApiPhraseFirstColumn(
int n;
rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n);
if( rc==SQLITE_OK ){
- pIter->b = &pIter->a[n];
+ assert( pIter->a || n==0 );
+ pIter->b = (pIter->a ? &pIter->a[n] : 0);
if( n<=0 ){
*piCol = -1;
}else if( pIter->a[0]==0x01 ){
@@ -2722,7 +2725,7 @@ int sqlite3Fts5GetTokenizer(
*pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]);
}else{
rc = pMod->x.xCreate(
- pMod->pUserData, &azArg[1], (nArg?nArg-1:0), &pConfig->pTok
+ pMod->pUserData, (azArg?&azArg[1]:0), (nArg?nArg-1:0), &pConfig->pTok
);
pConfig->pTokApi = &pMod->x;
if( rc!=SQLITE_OK ){
diff --git a/chromium/third_party/sqlite/src/ext/fts5/fts5_test_tok.c b/chromium/third_party/sqlite/src/ext/fts5/fts5_test_tok.c
index b5d4162fa83..a5d839da66e 100644
--- a/chromium/third_party/sqlite/src/ext/fts5/fts5_test_tok.c
+++ b/chromium/third_party/sqlite/src/ext/fts5/fts5_test_tok.c
@@ -211,7 +211,7 @@ static int fts5tokConnectMethod(
rc = pApi->xFindTokenizer(pApi, zModule, &pTokCtx, &pTab->tok);
if( rc==SQLITE_OK ){
- const char **azArg = (const char **)&azDequote[1];
+ const char **azArg = (nDequote>1 ? (const char **)&azDequote[1] : 0);
int nArg = nDequote>0 ? nDequote-1 : 0;
rc = pTab->tok.xCreate(pTokCtx, azArg, nArg, &pTab->pTok);
}
diff --git a/chromium/third_party/sqlite/src/ext/misc/appendvfs.c b/chromium/third_party/sqlite/src/ext/misc/appendvfs.c
index 14260efb52c..4ea5b1c2cb4 100644
--- a/chromium/third_party/sqlite/src/ext/misc/appendvfs.c
+++ b/chromium/third_party/sqlite/src/ext/misc/appendvfs.c
@@ -14,24 +14,23 @@
** appended onto the end of some other file, such as an executable.
**
** A special record must appear at the end of the file that identifies the
-** file as an appended database and provides an offset to page 1. For
-** best performance page 1 should be located at a disk page boundary, though
-** that is not required.
+** file as an appended database and provides the offset to the first page
+** of the exposed content. (Or, it is the length of the content prefix.)
+** For best performance page 1 should be located at a disk page boundary,
+** though that is not required.
**
** When opening a database using this VFS, the connection might treat
-** the file as an ordinary SQLite database, or it might treat is as a
-** database appended onto some other file. Here are the rules:
+** the file as an ordinary SQLite database, or it might treat it as a
+** database appended onto some other file. The decision is made by
+** applying the following rules in order:
**
-** (1) When opening a new empty file, that file is treated as an ordinary
-** database.
+** (1) An empty file is an ordinary database.
**
-** (2) When opening a file that begins with the standard SQLite prefix
-** string "SQLite format 3", that file is treated as an ordinary
-** database.
+** (2) If the file ends with the appendvfs trailer string
+** "Start-Of-SQLite3-NNNNNNNN" that file is an appended database.
**
-** (3) When opening a file that ends with the appendvfs trailer string
-** "Start-Of-SQLite3-NNNNNNNN" that file is treated as an appended
-** database.
+** (3) If the file begins with the standard SQLite prefix string
+** "SQLite format 3", that file is an ordinary database.
**
** (4) If none of the above apply and the SQLITE_OPEN_CREATE flag is
** set, then a new database is appended to the already existing file.
@@ -39,13 +38,13 @@
** (5) Otherwise, SQLITE_CANTOPEN is returned.
**
** To avoid unnecessary complications with the PENDING_BYTE, the size of
-** the file containing the database is limited to 1GB. This VFS will refuse
-** to read or write past the 1GB mark. This restriction might be lifted in
-** future versions. For now, if you need a large database, then keep the
-** database in a separate file.
+** the file containing the database is limited to 1GiB. (1073741824 bytes)
+** This VFS will not read or write past the 1GiB mark. This restriction
+** might be lifted in future versions. For now, if you need a larger
+** database, then keep it in a separate file.
**
-** If the file being opened is not an appended database, then this shim is
-** a pass-through into the default underlying VFS.
+** If the file being opened is a plain database (not an appended one), then
+** this shim is a pass-through into the default underlying VFS. (rule 3)
**/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
@@ -58,17 +57,27 @@ SQLITE_EXTENSION_INIT1
** 123456789 123456789 12345
**
** The NNNNNNNN represents a 64-bit big-endian unsigned integer which is
-** the offset to page 1.
+** the offset to page 1, and also the length of the prefix content.
*/
#define APND_MARK_PREFIX "Start-Of-SQLite3-"
#define APND_MARK_PREFIX_SZ 17
-#define APND_MARK_SIZE 25
+#define APND_MARK_FOS_SZ 8
+#define APND_MARK_SIZE (APND_MARK_PREFIX_SZ+APND_MARK_FOS_SZ)
/*
** Maximum size of the combined prefix + database + append-mark. This
** must be less than 0x40000000 to avoid locking issues on Windows.
*/
-#define APND_MAX_SIZE (65536*15259)
+#define APND_MAX_SIZE (0x40000000)
+
+/*
+** Try to align the database to an even multiple of APND_ROUNDUP bytes.
+*/
+#ifndef APND_ROUNDUP
+#define APND_ROUNDUP 4096
+#endif
+#define APND_ALIGN_MASK ((sqlite3_int64)(APND_ROUNDUP-1))
+#define APND_START_ROUNDUP(fsz) (((fsz)+APND_ALIGN_MASK) & ~APND_ALIGN_MASK)
/*
** Forward declaration of objects used by this utility
@@ -82,11 +91,45 @@ typedef struct ApndFile ApndFile;
#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData))
#define ORIGFILE(p) ((sqlite3_file*)(((ApndFile*)(p))+1))
-/* An open file */
+/* An open appendvfs file
+**
+** An instance of this structure describes the appended database file.
+** A separate sqlite3_file object is always appended. The appended
+** sqlite3_file object (which can be accessed using ORIGFILE()) describes
+** the entire file, including the prefix, the database, and the
+** append-mark.
+**
+** The structure of an AppendVFS database is like this:
+**
+** +-------------+---------+----------+-------------+
+** | prefix-file | padding | database | append-mark |
+** +-------------+---------+----------+-------------+
+** ^ ^
+** | |
+** iPgOne iMark
+**
+**
+** "prefix file" - file onto which the database has been appended.
+** "padding" - zero or more bytes inserted so that "database"
+** starts on an APND_ROUNDUP boundary
+** "database" - The SQLite database file
+** "append-mark" - The 25-byte "Start-Of-SQLite3-NNNNNNNN" that indicates
+** the offset from the start of prefix-file to the start
+** of "database".
+**
+** The size of the database is iMark - iPgOne.
+**
+** The NNNNNNNN in the "Start-Of-SQLite3-NNNNNNNN" suffix is the value
+** of iPgOne stored as a big-ending 64-bit integer.
+**
+** iMark will be the size of the underlying file minus 25 (APND_MARKSIZE).
+** Or, iMark is -1 to indicate that it has not yet been written.
+*/
struct ApndFile {
- sqlite3_file base; /* IO methods */
- sqlite3_int64 iPgOne; /* File offset to page 1 */
- sqlite3_int64 iMark; /* Start of the append-mark */
+ sqlite3_file base; /* Subclass. MUST BE FIRST! */
+ sqlite3_int64 iPgOne; /* Offset to the start of the database */
+ sqlite3_int64 iMark; /* Offset of the append mark. -1 if unwritten */
+ /* Always followed by another sqlite3_file that describes the whole file */
};
/*
@@ -178,8 +221,6 @@ static const sqlite3_io_methods apnd_io_methods = {
apndUnfetch /* xUnfetch */
};
-
-
/*
** Close an apnd-file.
*/
@@ -197,22 +238,37 @@ static int apndRead(
int iAmt,
sqlite_int64 iOfst
){
- ApndFile *p = (ApndFile *)pFile;
+ ApndFile *paf = (ApndFile *)pFile;
pFile = ORIGFILE(pFile);
- return pFile->pMethods->xRead(pFile, zBuf, iAmt, iOfst+p->iPgOne);
+ return pFile->pMethods->xRead(pFile, zBuf, iAmt, paf->iPgOne+iOfst);
}
/*
-** Add the append-mark onto the end of the file.
+** Add the append-mark onto what should become the end of the file.
+* If and only if this succeeds, internal ApndFile.iMark is updated.
+* Parameter iWriteEnd is the appendvfs-relative offset of the new mark.
*/
-static int apndWriteMark(ApndFile *p, sqlite3_file *pFile){
- int i;
+static int apndWriteMark(
+ ApndFile *paf,
+ sqlite3_file *pFile,
+ sqlite_int64 iWriteEnd
+){
+ sqlite_int64 iPgOne = paf->iPgOne;
unsigned char a[APND_MARK_SIZE];
+ int i = APND_MARK_FOS_SZ;
+ int rc;
+ assert(pFile == ORIGFILE(paf));
memcpy(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ);
- for(i=0; i<8; i++){
- a[APND_MARK_PREFIX_SZ+i] = (p->iPgOne >> (56 - i*8)) & 0xff;
+ while( --i >= 0 ){
+ a[APND_MARK_PREFIX_SZ+i] = (unsigned char)(iPgOne & 0xff);
+ iPgOne >>= 8;
+ }
+ iWriteEnd += paf->iPgOne;
+ if( SQLITE_OK==(rc = pFile->pMethods->xWrite
+ (pFile, a, APND_MARK_SIZE, iWriteEnd)) ){
+ paf->iMark = iWriteEnd;
}
- return pFile->pMethods->xWrite(pFile, a, APND_MARK_SIZE, p->iMark);
+ return rc;
}
/*
@@ -224,38 +280,28 @@ static int apndWrite(
int iAmt,
sqlite_int64 iOfst
){
- int rc;
- ApndFile *p = (ApndFile *)pFile;
+ ApndFile *paf = (ApndFile *)pFile;
+ sqlite_int64 iWriteEnd = iOfst + iAmt;
+ if( iWriteEnd>=APND_MAX_SIZE ) return SQLITE_FULL;
pFile = ORIGFILE(pFile);
- if( iOfst+iAmt>=APND_MAX_SIZE ) return SQLITE_FULL;
- rc = pFile->pMethods->xWrite(pFile, zBuf, iAmt, iOfst+p->iPgOne);
- if( rc==SQLITE_OK && iOfst + iAmt + p->iPgOne > p->iMark ){
- sqlite3_int64 sz = 0;
- rc = pFile->pMethods->xFileSize(pFile, &sz);
- if( rc==SQLITE_OK ){
- p->iMark = sz - APND_MARK_SIZE;
- if( iOfst + iAmt + p->iPgOne > p->iMark ){
- p->iMark = p->iPgOne + iOfst + iAmt;
- rc = apndWriteMark(p, pFile);
- }
- }
+ /* If append-mark is absent or will be overwritten, write it. */
+ if( paf->iMark < 0 || paf->iPgOne + iWriteEnd > paf->iMark ){
+ int rc = apndWriteMark(paf, pFile, iWriteEnd);
+ if( SQLITE_OK!=rc ) return rc;
}
- return rc;
+ return pFile->pMethods->xWrite(pFile, zBuf, iAmt, paf->iPgOne+iOfst);
}
/*
** Truncate an apnd-file.
*/
static int apndTruncate(sqlite3_file *pFile, sqlite_int64 size){
- int rc;
- ApndFile *p = (ApndFile *)pFile;
+ ApndFile *paf = (ApndFile *)pFile;
pFile = ORIGFILE(pFile);
- rc = pFile->pMethods->xTruncate(pFile, size+p->iPgOne+APND_MARK_SIZE);
- if( rc==SQLITE_OK ){
- p->iMark = p->iPgOne+size;
- rc = apndWriteMark(p, pFile);
- }
- return rc;
+ /* The append mark goes out first so truncate failure does not lose it. */
+ if( SQLITE_OK!=apndWriteMark(paf, pFile, size) ) return SQLITE_IOERR;
+ /* Truncate underlying file just past append mark */
+ return pFile->pMethods->xTruncate(pFile, paf->iMark+APND_MARK_SIZE);
}
/*
@@ -268,16 +314,12 @@ static int apndSync(sqlite3_file *pFile, int flags){
/*
** Return the current file-size of an apnd-file.
+** If the append mark is not yet there, the file-size is 0.
*/
static int apndFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
- ApndFile *p = (ApndFile *)pFile;
- int rc;
- pFile = ORIGFILE(p);
- rc = pFile->pMethods->xFileSize(pFile, pSize);
- if( rc==SQLITE_OK && p->iPgOne ){
- *pSize -= p->iPgOne + APND_MARK_SIZE;
- }
- return rc;
+ ApndFile *paf = (ApndFile *)pFile;
+ *pSize = ( paf->iMark >= 0 )? (paf->iMark - paf->iPgOne) : 0;
+ return SQLITE_OK;
}
/*
@@ -308,12 +350,13 @@ static int apndCheckReservedLock(sqlite3_file *pFile, int *pResOut){
** File control method. For custom operations on an apnd-file.
*/
static int apndFileControl(sqlite3_file *pFile, int op, void *pArg){
- ApndFile *p = (ApndFile *)pFile;
+ ApndFile *paf = (ApndFile *)pFile;
int rc;
pFile = ORIGFILE(pFile);
+ if( op==SQLITE_FCNTL_SIZE_HINT ) *(sqlite3_int64*)pArg += paf->iPgOne;
rc = pFile->pMethods->xFileControl(pFile, op, pArg);
if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){
- *(char**)pArg = sqlite3_mprintf("apnd(%lld)/%z", p->iPgOne, *(char**)pArg);
+ *(char**)pArg = sqlite3_mprintf("apnd(%lld)/%z", paf->iPgOne,*(char**)pArg);
}
return rc;
}
@@ -372,6 +415,9 @@ static int apndFetch(
void **pp
){
ApndFile *p = (ApndFile *)pFile;
+ if( p->iMark < 0 || iOfst+iAmt > p->iMark ){
+ return SQLITE_IOERR; /* Cannot read what is not yet there. */
+ }
pFile = ORIGFILE(pFile);
return pFile->pMethods->xFetch(pFile, iOfst+p->iPgOne, iAmt, pp);
}
@@ -384,94 +430,152 @@ static int apndUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
}
/*
-** Check to see if the file is an ordinary SQLite database file.
-*/
-static int apndIsOrdinaryDatabaseFile(sqlite3_int64 sz, sqlite3_file *pFile){
- int rc;
- char zHdr[16];
- static const char aSqliteHdr[] = "SQLite format 3";
- if( sz<512 ) return 0;
- rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), 0);
- if( rc ) return 0;
- return memcmp(zHdr, aSqliteHdr, sizeof(zHdr))==0;
-}
-
-/*
** Try to read the append-mark off the end of a file. Return the
-** start of the appended database if the append-mark is present. If
-** there is no append-mark, return -1;
+** start of the appended database if the append-mark is present.
+** If there is no valid append-mark, return -1;
+**
+** An append-mark is only valid if the NNNNNNNN start-of-database offset
+** indicates that the appended database contains at least one page. The
+** start-of-database value must be a multiple of 512.
*/
static sqlite3_int64 apndReadMark(sqlite3_int64 sz, sqlite3_file *pFile){
int rc, i;
sqlite3_int64 iMark;
+ int msbs = 8 * (APND_MARK_FOS_SZ-1);
unsigned char a[APND_MARK_SIZE];
- if( sz<=APND_MARK_SIZE ) return -1;
+ if( APND_MARK_SIZE!=(sz & 0x1ff) ) return -1;
rc = pFile->pMethods->xRead(pFile, a, APND_MARK_SIZE, sz-APND_MARK_SIZE);
if( rc ) return -1;
if( memcmp(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ)!=0 ) return -1;
- iMark = ((sqlite3_int64)(a[APND_MARK_PREFIX_SZ]&0x7f))<<56;
- for(i=1; i<8; i++){
- iMark += (sqlite3_int64)a[APND_MARK_PREFIX_SZ+i]<<(56-8*i);
+ iMark = ((sqlite3_int64)(a[APND_MARK_PREFIX_SZ] & 0x7f)) << msbs;
+ for(i=1; i<8; i++){
+ msbs -= 8;
+ iMark |= (sqlite3_int64)a[APND_MARK_PREFIX_SZ+i]<<msbs;
}
+ if( iMark > (sz - APND_MARK_SIZE - 512) ) return -1;
+ if( iMark & 0x1ff ) return -1;
return iMark;
}
+static const char apvfsSqliteHdr[] = "SQLite format 3";
+/*
+** Check to see if the file is an appendvfs SQLite database file.
+** Return true iff it is such. Parameter sz is the file's size.
+*/
+static int apndIsAppendvfsDatabase(sqlite3_int64 sz, sqlite3_file *pFile){
+ int rc;
+ char zHdr[16];
+ sqlite3_int64 iMark = apndReadMark(sz, pFile);
+ if( iMark>=0 ){
+ /* If file has the correct end-marker, the expected odd size, and the
+ ** SQLite DB type marker where the end-marker puts it, then it
+ ** is an appendvfs database.
+ */
+ rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), iMark);
+ if( SQLITE_OK==rc
+ && memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))==0
+ && (sz & 0x1ff) == APND_MARK_SIZE
+ && sz>=512+APND_MARK_SIZE
+ ){
+ return 1; /* It's an appendvfs database */
+ }
+ }
+ return 0;
+}
+
+/*
+** Check to see if the file is an ordinary SQLite database file.
+** Return true iff so. Parameter sz is the file's size.
+*/
+static int apndIsOrdinaryDatabaseFile(sqlite3_int64 sz, sqlite3_file *pFile){
+ char zHdr[16];
+ if( apndIsAppendvfsDatabase(sz, pFile) /* rule 2 */
+ || (sz & 0x1ff) != 0
+ || SQLITE_OK!=pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), 0)
+ || memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))!=0
+ ){
+ return 0;
+ }else{
+ return 1;
+ }
+}
+
/*
** Open an apnd file handle.
*/
static int apndOpen(
- sqlite3_vfs *pVfs,
+ sqlite3_vfs *pApndVfs,
const char *zName,
sqlite3_file *pFile,
int flags,
int *pOutFlags
){
- ApndFile *p;
- sqlite3_file *pSubFile;
- sqlite3_vfs *pSubVfs;
+ ApndFile *pApndFile = (ApndFile*)pFile;
+ sqlite3_file *pBaseFile = ORIGFILE(pFile);
+ sqlite3_vfs *pBaseVfs = ORIGVFS(pApndVfs);
int rc;
- sqlite3_int64 sz;
- pSubVfs = ORIGVFS(pVfs);
+ sqlite3_int64 sz = 0;
if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){
- return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags);
+ /* The appendvfs is not to be used for transient or temporary databases.
+ ** Just use the base VFS open to initialize the given file object and
+ ** open the underlying file. (Appendvfs is then unused for this file.)
+ */
+ return pBaseVfs->xOpen(pBaseVfs, zName, pFile, flags, pOutFlags);
}
- p = (ApndFile*)pFile;
- memset(p, 0, sizeof(*p));
- pSubFile = ORIGFILE(pFile);
+ memset(pApndFile, 0, sizeof(ApndFile));
pFile->pMethods = &apnd_io_methods;
- rc = pSubVfs->xOpen(pSubVfs, zName, pSubFile, flags, pOutFlags);
- if( rc ) goto apnd_open_done;
- rc = pSubFile->pMethods->xFileSize(pSubFile, &sz);
+ pApndFile->iMark = -1; /* Append mark not yet written */
+
+ rc = pBaseVfs->xOpen(pBaseVfs, zName, pBaseFile, flags, pOutFlags);
+ if( rc==SQLITE_OK ){
+ rc = pBaseFile->pMethods->xFileSize(pBaseFile, &sz);
+ }
if( rc ){
- pSubFile->pMethods->xClose(pSubFile);
- goto apnd_open_done;
+ pBaseFile->pMethods->xClose(pBaseFile);
+ pFile->pMethods = 0;
+ return rc;
}
- if( apndIsOrdinaryDatabaseFile(sz, pSubFile) ){
- memmove(pFile, pSubFile, pSubVfs->szOsFile);
+ if( apndIsOrdinaryDatabaseFile(sz, pBaseFile) ){
+ /* The file being opened appears to be just an ordinary DB. Copy
+ ** the base dispatch-table so this instance mimics the base VFS.
+ */
+ memmove(pApndFile, pBaseFile, pBaseVfs->szOsFile);
return SQLITE_OK;
}
- p->iMark = 0;
- p->iPgOne = apndReadMark(sz, pFile);
- if( p->iPgOne>0 ){
+ pApndFile->iPgOne = apndReadMark(sz, pFile);
+ if( pApndFile->iPgOne>=0 ){
+ pApndFile->iMark = sz - APND_MARK_SIZE; /* Append mark found */
return SQLITE_OK;
}
if( (flags & SQLITE_OPEN_CREATE)==0 ){
- pSubFile->pMethods->xClose(pSubFile);
+ pBaseFile->pMethods->xClose(pBaseFile);
rc = SQLITE_CANTOPEN;
+ pFile->pMethods = 0;
+ }else{
+ /* Round newly added appendvfs location to #define'd page boundary.
+ ** Note that nothing has yet been written to the underlying file.
+ ** The append mark will be written along with first content write.
+ ** Until then, paf->iMark value indicates it is not yet written.
+ */
+ pApndFile->iPgOne = APND_START_ROUNDUP(sz);
}
- p->iPgOne = (sz+0xfff) & ~(sqlite3_int64)0xfff;
-apnd_open_done:
- if( rc ) pFile->pMethods = 0;
return rc;
}
/*
-** All other VFS methods are pass-thrus.
+** Delete an apnd file.
+** For an appendvfs, this could mean delete the appendvfs portion,
+** leaving the appendee as it was before it gained an appendvfs.
+** For now, this code deletes the underlying file too.
*/
static int apndDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
return ORIGVFS(pVfs)->xDelete(ORIGVFS(pVfs), zPath, dirSync);
}
+
+/*
+** All other VFS methods are pass-thrus.
+*/
static int apndAccess(
sqlite3_vfs *pVfs,
const char *zPath,
diff --git a/chromium/third_party/sqlite/src/ext/misc/cksumvfs.c b/chromium/third_party/sqlite/src/ext/misc/cksumvfs.c
index a0888c0024d..0f6b00f4361 100644
--- a/chromium/third_party/sqlite/src/ext/misc/cksumvfs.c
+++ b/chromium/third_party/sqlite/src/ext/misc/cksumvfs.c
@@ -356,6 +356,41 @@ static void cksmVerifyFunc(
sqlite3_result_int(context, memcmp(data+nByte-8,cksum,8)==0);
}
+#ifdef SQLITE_CKSUMVFS_INIT_FUNCNAME
+/*
+** SQL function: initialize_cksumvfs(SCHEMANAME)
+**
+** This SQL functions (whose name is actually determined at compile-time
+** by the value of the SQLITE_CKSUMVFS_INIT_FUNCNAME macro) invokes:
+**
+** sqlite3_file_control(db, SCHEMANAME, SQLITE_FCNTL_RESERVE_BYTE, &n);
+**
+** In order to set the reserve bytes value to 8, so that cksumvfs will
+** operation. This feature is provided (if and only if the
+** SQLITE_CKSUMVFS_INIT_FUNCNAME compile-time option is set to a string
+** which is the name of the SQL function) so as to provide the ability
+** to invoke the file-control in programming languages that lack
+** direct access to the sqlite3_file_control() interface (ex: Java).
+**
+** This interface is undocumented, apart from this comment. Usage
+** example:
+**
+** 1. Compile with -DSQLITE_CKSUMVFS_INIT_FUNCNAME="ckvfs_init"
+** 2. Run: "SELECT cksum_init('main'); VACUUM;"
+*/
+static void cksmInitFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int nByte = 8;
+ const char *zSchemaName = (const char*)sqlite3_value_text(argv[0]);
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ sqlite3_file_control(db, zSchemaName, SQLITE_FCNTL_RESERVE_BYTES, &nByte);
+ /* Return NULL */
+}
+#endif /* SQLITE_CKSUMBFS_INIT_FUNCNAME */
+
/*
** Close a cksm-file.
*/
@@ -715,7 +750,17 @@ static int cksmGetLastError(sqlite3_vfs *pVfs, int a, char *b){
return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b);
}
static int cksmCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){
- return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p);
+ sqlite3_vfs *pOrig = ORIGVFS(pVfs);
+ int rc;
+ assert( pOrig->iVersion>=2 );
+ if( pOrig->xCurrentTimeInt64 ){
+ rc = pOrig->xCurrentTimeInt64(pOrig, p);
+ }else{
+ double r;
+ rc = pOrig->xCurrentTime(pOrig, &r);
+ *p = (sqlite3_int64)(r*86400000.0);
+ }
+ return rc;
}
static int cksmSetSystemCall(
sqlite3_vfs *pVfs,
@@ -746,6 +791,11 @@ static int cksmRegisterFunc(
rc = sqlite3_create_function(db, "verify_checksum", 1,
SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
0, cksmVerifyFunc, 0, 0);
+#ifdef SQLITE_CKSUMVFS_INIT_FUNCNAME
+ (void)sqlite3_create_function(db, SQLITE_CKSUMVFS_INIT_FUNCNAME, 1,
+ SQLITE_UTF8|SQLITE_DIRECTONLY,
+ 0, cksmInitFunc, 0, 0);
+#endif
return rc;
}
diff --git a/chromium/third_party/sqlite/src/ext/misc/ieee754.c b/chromium/third_party/sqlite/src/ext/misc/ieee754.c
index 121eb43d66f..6cdd79a4d4e 100644
--- a/chromium/third_party/sqlite/src/ext/misc/ieee754.c
+++ b/chromium/third_party/sqlite/src/ext/misc/ieee754.c
@@ -167,6 +167,14 @@ static void ieee754func(
int isNeg = 0;
m = sqlite3_value_int64(argv[0]);
e = sqlite3_value_int64(argv[1]);
+
+ /* Limit the range of e. Ticket 22dea1cfdb9151e4 2021-03-02 */
+ if( e>10000 ){
+ e = 10000;
+ }else if( e<-10000 ){
+ e = -10000;
+ }
+
if( m<0 ){
isNeg = 1;
m = -m;
diff --git a/chromium/third_party/sqlite/src/ext/misc/series.c b/chromium/third_party/sqlite/src/ext/misc/series.c
index 092383e57e7..a4e92ace883 100644
--- a/chromium/third_party/sqlite/src/ext/misc/series.c
+++ b/chromium/third_party/sqlite/src/ext/misc/series.c
@@ -247,7 +247,8 @@ static int seriesEof(sqlite3_vtab_cursor *cur){
** 4: step=VALUE
**
** Also, if bit 8 is set, that means that the series should be output
-** in descending order rather than in ascending order.
+** in descending order rather than in ascending order. If bit 16 is
+** set, then output must appear in ascending order.
**
** This routine should initialize the cursor and position it so that it
** is pointing at the first row, or pointing off the end of the table
@@ -273,7 +274,12 @@ static int seriesFilter(
}
if( idxNum & 4 ){
pCur->iStep = sqlite3_value_int64(argv[i++]);
- if( pCur->iStep<1 ) pCur->iStep = 1;
+ if( pCur->iStep==0 ){
+ pCur->iStep = 1;
+ }else if( pCur->iStep<0 ){
+ pCur->iStep = -pCur->iStep;
+ if( (idxNum & 16)==0 ) idxNum |= 8;
+ }
}else{
pCur->iStep = 1;
}
@@ -367,7 +373,11 @@ static int seriesBestIndex(
pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0));
pIdxInfo->estimatedRows = 1000;
if( pIdxInfo->nOrderBy==1 ){
- if( pIdxInfo->aOrderBy[0].desc ) idxNum |= 8;
+ if( pIdxInfo->aOrderBy[0].desc ){
+ idxNum |= 8;
+ }else{
+ idxNum |= 16;
+ }
pIdxInfo->orderByConsumed = 1;
}
}else{
diff --git a/chromium/third_party/sqlite/src/ext/misc/shathree.c b/chromium/third_party/sqlite/src/ext/misc/shathree.c
index 56eba564ce3..ef25cb56c6d 100644
--- a/chromium/third_party/sqlite/src/ext/misc/shathree.c
+++ b/chromium/third_party/sqlite/src/ext/misc/shathree.c
@@ -31,7 +31,10 @@ SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
#include <stdarg.h>
+
+#ifndef SQLITE_AMALGAMATION
typedef sqlite3_uint64 u64;
+#endif /* SQLITE_AMALGAMATION */
/******************************************************************************
** The Hash Engine
@@ -621,9 +624,11 @@ static void sha3QueryFunc(
}
nCol = sqlite3_column_count(pStmt);
z = sqlite3_sql(pStmt);
- n = (int)strlen(z);
- hash_step_vformat(&cx,"S%d:",n);
- SHA3Update(&cx,(unsigned char*)z,n);
+ if( z ){
+ n = (int)strlen(z);
+ hash_step_vformat(&cx,"S%d:",n);
+ SHA3Update(&cx,(unsigned char*)z,n);
+ }
/* Compute a hash over the result of the query */
while( SQLITE_ROW==sqlite3_step(pStmt) ){
diff --git a/chromium/third_party/sqlite/src/ext/misc/wholenumber.c b/chromium/third_party/sqlite/src/ext/misc/wholenumber.c
index 5643f9cf7e7..03d6e6902e0 100644
--- a/chromium/third_party/sqlite/src/ext/misc/wholenumber.c
+++ b/chromium/third_party/sqlite/src/ext/misc/wholenumber.c
@@ -220,7 +220,7 @@ static int wholenumberBestIndex(
pIdxInfo->orderByConsumed = 1;
}
if( (idxNum & 12)==0 ){
- pIdxInfo->estimatedCost = (double)100000000;
+ pIdxInfo->estimatedCost = 1e99;
}else if( (idxNum & 3)==0 ){
pIdxInfo->estimatedCost = (double)5;
}else{
diff --git a/chromium/third_party/sqlite/src/ext/rbu/rbu.c b/chromium/third_party/sqlite/src/ext/rbu/rbu.c
index 02822c4a429..91773409cf8 100644
--- a/chromium/third_party/sqlite/src/ext/rbu/rbu.c
+++ b/chromium/third_party/sqlite/src/ext/rbu/rbu.c
@@ -183,6 +183,13 @@ int main(int argc, char **argv){
break;
}
+ if( nStatStep>0 ){
+ sqlite3_int64 nUsed;
+ sqlite3_int64 nHighwater;
+ sqlite3_status64(SQLITE_STATUS_MEMORY_USED, &nUsed, &nHighwater, 0);
+ fprintf(stdout, "memory used=%lld highwater=%lld\n", nUsed, nHighwater);
+ }
+
sqlite3_free(zErrmsg);
return (rc==SQLITE_OK || rc==SQLITE_DONE) ? 0 : 1;
}
diff --git a/chromium/third_party/sqlite/src/ext/rbu/rbudiff.test b/chromium/third_party/sqlite/src/ext/rbu/rbudiff.test
index 965ce297e33..5c2bf9bee7b 100644
--- a/chromium/third_party/sqlite/src/ext/rbu/rbudiff.test
+++ b/chromium/third_party/sqlite/src/ext/rbu/rbudiff.test
@@ -268,6 +268,14 @@ tablE t1 USING FTs5(c);
DELETE FROM t1 WHERE rowid = 1;
INSERT INTO t1 VALUES('a b c');
}
+ 4 {
+ creAte virTUal tablE t1 USING FTs5(c);
+ INSERT INTO t1 VALUES('a b c');
+ INSERT INTO t1 VALUES('a b c');
+ } {
+ DELETE FROM t1 WHERE rowid = 1;
+ INSERT INTO t1 VALUES('a b c');
+ }
} {
forcedelete test.db test.db2
diff --git a/chromium/third_party/sqlite/src/ext/rbu/sqlite3rbu.c b/chromium/third_party/sqlite/src/ext/rbu/sqlite3rbu.c
index 12a0a466033..c5f809848d1 100644
--- a/chromium/third_party/sqlite/src/ext/rbu/sqlite3rbu.c
+++ b/chromium/third_party/sqlite/src/ext/rbu/sqlite3rbu.c
@@ -4828,22 +4828,24 @@ static int rbuVfsShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
#endif
assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) );
- if( pRbu && (pRbu->eStage==RBU_STAGE_OAL || pRbu->eStage==RBU_STAGE_MOVE) ){
- /* Magic number 1 is the WAL_CKPT_LOCK lock. Preventing SQLite from
- ** taking this lock also prevents any checkpoints from occurring.
- ** todo: really, it's not clear why this might occur, as
- ** wal_autocheckpoint ought to be turned off. */
+ if( pRbu && (
+ pRbu->eStage==RBU_STAGE_OAL
+ || pRbu->eStage==RBU_STAGE_MOVE
+ || pRbu->eStage==RBU_STAGE_DONE
+ )){
+ /* Prevent SQLite from taking a shm-lock on the target file when it
+ ** is supplying heap memory to the upper layer in place of *-shm
+ ** segments. */
if( ofst==WAL_LOCK_CKPT && n==1 ) rc = SQLITE_BUSY;
}else{
int bCapture = 0;
if( pRbu && pRbu->eStage==RBU_STAGE_CAPTURE ){
bCapture = 1;
}
-
if( bCapture==0 || 0==(flags & SQLITE_SHM_UNLOCK) ){
rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags);
if( bCapture && rc==SQLITE_OK ){
- pRbu->mLock |= (1 << ofst);
+ pRbu->mLock |= ((1<<n) - 1) << ofst;
}
}
}
diff --git a/chromium/third_party/sqlite/src/ext/session/session2.test b/chromium/third_party/sqlite/src/ext/session/session2.test
index cd8c2869e79..806687745e5 100644
--- a/chromium/third_party/sqlite/src/ext/session/session2.test
+++ b/chromium/third_party/sqlite/src/ext/session/session2.test
@@ -35,7 +35,7 @@ proc test_reset {} {
test_reset
do_execsql_test 1.0 {
- CREATE TABLE t1(a PRIMARY KEY, b);
+ CREATE TABLE t1(a INT PRIMARY KEY, b);
INSERT INTO t1 VALUES('i', 'one');
}
do_iterator_test 1.1 t1 {
@@ -184,7 +184,7 @@ set set_of_tests {
test_reset
do_common_sql {
- CREATE TABLE t1(a PRIMARY KEY, b);
+ CREATE TABLE t1(a int PRIMARY KEY, b);
CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
CREATE TABLE t4(a, b, PRIMARY KEY(b, a));
@@ -206,17 +206,17 @@ sqlite3 db3 test.db3
do_test 3.0 {
execsql {
ATTACH 'test.db3' AS 'aux';
- CREATE TABLE t1(a, b PRIMARY KEY);
+ CREATE TABLE t1(a int, b PRIMARY KEY);
CREATE TABLE t2(x, y, z);
CREATE TABLE t3(a);
- CREATE TABLE aux.t1(a PRIMARY KEY, b);
+ CREATE TABLE aux.t1(a int PRIMARY KEY, b);
CREATE TABLE aux.t2(a, b INTEGER PRIMARY KEY);
CREATE TABLE aux.t3(a, b, c, PRIMARY KEY(a, b));
CREATE TABLE aux.t4(a, b, PRIMARY KEY(b, a));
}
execsql {
- CREATE TABLE t1(a PRIMARY KEY, b);
+ CREATE TABLE t1(a int PRIMARY KEY, b);
CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
CREATE TABLE t4(a, b, PRIMARY KEY(b, a));
@@ -588,4 +588,52 @@ do_execsql_test 10.2 {
} {0 0 1 1}
S delete
+#-------------------------------------------------------------------------
+test_reset
+do_common_sql {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d, e, f);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<32
+ )
+ INSERT INTO t1 SELECT NULL, 0, 0, 0, 0, 0 FROM s
+}
+
+do_then_apply_sql {
+ UPDATE t1 SET f=f+1 WHERE a=1;
+ UPDATE t1 SET e=e+1 WHERE a=2;
+ UPDATE t1 SET e=e+1, f=f+1 WHERE a=3;
+ UPDATE t1 SET d=d+1 WHERE a=4;
+ UPDATE t1 SET d=d+1, f=f+1 WHERE a=5;
+ UPDATE t1 SET d=d+1, e=e+1 WHERE a=6;
+ UPDATE t1 SET d=d+1, e=e+1, f=f+1 WHERE a=7;
+ UPDATE t1 SET c=c+1 WHERE a=8;
+ UPDATE t1 SET c=c+1, f=f+1 WHERE a=9;
+ UPDATE t1 SET c=c+1, e=e+1 WHERE a=10;
+ UPDATE t1 SET c=c+1, e=e+1, f=f+1 WHERE a=11;
+ UPDATE t1 SET c=c+1, d=d+1 WHERE a=12;
+ UPDATE t1 SET c=c+1, d=d+1, f=f+1 WHERE a=13;
+ UPDATE t1 SET c=c+1, d=d+1, e=e+1 WHERE a=14;
+ UPDATE t1 SET c=c+1, d=d+1, e=e+1, f=f+1 WHERE a=15;
+ UPDATE t1 SET d=d+1 WHERE a=16;
+ UPDATE t1 SET d=d+1, f=f+1 WHERE a=17;
+ UPDATE t1 SET d=d+1, e=e+1 WHERE a=18;
+ UPDATE t1 SET d=d+1, e=e+1, f=f+1 WHERE a=19;
+ UPDATE t1 SET d=d+1, d=d+1 WHERE a=20;
+ UPDATE t1 SET d=d+1, d=d+1, f=f+1 WHERE a=21;
+ UPDATE t1 SET d=d+1, d=d+1, e=e+1 WHERE a=22;
+ UPDATE t1 SET d=d+1, d=d+1, e=e+1, f=f+1 WHERE a=23;
+ UPDATE t1 SET d=d+1, c=c+1 WHERE a=24;
+ UPDATE t1 SET d=d+1, c=c+1, f=f+1 WHERE a=25;
+ UPDATE t1 SET d=d+1, c=c+1, e=e+1 WHERE a=26;
+ UPDATE t1 SET d=d+1, c=c+1, e=e+1, f=f+1 WHERE a=27;
+ UPDATE t1 SET d=d+1, c=c+1, d=d+1 WHERE a=28;
+ UPDATE t1 SET d=d+1, c=c+1, d=d+1, f=f+1 WHERE a=29;
+ UPDATE t1 SET d=d+1, c=c+1, d=d+1, e=e+1 WHERE a=30;
+ UPDATE t1 SET d=d+1, c=c+1, d=d+1, e=e+1, f=f+1 WHERE a=31;
+}
+
+do_test 11.0 {
+ compare_db db db2
+} {}
+
finish_test
diff --git a/chromium/third_party/sqlite/src/ext/session/sessionmem.test b/chromium/third_party/sqlite/src/ext/session/sessionmem.test
new file mode 100644
index 00000000000..75b48623d0f
--- /dev/null
+++ b/chromium/third_party/sqlite/src/ext/session/sessionmem.test
@@ -0,0 +1,57 @@
+# 2020 December 23
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for the SQLite sessions module
+# Specifically, for the sqlite3session_memory_used() API.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+
+set testprefix sessionmem
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(i INTEGER PRIMARY KEY, x, y);
+ CREATE TABLE t2(i INTEGER, x, y, PRIMARY KEY(x, y));
+}
+
+do_test 1.1 {
+ sqlite3session S db main
+ S attach *
+} {}
+
+foreach {tn sql eRes} {
+ 1 { INSERT INTO t1 VALUES(1, 2, 3) } 1
+ 2 { UPDATE t1 SET x=5 } 0
+ 3 { UPDATE t1 SET i=5 } 1
+ 4 { DELETE FROM t1 } 0
+ 5 { INSERT INTO t1 VALUES(1, 2, 3) } 0
+ 6 { INSERT INTO t1 VALUES(5, 2, 3) } 0
+ 7 { INSERT INTO t2 VALUES('a', 'b', 'c') } 1
+ 8 { INSERT INTO t2 VALUES('d', 'e', 'f') } 1
+ 9 { UPDATE t2 SET i='e' } 0
+} {
+ set mem1 [S memory_used]
+ do_test 1.2.$tn.(mu=$mem1) {
+ execsql $sql
+ set mem2 [S memory_used]
+ expr {$mem2 > $mem1}
+ } $eRes
+}
+
+do_test 1.3 {
+ S delete
+} {}
+
+finish_test
diff --git a/chromium/third_party/sqlite/src/ext/session/sessionnoop.test b/chromium/third_party/sqlite/src/ext/session/sessionnoop.test
new file mode 100644
index 00000000000..16c60b7abff
--- /dev/null
+++ b/chromium/third_party/sqlite/src/ext/session/sessionnoop.test
@@ -0,0 +1,187 @@
+# 2021 Februar 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+
+set testprefix sessionnoop
+
+#-------------------------------------------------------------------------
+# Test plan:
+#
+# 1.*: Test that concatenating changesets cannot produce a noop UPDATE.
+# 2.*: Test that rebasing changesets cannot produce a noop UPDATE.
+# 3.*: Test that sqlite3changeset_apply() ignores noop UPDATE changes.
+#
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a PRIMARY KEY, b, c, d);
+ INSERT INTO t1 VALUES(1, 1, 1, 1);
+ INSERT INTO t1 VALUES(2, 2, 2, 2);
+ INSERT INTO t1 VALUES(3, 3, 3, 3);
+}
+
+proc do_concat_test {tn sql1 sql2 res} {
+ uplevel [list do_test $tn [subst -nocommands {
+ set C1 [changeset_from_sql {$sql1}]
+ set C2 [changeset_from_sql {$sql2}]
+ set C3 [sqlite3changeset_concat [set C1] [set C2]]
+ set got [list]
+ sqlite3session_foreach elem [set C3] { lappend got [set elem] }
+ set got
+ }] [list {*}$res]]
+}
+
+do_concat_test 1.1 {
+ UPDATE t1 SET c=c+1;
+} {
+ UPDATE t1 SET c=c-1;
+} {
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 {
+ CREATE TABLE t1(a PRIMARY KEY, b, c);
+ INSERT INTO t1 VALUES(1, 1, 1);
+ INSERT INTO t1 VALUES(2, 2, 2);
+ INSERT INTO t1 VALUES(3, 3, 3);
+}
+
+proc do_rebase_test {tn sql_local sql_remote conflict_res expected} {
+ proc xConflict {args} [list return $conflict_res]
+
+ uplevel [list \
+ do_test $tn [subst -nocommands {
+ execsql BEGIN
+ set c_remote [changeset_from_sql {$sql_remote}]
+ execsql ROLLBACK
+
+ execsql BEGIN
+ set c_local [changeset_from_sql {$sql_local}]
+ set base [sqlite3changeset_apply_v2 db [set c_remote] xConflict]
+ execsql ROLLBACK
+
+ sqlite3rebaser_create R
+ R config [set base]
+ set res [list]
+ sqlite3session_foreach elem [R rebase [set c_local]] {
+ lappend res [set elem]
+ }
+ R delete
+ set res
+ }] [list {*}$expected]
+ ]
+}
+
+do_rebase_test 2.1 {
+ UPDATE t1 SET c=2 WHERE a=1; -- local
+} {
+ UPDATE t1 SET c=3 WHERE a=1; -- remote
+} OMIT {
+ {UPDATE t1 0 X.. {i 1 {} {} i 3} {{} {} {} {} i 2}}
+}
+
+do_rebase_test 2.2 {
+ UPDATE t1 SET c=2 WHERE a=1; -- local
+} {
+ UPDATE t1 SET c=3 WHERE a=1; -- remote
+} REPLACE {
+}
+
+do_rebase_test 2.3.1 {
+ UPDATE t1 SET c=4 WHERE a=1; -- local
+} {
+ UPDATE t1 SET c=4 WHERE a=1 -- remote
+} OMIT {
+ {UPDATE t1 0 X.. {i 1 {} {} i 4} {{} {} {} {} i 4}}
+}
+
+do_rebase_test 2.3.2 {
+ UPDATE t1 SET c=5 WHERE a=1; -- local
+} {
+ UPDATE t1 SET c=5 WHERE a=1 -- remote
+} REPLACE {
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 3.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+ INSERT INTO t1 VALUES(1, 1, 1);
+ INSERT INTO t1 VALUES(2, 2, 2);
+ INSERT INTO t1 VALUES(3, 3, 3);
+ INSERT INTO t1 VALUES(4, 4, 4);
+}
+
+# Arg $pkstr contains one character for each column in the table. An
+# "X" for PK column, or a "." for a non-PK.
+#
+proc mk_tbl_header {name pkstr} {
+ set ret [binary format H2c 54 [string length $pkstr]]
+ foreach i [split $pkstr {}] {
+ if {$i=="X"} {
+ append ret [binary format H2 01]
+ } else {
+ if {$i!="."} {error "bad pkstr: $pkstr ($i)"}
+ append ret [binary format H2 00]
+ }
+ }
+ append ret $name
+ append ret [binary format H2 00]
+ set ret
+}
+
+proc mk_update_change {args} {
+ set ret [binary format H2H2 17 00]
+ foreach a $args {
+ if {$a==""} {
+ append ret [binary format H2 00]
+ } else {
+ append ret [binary format H2W 01 $a]
+ }
+ }
+ set ret
+}
+
+proc xConflict {args} { return "ABORT" }
+do_test 3.1 {
+ set C [mk_tbl_header t1 X..]
+ append C [mk_update_change 1 {} 1 {} {} 500]
+ append C [mk_update_change 2 {} {} {} {} {}]
+ append C [mk_update_change 3 3 {} {} 600 {}]
+ append C [mk_update_change 4 {} {} {} {} {}]
+
+ sqlite3changeset_apply_v2 db $C xConflict
+} {}
+do_execsql_test 3.2 {
+ SELECT * FROM t1
+} {
+ 1 1 500
+ 2 2 2
+ 3 600 3
+ 4 4 4
+}
+
+
+
+
+
+
+finish_test
+
diff --git a/chromium/third_party/sqlite/src/ext/session/sessionwor.test b/chromium/third_party/sqlite/src/ext/session/sessionwor.test
index 0f0b429d7be..7d9e5c6a897 100644
--- a/chromium/third_party/sqlite/src/ext/session/sessionwor.test
+++ b/chromium/third_party/sqlite/src/ext/session/sessionwor.test
@@ -69,7 +69,9 @@ foreach {tn wo} {
} {
reset_db
- do_execsql_test 2.$tn.0 "CREATE TABLE t1(a INTEGER PRIMARY KEY, b) $wo ;"
+ do_execsql_test 2.$tn.0.1 "CREATE TABLE t1(a INTEGER PRIMARY KEY, b) $wo ;"
+ do_execsql_test 2.$tn.0.2 "CREATE TABLE t2(a INTEGER PRIMARY KEY, b) $wo ;"
+ do_execsql_test 2.$tn.0.3 "CREATE TABLE t3(a INTEGER PRIMARY KEY, b) $wo ;"
do_iterator_test 1.1 t1 {
INSERT INTO t1 VALUES(1, 'two');
@@ -94,6 +96,27 @@ foreach {tn wo} {
} {
{DELETE t1 0 X. {i 1 t four} {}}
}
+
+ do_execsql_test 2.$tn.5 {
+ INSERT INTO t1 VALUES(1, 'one');
+ INSERT INTO t1 VALUES(2, 'two');
+ INSERT INTO t1 VALUES(3, 'three');
+ }
+
+ do_iterator_test 2.$tn.6 t2 {
+ INSERT INTO t2 SELECT a, b FROM t1
+ } {
+ {INSERT t2 0 X. {} {i 1 t one}}
+ {INSERT t2 0 X. {} {i 2 t two}}
+ {INSERT t2 0 X. {} {i 3 t three}}
+ }
+ do_iterator_test 2.$tn.7 t3 {
+ INSERT INTO t3 SELECT * FROM t1
+ } {
+ {INSERT t3 0 X. {} {i 1 t one}}
+ {INSERT t3 0 X. {} {i 2 t two}}
+ {INSERT t3 0 X. {} {i 3 t three}}
+ }
}
finish_test
diff --git a/chromium/third_party/sqlite/src/ext/session/sqlite3session.c b/chromium/third_party/sqlite/src/ext/session/sqlite3session.c
index dae26047194..8210056a890 100644
--- a/chromium/third_party/sqlite/src/ext/session/sqlite3session.c
+++ b/chromium/third_party/sqlite/src/ext/session/sqlite3session.c
@@ -48,6 +48,7 @@ struct sqlite3_session {
int rc; /* Non-zero if an error has occurred */
void *pFilterCtx; /* First argument to pass to xTableFilter */
int (*xTableFilter)(void *pCtx, const char *zTab);
+ i64 nMalloc; /* Number of bytes of data allocated */
sqlite3_value *pZeroBlob; /* Value containing X'' */
sqlite3_session *pNext; /* Next session object on same db. */
SessionTable *pTable; /* List of attached tables */
@@ -90,6 +91,7 @@ struct sqlite3_changeset_iter {
SessionBuffer tblhdr; /* Buffer to hold apValue/zTab/abPK/ */
int bPatchset; /* True if this is a patchset */
int bInvert; /* True to invert changeset */
+ int bSkipEmpty; /* Skip noop UPDATE changes */
int rc; /* Iterator error code */
sqlite3_stmt *pConflict; /* Points to conflicting row, if any */
char *zTab; /* Current table */
@@ -431,6 +433,26 @@ static int sessionSerializeValue(
return SQLITE_OK;
}
+/*
+** Allocate and return a pointer to a buffer nByte bytes in size. If
+** pSession is not NULL, increase the sqlite3_session.nMalloc variable
+** by the number of bytes allocated.
+*/
+static void *sessionMalloc64(sqlite3_session *pSession, i64 nByte){
+ void *pRet = sqlite3_malloc64(nByte);
+ if( pSession ) pSession->nMalloc += sqlite3_msize(pRet);
+ return pRet;
+}
+
+/*
+** Free buffer pFree, which must have been allocated by an earlier
+** call to sessionMalloc64(). If pSession is not NULL, decrease the
+** sqlite3_session.nMalloc counter by the number of bytes freed.
+*/
+static void sessionFree(sqlite3_session *pSession, void *pFree){
+ if( pSession ) pSession->nMalloc -= sqlite3_msize(pFree);
+ sqlite3_free(pFree);
+}
/*
** This macro is used to calculate hash key values for data structures. In
@@ -898,13 +920,19 @@ static int sessionPreupdateEqual(
** Growing the hash table in this case is a performance optimization only,
** it is not required for correct operation.
*/
-static int sessionGrowHash(int bPatchset, SessionTable *pTab){
+static int sessionGrowHash(
+ sqlite3_session *pSession, /* For memory accounting. May be NULL */
+ int bPatchset,
+ SessionTable *pTab
+){
if( pTab->nChange==0 || pTab->nEntry>=(pTab->nChange/2) ){
int i;
SessionChange **apNew;
sqlite3_int64 nNew = 2*(sqlite3_int64)(pTab->nChange ? pTab->nChange : 128);
- apNew = (SessionChange **)sqlite3_malloc64(sizeof(SessionChange *) * nNew);
+ apNew = (SessionChange**)sessionMalloc64(
+ pSession, sizeof(SessionChange*) * nNew
+ );
if( apNew==0 ){
if( pTab->nChange==0 ){
return SQLITE_ERROR;
@@ -925,7 +953,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){
}
}
- sqlite3_free(pTab->apChange);
+ sessionFree(pSession, pTab->apChange);
pTab->nChange = nNew;
pTab->apChange = apNew;
}
@@ -959,6 +987,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){
** be freed using sqlite3_free() by the caller
*/
static int sessionTableInfo(
+ sqlite3_session *pSession, /* For memory accounting. May be NULL */
sqlite3 *db, /* Database connection */
const char *zDb, /* Name of attached database (e.g. "main") */
const char *zThis, /* Table name */
@@ -1013,7 +1042,7 @@ static int sessionTableInfo(
if( rc==SQLITE_OK ){
nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1);
- pAlloc = sqlite3_malloc64(nByte);
+ pAlloc = sessionMalloc64(pSession, nByte);
if( pAlloc==0 ){
rc = SQLITE_NOMEM;
}
@@ -1056,7 +1085,7 @@ static int sessionTableInfo(
*pabPK = 0;
*pnCol = 0;
if( pzTab ) *pzTab = 0;
- sqlite3_free(azCol);
+ sessionFree(pSession, azCol);
}
sqlite3_finalize(pStmt);
return rc;
@@ -1078,7 +1107,7 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
if( pTab->nCol==0 ){
u8 *abPK;
assert( pTab->azCol==0 || pTab->abPK==0 );
- pSession->rc = sessionTableInfo(pSession->db, pSession->zDb,
+ pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb,
pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK
);
if( pSession->rc==SQLITE_OK ){
@@ -1169,7 +1198,7 @@ static void sessionPreupdateOneChange(
}
/* Grow the hash table if required */
- if( sessionGrowHash(0, pTab) ){
+ if( sessionGrowHash(pSession, 0, pTab) ){
pSession->rc = SQLITE_NOMEM;
return;
}
@@ -1236,7 +1265,7 @@ static void sessionPreupdateOneChange(
}
/* Allocate the change object */
- pChange = (SessionChange *)sqlite3_malloc64(nByte);
+ pChange = (SessionChange *)sessionMalloc64(pSession, nByte);
if( !pChange ){
rc = SQLITE_NOMEM;
goto error_out;
@@ -1609,7 +1638,7 @@ int sqlite3session_diff(
int nCol; /* Columns in zFrom.zTbl */
u8 *abPK;
const char **azCol = 0;
- rc = sessionTableInfo(db, zFrom, zTbl, &nCol, 0, &azCol, &abPK);
+ rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK);
if( rc==SQLITE_OK ){
if( pTo->nCol!=nCol ){
bMismatch = 1;
@@ -1707,7 +1736,7 @@ int sqlite3session_create(
** Free the list of table objects passed as the first argument. The contents
** of the changed-rows hash tables are also deleted.
*/
-static void sessionDeleteTable(SessionTable *pList){
+static void sessionDeleteTable(sqlite3_session *pSession, SessionTable *pList){
SessionTable *pNext;
SessionTable *pTab;
@@ -1719,12 +1748,12 @@ static void sessionDeleteTable(SessionTable *pList){
SessionChange *pNextChange;
for(p=pTab->apChange[i]; p; p=pNextChange){
pNextChange = p->pNext;
- sqlite3_free(p);
+ sessionFree(pSession, p);
}
}
- sqlite3_free((char*)pTab->azCol); /* cast works around VC++ bug */
- sqlite3_free(pTab->apChange);
- sqlite3_free(pTab);
+ sessionFree(pSession, (char*)pTab->azCol); /* cast works around VC++ bug */
+ sessionFree(pSession, pTab->apChange);
+ sessionFree(pSession, pTab);
}
}
@@ -1752,9 +1781,11 @@ void sqlite3session_delete(sqlite3_session *pSession){
/* Delete all attached table objects. And the contents of their
** associated hash-tables. */
- sessionDeleteTable(pSession->pTable);
+ sessionDeleteTable(pSession, pSession->pTable);
- /* Free the session object itself. */
+ /* Assert that all allocations have been freed and then free the
+ ** session object itself. */
+ assert( pSession->nMalloc==0 );
sqlite3_free(pSession);
}
@@ -1801,7 +1832,8 @@ int sqlite3session_attach(
if( !pTab ){
/* Allocate new SessionTable object. */
- pTab = (SessionTable *)sqlite3_malloc64(sizeof(SessionTable) + nName + 1);
+ int nByte = sizeof(SessionTable) + nName + 1;
+ pTab = (SessionTable*)sessionMalloc64(pSession, nByte);
if( !pTab ){
rc = SQLITE_NOMEM;
}else{
@@ -2398,7 +2430,7 @@ static int sessionGenerateChangeset(
int nNoop; /* Size of buffer after writing tbl header */
/* Check the table schema is still Ok. */
- rc = sessionTableInfo(db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK);
+ rc = sessionTableInfo(0, db, pSession->zDb, zName, &nCol, 0,&azCol,&abPK);
if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){
rc = SQLITE_SCHEMA;
}
@@ -2574,6 +2606,13 @@ int sqlite3session_isempty(sqlite3_session *pSession){
}
/*
+** Return the amount of heap memory in use.
+*/
+sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession){
+ return pSession->nMalloc;
+}
+
+/*
** Do the work for either sqlite3changeset_start() or start_strm().
*/
static int sessionChangesetStart(
@@ -2582,7 +2621,8 @@ static int sessionChangesetStart(
void *pIn,
int nChangeset, /* Size of buffer pChangeset in bytes */
void *pChangeset, /* Pointer to buffer containing changeset */
- int bInvert /* True to invert changeset */
+ int bInvert, /* True to invert changeset */
+ int bSkipEmpty /* True to skip empty UPDATE changes */
){
sqlite3_changeset_iter *pRet; /* Iterator to return */
int nByte; /* Number of bytes to allocate for iterator */
@@ -2603,6 +2643,7 @@ static int sessionChangesetStart(
pRet->in.pIn = pIn;
pRet->in.bEof = (xInput ? 0 : 1);
pRet->bInvert = bInvert;
+ pRet->bSkipEmpty = bSkipEmpty;
/* Populate the output variable and return success. */
*pp = pRet;
@@ -2617,7 +2658,7 @@ int sqlite3changeset_start(
int nChangeset, /* Size of buffer pChangeset in bytes */
void *pChangeset /* Pointer to buffer containing changeset */
){
- return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0);
+ return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0, 0);
}
int sqlite3changeset_start_v2(
sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
@@ -2626,7 +2667,7 @@ int sqlite3changeset_start_v2(
int flags
){
int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT);
- return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert);
+ return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert, 0);
}
/*
@@ -2637,7 +2678,7 @@ int sqlite3changeset_start_strm(
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
){
- return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0);
+ return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0, 0);
}
int sqlite3changeset_start_v2_strm(
sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
@@ -2646,7 +2687,7 @@ int sqlite3changeset_start_v2_strm(
int flags
){
int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT);
- return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert);
+ return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert, 0);
}
/*
@@ -2772,11 +2813,14 @@ static int sessionReadRecord(
SessionInput *pIn, /* Input data */
int nCol, /* Number of values in record */
u8 *abPK, /* Array of primary key flags, or NULL */
- sqlite3_value **apOut /* Write values to this array */
+ sqlite3_value **apOut, /* Write values to this array */
+ int *pbEmpty
){
int i; /* Used to iterate through columns */
int rc = SQLITE_OK;
+ assert( pbEmpty==0 || *pbEmpty==0 );
+ if( pbEmpty ) *pbEmpty = 1;
for(i=0; i<nCol && rc==SQLITE_OK; i++){
int eType = 0; /* Type of value (SQLITE_NULL, TEXT etc.) */
if( abPK && abPK[i]==0 ) continue;
@@ -2788,6 +2832,7 @@ static int sessionReadRecord(
eType = pIn->aData[pIn->iNext++];
assert( apOut[i]==0 );
if( eType ){
+ if( pbEmpty ) *pbEmpty = 0;
apOut[i] = sqlite3ValueNew(0);
if( !apOut[i] ) rc = SQLITE_NOMEM;
}
@@ -2967,31 +3012,27 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
}
/*
-** Advance the changeset iterator to the next change.
+** Advance the changeset iterator to the next change. The differences between
+** this function and sessionChangesetNext() are that
**
-** If both paRec and pnRec are NULL, then this function works like the public
-** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the
-** sqlite3changeset_new() and old() APIs may be used to query for values.
-**
-** Otherwise, if paRec and pnRec are not NULL, then a pointer to the change
-** record is written to *paRec before returning and the number of bytes in
-** the record to *pnRec.
+** * If pbEmpty is not NULL and the change is a no-op UPDATE (an UPDATE
+** that modifies no columns), this function sets (*pbEmpty) to 1.
**
-** Either way, this function returns SQLITE_ROW if the iterator is
-** successfully advanced to the next change in the changeset, an SQLite
-** error code if an error occurs, or SQLITE_DONE if there are no further
-** changes in the changeset.
+** * If the iterator is configured to skip no-op UPDATEs,
+** sessionChangesetNext() does that. This function does not.
*/
-static int sessionChangesetNext(
+static int sessionChangesetNextOne(
sqlite3_changeset_iter *p, /* Changeset iterator */
u8 **paRec, /* If non-NULL, store record pointer here */
int *pnRec, /* If non-NULL, store size of record here */
- int *pbNew /* If non-NULL, true if new table */
+ int *pbNew, /* If non-NULL, true if new table */
+ int *pbEmpty
){
int i;
u8 op;
assert( (paRec==0 && pnRec==0) || (paRec && pnRec) );
+ assert( pbEmpty==0 || *pbEmpty==0 );
/* If the iterator is in the error-state, return immediately. */
if( p->rc!=SQLITE_OK ) return p->rc;
@@ -3064,13 +3105,13 @@ static int sessionChangesetNext(
/* If this is an UPDATE or DELETE, read the old.* record. */
if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){
u8 *abPK = p->bPatchset ? p->abPK : 0;
- p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld);
+ p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld, 0);
if( p->rc!=SQLITE_OK ) return p->rc;
}
/* If this is an INSERT or UPDATE, read the new.* record. */
if( p->op!=SQLITE_DELETE ){
- p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew);
+ p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew, pbEmpty);
if( p->rc!=SQLITE_OK ) return p->rc;
}
@@ -3098,6 +3139,37 @@ static int sessionChangesetNext(
}
/*
+** Advance the changeset iterator to the next change.
+**
+** If both paRec and pnRec are NULL, then this function works like the public
+** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the
+** sqlite3changeset_new() and old() APIs may be used to query for values.
+**
+** Otherwise, if paRec and pnRec are not NULL, then a pointer to the change
+** record is written to *paRec before returning and the number of bytes in
+** the record to *pnRec.
+**
+** Either way, this function returns SQLITE_ROW if the iterator is
+** successfully advanced to the next change in the changeset, an SQLite
+** error code if an error occurs, or SQLITE_DONE if there are no further
+** changes in the changeset.
+*/
+static int sessionChangesetNext(
+ sqlite3_changeset_iter *p, /* Changeset iterator */
+ u8 **paRec, /* If non-NULL, store record pointer here */
+ int *pnRec, /* If non-NULL, store size of record here */
+ int *pbNew /* If non-NULL, true if new table */
+){
+ int bEmpty;
+ int rc;
+ do {
+ bEmpty = 0;
+ rc = sessionChangesetNextOne(p, paRec, pnRec, pbNew, &bEmpty);
+ }while( rc==SQLITE_ROW && p->bSkipEmpty && bEmpty);
+ return rc;
+}
+
+/*
** Advance an iterator created by sqlite3changeset_start() to the next
** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE
** or SQLITE_CORRUPT.
@@ -3369,9 +3441,9 @@ static int sessionChangesetInvert(
/* Read the old.* and new.* records for the update change. */
pInput->iNext += 2;
- rc = sessionReadRecord(pInput, nCol, 0, &apVal[0]);
+ rc = sessionReadRecord(pInput, nCol, 0, &apVal[0], 0);
if( rc==SQLITE_OK ){
- rc = sessionReadRecord(pInput, nCol, 0, &apVal[nCol]);
+ rc = sessionReadRecord(pInput, nCol, 0, &apVal[nCol], 0);
}
/* Write the new old.* record. Consists of the PK columns from the
@@ -3472,16 +3544,25 @@ int sqlite3changeset_invert_strm(
return rc;
}
+
+typedef struct SessionUpdate SessionUpdate;
+struct SessionUpdate {
+ sqlite3_stmt *pStmt;
+ u32 *aMask;
+ SessionUpdate *pNext;
+};
+
typedef struct SessionApplyCtx SessionApplyCtx;
struct SessionApplyCtx {
sqlite3 *db;
sqlite3_stmt *pDelete; /* DELETE statement */
- sqlite3_stmt *pUpdate; /* UPDATE statement */
sqlite3_stmt *pInsert; /* INSERT statement */
sqlite3_stmt *pSelect; /* SELECT statement */
int nCol; /* Size of azCol[] and abPK[] arrays */
const char **azCol; /* Array of column names */
u8 *abPK; /* Boolean array - true if column is in PK */
+ u32 *aUpdateMask; /* Used by sessionUpdateFind */
+ SessionUpdate *pUp;
int bStat1; /* True if table is sqlite_stat1 */
int bDeferConstraints; /* True to defer constraints */
int bInvertConstraints; /* Invert when iterating constraints buffer */
@@ -3491,6 +3572,167 @@ struct SessionApplyCtx {
u8 bRebase; /* True to collect rebase information */
};
+/* Number of prepared UPDATE statements to cache. */
+#define SESSION_UPDATE_CACHE_SZ 12
+
+/*
+** Find a prepared UPDATE statement suitable for the UPDATE step currently
+** being visited by the iterator. The UPDATE is of the form:
+**
+** UPDATE tbl SET col = ?, col2 = ? WHERE pk1 IS ? AND pk2 IS ?
+*/
+static int sessionUpdateFind(
+ sqlite3_changeset_iter *pIter,
+ SessionApplyCtx *p,
+ int bPatchset,
+ sqlite3_stmt **ppStmt
+){
+ int rc = SQLITE_OK;
+ SessionUpdate *pUp = 0;
+ int nCol = pIter->nCol;
+ int nU32 = (pIter->nCol+33)/32;
+ int ii;
+
+ if( p->aUpdateMask==0 ){
+ p->aUpdateMask = sqlite3_malloc(nU32*sizeof(u32));
+ if( p->aUpdateMask==0 ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ memset(p->aUpdateMask, 0, nU32*sizeof(u32));
+ rc = SQLITE_CORRUPT;
+ for(ii=0; ii<pIter->nCol; ii++){
+ if( sessionChangesetNew(pIter, ii) ){
+ p->aUpdateMask[ii/32] |= (1<<(ii%32));
+ rc = SQLITE_OK;
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ if( bPatchset ) p->aUpdateMask[nCol/32] |= (1<<(nCol%32));
+
+ if( p->pUp ){
+ int nUp = 0;
+ SessionUpdate **pp = &p->pUp;
+ while( 1 ){
+ nUp++;
+ if( 0==memcmp(p->aUpdateMask, (*pp)->aMask, nU32*sizeof(u32)) ){
+ pUp = *pp;
+ *pp = pUp->pNext;
+ pUp->pNext = p->pUp;
+ p->pUp = pUp;
+ break;
+ }
+
+ if( (*pp)->pNext ){
+ pp = &(*pp)->pNext;
+ }else{
+ if( nUp>=SESSION_UPDATE_CACHE_SZ ){
+ sqlite3_finalize((*pp)->pStmt);
+ sqlite3_free(*pp);
+ *pp = 0;
+ }
+ break;
+ }
+ }
+ }
+
+ if( pUp==0 ){
+ int nByte = sizeof(SessionUpdate) * nU32*sizeof(u32);
+ int bStat1 = (sqlite3_stricmp(pIter->zTab, "sqlite_stat1")==0);
+ pUp = (SessionUpdate*)sqlite3_malloc(nByte);
+ if( pUp==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ const char *zSep = "";
+ SessionBuffer buf;
+
+ memset(&buf, 0, sizeof(buf));
+ pUp->aMask = (u32*)&pUp[1];
+ memcpy(pUp->aMask, p->aUpdateMask, nU32*sizeof(u32));
+
+ sessionAppendStr(&buf, "UPDATE main.", &rc);
+ sessionAppendIdent(&buf, pIter->zTab, &rc);
+ sessionAppendStr(&buf, " SET ", &rc);
+
+ /* Create the assignments part of the UPDATE */
+ for(ii=0; ii<pIter->nCol; ii++){
+ if( p->abPK[ii]==0 && sessionChangesetNew(pIter, ii) ){
+ sessionAppendStr(&buf, zSep, &rc);
+ sessionAppendIdent(&buf, p->azCol[ii], &rc);
+ sessionAppendStr(&buf, " = ?", &rc);
+ sessionAppendInteger(&buf, ii*2+1, &rc);
+ zSep = ", ";
+ }
+ }
+
+ /* Create the WHERE clause part of the UPDATE */
+ zSep = "";
+ sessionAppendStr(&buf, " WHERE ", &rc);
+ for(ii=0; ii<pIter->nCol; ii++){
+ if( p->abPK[ii] || (bPatchset==0 && sessionChangesetOld(pIter, ii)) ){
+ sessionAppendStr(&buf, zSep, &rc);
+ if( bStat1 && ii==1 ){
+ assert( sqlite3_stricmp(p->azCol[ii], "idx")==0 );
+ sessionAppendStr(&buf,
+ "idx IS CASE "
+ "WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL "
+ "ELSE ?4 END ", &rc
+ );
+ }else{
+ sessionAppendIdent(&buf, p->azCol[ii], &rc);
+ sessionAppendStr(&buf, " IS ?", &rc);
+ sessionAppendInteger(&buf, ii*2+2, &rc);
+ }
+ zSep = " AND ";
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ char *zSql = (char*)buf.aBuf;
+ rc = sqlite3_prepare_v2(p->db, zSql, buf.nBuf, &pUp->pStmt, 0);
+ }
+
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(pUp);
+ pUp = 0;
+ }else{
+ pUp->pNext = p->pUp;
+ p->pUp = pUp;
+ }
+ sqlite3_free(buf.aBuf);
+ }
+ }
+ }
+
+ assert( (rc==SQLITE_OK)==(pUp!=0) );
+ if( pUp ){
+ *ppStmt = pUp->pStmt;
+ }else{
+ *ppStmt = 0;
+ }
+ return rc;
+}
+
+/*
+** Free all cached UPDATE statements.
+*/
+static void sessionUpdateFree(SessionApplyCtx *p){
+ SessionUpdate *pUp;
+ SessionUpdate *pNext;
+ for(pUp=p->pUp; pUp; pUp=pNext){
+ pNext = pUp->pNext;
+ sqlite3_finalize(pUp->pStmt);
+ sqlite3_free(pUp);
+ }
+ p->pUp = 0;
+ sqlite3_free(p->aUpdateMask);
+ p->aUpdateMask = 0;
+}
+
/*
** Formulate a statement to DELETE a row from database db. Assuming a table
** structure like this:
@@ -3561,103 +3803,6 @@ static int sessionDeleteRow(
}
/*
-** Formulate and prepare a statement to UPDATE a row from database db.
-** Assuming a table structure like this:
-**
-** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
-**
-** The UPDATE statement looks like this:
-**
-** UPDATE x SET
-** a = CASE WHEN ?2 THEN ?3 ELSE a END,
-** b = CASE WHEN ?5 THEN ?6 ELSE b END,
-** c = CASE WHEN ?8 THEN ?9 ELSE c END,
-** d = CASE WHEN ?11 THEN ?12 ELSE d END
-** WHERE a = ?1 AND c = ?7 AND (?13 OR
-** (?5==0 OR b IS ?4) AND (?11==0 OR d IS ?10) AND
-** )
-**
-** For each column in the table, there are three variables to bind:
-**
-** ?(i*3+1) The old.* value of the column, if any.
-** ?(i*3+2) A boolean flag indicating that the value is being modified.
-** ?(i*3+3) The new.* value of the column, if any.
-**
-** Also, a boolean flag that, if set to true, causes the statement to update
-** a row even if the non-PK values do not match. This is required if the
-** conflict-handler is invoked with CHANGESET_DATA and returns
-** CHANGESET_REPLACE. This is variable "?(nCol*3+1)".
-**
-** If successful, SQLITE_OK is returned and SessionApplyCtx.pUpdate is left
-** pointing to the prepared version of the SQL statement.
-*/
-static int sessionUpdateRow(
- sqlite3 *db, /* Database handle */
- const char *zTab, /* Table name */
- SessionApplyCtx *p /* Session changeset-apply context */
-){
- int rc = SQLITE_OK;
- int i;
- const char *zSep = "";
- SessionBuffer buf = {0, 0, 0};
-
- /* Append "UPDATE tbl SET " */
- sessionAppendStr(&buf, "UPDATE main.", &rc);
- sessionAppendIdent(&buf, zTab, &rc);
- sessionAppendStr(&buf, " SET ", &rc);
-
- /* Append the assignments */
- for(i=0; i<p->nCol; i++){
- sessionAppendStr(&buf, zSep, &rc);
- sessionAppendIdent(&buf, p->azCol[i], &rc);
- sessionAppendStr(&buf, " = CASE WHEN ?", &rc);
- sessionAppendInteger(&buf, i*3+2, &rc);
- sessionAppendStr(&buf, " THEN ?", &rc);
- sessionAppendInteger(&buf, i*3+3, &rc);
- sessionAppendStr(&buf, " ELSE ", &rc);
- sessionAppendIdent(&buf, p->azCol[i], &rc);
- sessionAppendStr(&buf, " END", &rc);
- zSep = ", ";
- }
-
- /* Append the PK part of the WHERE clause */
- sessionAppendStr(&buf, " WHERE ", &rc);
- for(i=0; i<p->nCol; i++){
- if( p->abPK[i] ){
- sessionAppendIdent(&buf, p->azCol[i], &rc);
- sessionAppendStr(&buf, " = ?", &rc);
- sessionAppendInteger(&buf, i*3+1, &rc);
- sessionAppendStr(&buf, " AND ", &rc);
- }
- }
-
- /* Append the non-PK part of the WHERE clause */
- sessionAppendStr(&buf, " (?", &rc);
- sessionAppendInteger(&buf, p->nCol*3+1, &rc);
- sessionAppendStr(&buf, " OR 1", &rc);
- for(i=0; i<p->nCol; i++){
- if( !p->abPK[i] ){
- sessionAppendStr(&buf, " AND (?", &rc);
- sessionAppendInteger(&buf, i*3+2, &rc);
- sessionAppendStr(&buf, "=0 OR ", &rc);
- sessionAppendIdent(&buf, p->azCol[i], &rc);
- sessionAppendStr(&buf, " IS ?", &rc);
- sessionAppendInteger(&buf, i*3+1, &rc);
- sessionAppendStr(&buf, ")", &rc);
- }
- }
- sessionAppendStr(&buf, ")", &rc);
-
- if( rc==SQLITE_OK ){
- rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0);
- }
- sqlite3_free(buf.aBuf);
-
- return rc;
-}
-
-
-/*
** Formulate and prepare an SQL statement to query table zTab by primary
** key. Assuming the following table structure:
**
@@ -3738,17 +3883,6 @@ static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){
);
}
if( rc==SQLITE_OK ){
- rc = sessionPrepare(db, &p->pUpdate,
- "UPDATE main.sqlite_stat1 SET "
- "tbl = CASE WHEN ?2 THEN ?3 ELSE tbl END, "
- "idx = CASE WHEN ?5 THEN ?6 ELSE idx END, "
- "stat = CASE WHEN ?8 THEN ?9 ELSE stat END "
- "WHERE tbl=?1 AND idx IS "
- "CASE WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL ELSE ?4 END "
- "AND (?10 OR ?8=0 OR stat IS ?7)"
- );
- }
- if( rc==SQLITE_OK ){
rc = sessionPrepare(db, &p->pDelete,
"DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS "
"CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END "
@@ -4064,7 +4198,7 @@ static int sessionApplyOneOp(
int nCol;
int rc = SQLITE_OK;
- assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect );
+ assert( p->pDelete && p->pInsert && p->pSelect );
assert( p->azCol && p->abPK );
assert( !pbReplace || *pbReplace==0 );
@@ -4104,29 +4238,28 @@ static int sessionApplyOneOp(
}else if( op==SQLITE_UPDATE ){
int i;
+ sqlite3_stmt *pUp = 0;
+ int bPatchset = (pbRetry==0 || pIter->bPatchset);
+
+ rc = sessionUpdateFind(pIter, p, bPatchset, &pUp);
/* Bind values to the UPDATE statement. */
for(i=0; rc==SQLITE_OK && i<nCol; i++){
sqlite3_value *pOld = sessionChangesetOld(pIter, i);
sqlite3_value *pNew = sessionChangesetNew(pIter, i);
-
- sqlite3_bind_int(p->pUpdate, i*3+2, !!pNew);
- if( pOld ){
- rc = sessionBindValue(p->pUpdate, i*3+1, pOld);
+ if( p->abPK[i] || (bPatchset==0 && pOld) ){
+ rc = sessionBindValue(pUp, i*2+2, pOld);
}
if( rc==SQLITE_OK && pNew ){
- rc = sessionBindValue(p->pUpdate, i*3+3, pNew);
+ rc = sessionBindValue(pUp, i*2+1, pNew);
}
}
- if( rc==SQLITE_OK ){
- sqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0 || pIter->bPatchset);
- }
if( rc!=SQLITE_OK ) return rc;
/* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict,
** the result will be SQLITE_OK with 0 rows modified. */
- sqlite3_step(p->pUpdate);
- rc = sqlite3_reset(p->pUpdate);
+ sqlite3_step(pUp);
+ rc = sqlite3_reset(pUp);
if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
/* A NOTFOUND or DATA error. Search the table to see if it contains
@@ -4258,7 +4391,7 @@ static int sessionRetryConstraints(
memset(&pApply->constraints, 0, sizeof(SessionBuffer));
rc = sessionChangesetStart(
- &pIter2, 0, 0, cons.nBuf, cons.aBuf, pApply->bInvertConstraints
+ &pIter2, 0, 0, cons.nBuf, cons.aBuf, pApply->bInvertConstraints, 1
);
if( rc==SQLITE_OK ){
size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*);
@@ -4349,14 +4482,13 @@ static int sessionChangesetApply(
);
if( rc!=SQLITE_OK ) break;
+ sessionUpdateFree(&sApply);
sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */
sqlite3_finalize(sApply.pDelete);
- sqlite3_finalize(sApply.pUpdate);
sqlite3_finalize(sApply.pInsert);
sqlite3_finalize(sApply.pSelect);
sApply.db = db;
sApply.pDelete = 0;
- sApply.pUpdate = 0;
sApply.pInsert = 0;
sApply.pSelect = 0;
sApply.nCol = 0;
@@ -4384,7 +4516,7 @@ static int sessionChangesetApply(
int i;
sqlite3changeset_pk(pIter, &abPK, 0);
- rc = sessionTableInfo(
+ rc = sessionTableInfo(0,
db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK
);
if( rc!=SQLITE_OK ) break;
@@ -4420,11 +4552,10 @@ static int sessionChangesetApply(
}
sApply.bStat1 = 1;
}else{
- if((rc = sessionSelectRow(db, zTab, &sApply))
- || (rc = sessionUpdateRow(db, zTab, &sApply))
- || (rc = sessionDeleteRow(db, zTab, &sApply))
- || (rc = sessionInsertRow(db, zTab, &sApply))
- ){
+ if( (rc = sessionSelectRow(db, zTab, &sApply))
+ || (rc = sessionDeleteRow(db, zTab, &sApply))
+ || (rc = sessionInsertRow(db, zTab, &sApply))
+ ){
break;
}
sApply.bStat1 = 0;
@@ -4483,9 +4614,9 @@ static int sessionChangesetApply(
*pnRebase = sApply.rebase.nBuf;
sApply.rebase.aBuf = 0;
}
+ sessionUpdateFree(&sApply);
sqlite3_finalize(sApply.pInsert);
sqlite3_finalize(sApply.pDelete);
- sqlite3_finalize(sApply.pUpdate);
sqlite3_finalize(sApply.pSelect);
sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */
sqlite3_free((char*)sApply.constraints.aBuf);
@@ -4516,8 +4647,8 @@ int sqlite3changeset_apply_v2(
int flags
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
- int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
- int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset,bInverse);
+ int bInv = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
+ int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset, bInv, 1);
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
@@ -4575,7 +4706,7 @@ int sqlite3changeset_apply_v2_strm(
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
- int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse);
+ int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse, 1);
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
@@ -4863,7 +4994,7 @@ static int sessionChangesetToHash(
}
}
- if( sessionGrowHash(pIter->bPatchset, pTab) ){
+ if( sessionGrowHash(0, pIter->bPatchset, pTab) ){
rc = SQLITE_NOMEM;
break;
}
@@ -5049,7 +5180,7 @@ int sqlite3changegroup_output_strm(
*/
void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){
if( pGrp ){
- sessionDeleteTable(pGrp->pList);
+ sessionDeleteTable(0, pGrp->pList);
sqlite3_free(pGrp);
}
}
@@ -5195,7 +5326,7 @@ static void sessionAppendPartialUpdate(
int n1 = sessionSerialLen(a1);
int n2 = sessionSerialLen(a2);
if( pIter->abPK[i] || a2[0]==0 ){
- if( !pIter->abPK[i] ) bData = 1;
+ if( !pIter->abPK[i] && a1[0] ) bData = 1;
memcpy(pOut, a1, n1);
pOut += n1;
}else if( a2[0]!=0xFF ){
@@ -5450,7 +5581,7 @@ int sqlite3rebaser_rebase_strm(
*/
void sqlite3rebaser_delete(sqlite3_rebaser *p){
if( p ){
- sessionDeleteTable(p->grp.pList);
+ sessionDeleteTable(0, p->grp.pList);
sqlite3_free(p);
}
}
diff --git a/chromium/third_party/sqlite/src/ext/session/sqlite3session.h b/chromium/third_party/sqlite/src/ext/session/sqlite3session.h
index 1ed3b550f43..9ebf360ea3e 100644
--- a/chromium/third_party/sqlite/src/ext/session/sqlite3session.h
+++ b/chromium/third_party/sqlite/src/ext/session/sqlite3session.h
@@ -441,6 +441,14 @@ int sqlite3session_patchset(
int sqlite3session_isempty(sqlite3_session *pSession);
/*
+** CAPI3REF: Query for the amount of heap memory used by a session object.
+**
+** This API returns the total amount of heap memory in bytes currently
+** used by the session object passed as the only argument.
+*/
+sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession);
+
+/*
** CAPI3REF: Create An Iterator To Traverse A Changeset
** CONSTRUCTOR: sqlite3_changeset_iter
**
@@ -542,18 +550,23 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this
** is not the case, this function returns [SQLITE_MISUSE].
**
-** If argument pzTab is not NULL, then *pzTab is set to point to a
-** nul-terminated utf-8 encoded string containing the name of the table
-** affected by the current change. The buffer remains valid until either
-** sqlite3changeset_next() is called on the iterator or until the
-** conflict-handler function returns. If pnCol is not NULL, then *pnCol is
-** set to the number of columns in the table affected by the change. If
-** pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change
+** Arguments pOp, pnCol and pzTab may not be NULL. Upon return, three
+** outputs are set through these pointers:
+**
+** *pOp is set to one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE],
+** depending on the type of change that the iterator currently points to;
+**
+** *pnCol is set to the number of columns in the table affected by the change; and
+**
+** *pzTab is set to point to a nul-terminated utf-8 encoded string containing
+** the name of the table affected by the current change. The buffer remains
+** valid until either sqlite3changeset_next() is called on the iterator
+** or until the conflict-handler function returns.
+**
+** If pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change
** is an indirect change, or false (0) otherwise. See the documentation for
** [sqlite3session_indirect()] for a description of direct and indirect
-** changes. Finally, if pOp is not NULL, then *pOp is set to one of
-** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the
-** type of change that the iterator currently points to.
+** changes.
**
** If no error occurs, SQLITE_OK is returned. If an error does occur, an
** SQLite error code is returned. The values of the output variables may not
diff --git a/chromium/third_party/sqlite/src/ext/session/test_session.c b/chromium/third_party/sqlite/src/ext/session/test_session.c
index 82f30979f51..c3898fd860e 100644
--- a/chromium/third_party/sqlite/src/ext/session/test_session.c
+++ b/chromium/third_party/sqlite/src/ext/session/test_session.c
@@ -146,7 +146,10 @@ static int SQLITE_TCLAPI test_sql_exec_changeset(
static int test_tcl_integer(Tcl_Interp *interp, const char *zVar){
Tcl_Obj *pObj;
int iVal = 0;
- pObj = Tcl_ObjGetVar2(interp, Tcl_NewStringObj(zVar, -1), 0, TCL_GLOBAL_ONLY);
+ Tcl_Obj *pName = Tcl_NewStringObj(zVar, -1);
+ Tcl_IncrRefCount(pName);
+ pObj = Tcl_ObjGetVar2(interp, pName, 0, TCL_GLOBAL_ONLY);
+ Tcl_DecrRefCount(pName);
if( pObj ) Tcl_GetIntFromObj(0, pObj, &iVal);
return iVal;
}
@@ -242,6 +245,7 @@ static int SQLITE_TCLAPI test_session_cmd(
{ "table_filter", 1, "SCRIPT", }, /* 6 */
{ "patchset", 0, "", }, /* 7 */
{ "diff", 2, "FROMDB TBL", }, /* 8 */
+ { "memory_used", 0, "", }, /* 9 */
{ 0 }
};
int iSub;
@@ -347,6 +351,12 @@ static int SQLITE_TCLAPI test_session_cmd(
}
break;
}
+
+ case 9: { /* memory_used */
+ sqlite3_int64 nMalloc = sqlite3session_memory_used(pSession);
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nMalloc));
+ break;
+ }
}
return TCL_OK;