Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -1078,11 +1078,11 @@ fastfuzztest: fuzzcheck$(TEXE) $(FUZZDATA) ./fuzzcheck$(TEXE) --limit-mem 100M $(FUZZDATA) valgrindfuzz: fuzzcheck$(TEXT) $(FUZZDATA) - valgrind ./fuzzcheck$(TEXE) --cell-size-check --limit-mem 10M $(FUZZDATA) + valgrind ./fuzzcheck$(TEXE) --cell-size-check --limit-mem 10M --timeout 600 $(FUZZDATA) # Minimal testing that runs in less than 3 minutes # quicktest: ./testfixture$(TEXE) ./testfixture$(TEXE) $(TOP)/test/extraquick.test $(TESTOPTS) Index: ext/fts5/fts5Int.h ================================================================== --- ext/fts5/fts5Int.h +++ ext/fts5/fts5Int.h @@ -158,10 +158,11 @@ /* Values loaded from the %_config table */ int iCookie; /* Incremented when %_config is modified */ int pgsz; /* Approximate page size used in %_data */ int nAutomerge; /* 'automerge' setting */ int nCrisisMerge; /* Maximum allowed segments per level */ + int nHashSize; /* Bytes of memory for in-memory hash */ char *zRank; /* Name of rank function */ char *zRankArgs; /* Arguments to rank function */ /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */ char **pzErrmsg; Index: ext/fts5/fts5_config.c ================================================================== --- ext/fts5/fts5_config.c +++ ext/fts5/fts5_config.c @@ -18,10 +18,11 @@ #include "fts5Int.h" #define FTS5_DEFAULT_PAGE_SIZE 4050 #define FTS5_DEFAULT_AUTOMERGE 4 #define FTS5_DEFAULT_CRISISMERGE 16 +#define FTS5_DEFAULT_HASHSIZE (1024*1024) /* Maximum allowed page size */ #define FTS5_MAX_PAGE_SIZE (128*1024) static int fts5_iswhitespace(char x){ @@ -704,37 +705,41 @@ int rc = SQLITE_OK; *pzRank = 0; *pzRankArgs = 0; - p = fts5ConfigSkipWhitespace(p); - pRank = p; - p = fts5ConfigSkipBareword(p); - - if( p ){ - zRank = sqlite3Fts5MallocZero(&rc, 1 + p - pRank); - if( zRank ) memcpy(zRank, pRank, p-pRank); - }else{ - rc = SQLITE_ERROR; - } - - if( rc==SQLITE_OK ){ - p = fts5ConfigSkipWhitespace(p); - if( *p!='(' ) rc = SQLITE_ERROR; - p++; - } - if( rc==SQLITE_OK ){ - const char *pArgs; - p = fts5ConfigSkipWhitespace(p); - pArgs = p; - if( *p!=')' ){ - p = fts5ConfigSkipArgs(p); - if( p==0 ){ - rc = SQLITE_ERROR; - }else{ - zRankArgs = sqlite3Fts5MallocZero(&rc, 1 + p - pArgs); - if( zRankArgs ) memcpy(zRankArgs, pArgs, p-pArgs); + if( p==0 ){ + rc = SQLITE_ERROR; + }else{ + p = fts5ConfigSkipWhitespace(p); + pRank = p; + p = fts5ConfigSkipBareword(p); + + if( p ){ + zRank = sqlite3Fts5MallocZero(&rc, 1 + p - pRank); + if( zRank ) memcpy(zRank, pRank, p-pRank); + }else{ + rc = SQLITE_ERROR; + } + + if( rc==SQLITE_OK ){ + p = fts5ConfigSkipWhitespace(p); + if( *p!='(' ) rc = SQLITE_ERROR; + p++; + } + if( rc==SQLITE_OK ){ + const char *pArgs; + p = fts5ConfigSkipWhitespace(p); + pArgs = p; + if( *p!=')' ){ + p = fts5ConfigSkipArgs(p); + if( p==0 ){ + rc = SQLITE_ERROR; + }else{ + zRankArgs = sqlite3Fts5MallocZero(&rc, 1 + p - pArgs); + if( zRankArgs ) memcpy(zRankArgs, pArgs, p-pArgs); + } } } } if( rc!=SQLITE_OK ){ @@ -764,10 +769,22 @@ *pbBadkey = 1; }else{ pConfig->pgsz = pgsz; } } + + else if( 0==sqlite3_stricmp(zKey, "hashsize") ){ + int nHashSize = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + nHashSize = sqlite3_value_int(pVal); + } + if( nHashSize<=0 ){ + *pbBadkey = 1; + }else{ + pConfig->nHashSize = nHashSize; + } + } else if( 0==sqlite3_stricmp(zKey, "automerge") ){ int nAutomerge = -1; if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ nAutomerge = sqlite3_value_int(pVal); @@ -825,10 +842,11 @@ /* Set default values */ pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE; pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE; pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; + pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE; zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName); if( zSql ){ rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p, 0); sqlite3_free(zSql); Index: ext/fts5/fts5_index.c ================================================================== --- ext/fts5/fts5_index.c +++ ext/fts5/fts5_index.c @@ -286,11 +286,10 @@ /* ** Variables related to the accumulation of tokens and doclists within the ** in-memory hash tables before they are flushed to disk. */ Fts5Hash *pHash; /* Hash table for in-memory data */ - int nMaxPendingData; /* Max pending data before flush to disk */ int nPendingData; /* Current bytes of pending data */ i64 iWriteRowid; /* Rowid for current doc being written */ int bDelete; /* Current write is a delete */ /* Error state. */ @@ -4451,11 +4450,11 @@ } /* Flush the hash table to disk if required */ if( iRowidiWriteRowid || (iRowid==p->iWriteRowid && p->bDelete==0) - || (p->nPendingData > p->nMaxPendingData) + || (p->nPendingData > p->pConfig->nHashSize) ){ fts5IndexFlush(p); } p->iWriteRowid = iRowid; @@ -4517,11 +4516,10 @@ *pp = p = (Fts5Index*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Index)); if( rc==SQLITE_OK ){ p->pConfig = pConfig; p->nWorkUnit = FTS5_WORK_UNIT; - p->nMaxPendingData = 1024*1024; p->zDataTbl = sqlite3Fts5Mprintf(&rc, "%s_data", pConfig->zName); if( p->zDataTbl && bCreate ){ rc = sqlite3Fts5CreateTable( pConfig, "data", "id INTEGER PRIMARY KEY, block BLOB", 0, pzErr ); Index: ext/fts5/fts5_main.c ================================================================== --- ext/fts5/fts5_main.c +++ ext/fts5/fts5_main.c @@ -392,10 +392,19 @@ /* Call sqlite3_declare_vtab() */ if( rc==SQLITE_OK ){ rc = sqlite3Fts5ConfigDeclareVtab(pConfig); } + + /* Load the initial configuration */ + if( rc==SQLITE_OK ){ + assert( pConfig->pzErrmsg==0 ); + pConfig->pzErrmsg = pzErr; + rc = sqlite3Fts5IndexLoadConfig(pTab->pIndex); + sqlite3Fts5IndexRollback(pTab->pIndex); + pConfig->pzErrmsg = 0; + } if( rc!=SQLITE_OK ){ fts5FreeVtab(pTab); pTab = 0; }else if( bCreate ){ @@ -827,10 +836,40 @@ } return rc; } + +static sqlite3_stmt *fts5PrepareStatement( + int *pRc, + Fts5Config *pConfig, + const char *zFmt, + ... +){ + sqlite3_stmt *pRet = 0; + va_list ap; + va_start(ap, zFmt); + + if( *pRc==SQLITE_OK ){ + int rc; + char *zSql = sqlite3_vmprintf(zFmt, ap); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pRet, 0); + if( rc!=SQLITE_OK ){ + *pConfig->pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(pConfig->db)); + } + sqlite3_free(zSql); + } + *pRc = rc; + } + + va_end(ap); + return pRet; +} + static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){ Fts5Config *pConfig = pTab->pConfig; Fts5Sorter *pSorter; int nPhrase; int nByte; @@ -851,21 +890,17 @@ ** is not possible as SQLite reference counts the virtual table objects. ** And since the statement required here reads from this very virtual ** table, saving it creates a circular reference. ** ** If SQLite a built-in statement cache, this wouldn't be a problem. */ - zSql = sqlite3Fts5Mprintf(&rc, + pSorter->pStmt = fts5PrepareStatement(&rc, pConfig, "SELECT rowid, rank FROM %Q.%Q ORDER BY %s(%s%s%s) %s", pConfig->zDb, pConfig->zName, zRank, pConfig->zName, (zRankArgs ? ", " : ""), (zRankArgs ? zRankArgs : ""), bDesc ? "DESC" : "ASC" ); - if( zSql ){ - rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pSorter->pStmt, 0); - sqlite3_free(zSql); - } pCsr->pSorter = pSorter; if( rc==SQLITE_OK ){ assert( pTab->pSortCsr==0 ); pTab->pSortCsr = pCsr; Index: ext/fts5/test/fts5fault1.test ================================================================== --- ext/fts5/test/fts5fault1.test +++ ext/fts5/test/fts5fault1.test @@ -50,11 +50,11 @@ } -body { execsql { INSERT INTO t1 VALUES('a b c', 'a bc def ghij klmno'); } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} {1 {vtable constructor failed: t1}} } reset_db do_execsql_test 3.0 { CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix='1, 2, 3'); @@ -64,11 +64,11 @@ do_faultsim_test 3 -prep { faultsim_restore_and_reopen } -body { execsql { DELETE FROM t1 } } -test { - faultsim_test_result {0 {}} + faultsim_test_result {0 {}} {1 {vtable constructor failed: t1}} } reset_db do_execsql_test 4.0 { CREATE VIRTUAL TABLE t2 USING fts5(a, b); @@ -99,11 +99,11 @@ do_faultsim_test 4.$tn -prep { faultsim_restore_and_reopen } -body " execsql { SELECT rowid FROM t2 WHERE t2 MATCH '$expr' } " -test " - faultsim_test_result {[list 0 $res]} + faultsim_test_result {[list 0 $res]} {1 {vtable constructor failed: t2}} " } #------------------------------------------------------------------------- Index: ext/fts5/test/fts5fault4.test ================================================================== --- ext/fts5/test/fts5fault4.test +++ ext/fts5/test/fts5fault4.test @@ -105,11 +105,11 @@ set ::res [db eval {SELECT rowid, x1 FROM x1 WHERE x1 MATCH '*reads'}] do_faultsim_test 4 -faults oom-* -body { db eval {SELECT rowid, x, x1 FROM x1 WHERE x1 MATCH '*reads'} } -test { - faultsim_test_result {0 {0 {} 3}} + faultsim_test_result {0 {0 {} 4}} } #------------------------------------------------------------------------- # An OOM within a query that uses a custom rank function. # Index: ext/fts5/test/fts5rank.test ================================================================== --- ext/fts5/test/fts5rank.test +++ ext/fts5/test/fts5rank.test @@ -38,8 +38,63 @@ do_execsql_test 1.3 { SELECT highlight(xyz, 0, '[', ']') FROM xyz WHERE xyz MATCH 'x AND y' ORDER BY rank } [list [string map {x [x] y [y]} $doc]] + +#------------------------------------------------------------------------- +# Check that the 'rank' option really is persistent. +# +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE tt USING fts5(a); + INSERT INTO tt VALUES('a x x x x'); + INSERT INTO tt VALUES('x x a a a'); + INSERT INTO tt VALUES('x a a x x'); +} + +proc firstinst {cmd} { + foreach {p c o} [$cmd xInst 0] {} + return $o +} +sqlite3_fts5_create_function db firstinst firstinst + +do_execsql_test 2.1 { + SELECT rowid FROM tt('a') ORDER BY rank; +} {2 3 1} + +do_execsql_test 2.2 { + SELECT rowid FROM tt('a', 'firstinst()') ORDER BY rank; +} {1 3 2} + +do_execsql_test 2.3 { + INSERT INTO tt(tt, rank) VALUES('rank', 'firstinst()'); + SELECT rowid FROM tt('a') ORDER BY rank; +} {1 3 2} + +do_test 2.4 { + sqlite3 db2 test.db + catchsql { SELECT rowid FROM tt('a') ORDER BY rank; } db2 +} {1 {no such function: firstinst}} + +do_test 2.5 { + db2 close + sqlite3 db2 test.db + sqlite3_fts5_create_function db2 firstinst firstinst + execsql { SELECT rowid FROM tt('a') ORDER BY rank; } db2 +} {1 3 2} + +do_test 2.6 { + execsql { SELECT rowid FROM tt('a') ORDER BY rank; } db2 +} {1 3 2} + +do_test 2.7 { + execsql { SELECT rowid FROM tt('a') ORDER BY rank; } db +} {1 3 2} + + + + + + finish_test Index: ext/fts5/test/fts5simple.test ================================================================== --- ext/fts5/test/fts5simple.test +++ ext/fts5/test/fts5simple.test @@ -16,11 +16,11 @@ # If SQLITE_ENABLE_FTS5 is defined, omit this file. ifcapable !fts5 { finish_test return } - + #------------------------------------------------------------------------- # set doc "x x [string repeat {y } 50]z z" do_execsql_test 1.0 { CREATE VIRTUAL TABLE t1 USING fts5(x); @@ -319,8 +319,37 @@ } do_execsql_test 13.3 { INSERT INTO xy(xy) VALUES('integrity-check'); } + +#------------------------------------------------------------------------- +# +do_execsql_test 14.1 { + CREATE VIRTUAL TABLE ttt USING fts5(x); + BEGIN; + INSERT INTO ttt(rowid, x) VALUES(1, 'a b c'); + INSERT INTO ttt(rowid, x) VALUES(2, 'a b c'); + INSERT INTO ttt(rowid, x) VALUES(3, 'a b c'); + COMMIT; +} +do_test 14.2 { + fts5_level_segs ttt +} {1} + +#------------------------------------------------------------------------- +db func rnddoc fts5_rnddoc +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE x1 USING fts5(x); + INSERT INTO x1(x1, rank) VALUES('pgsz', 32); + + WITH ii(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<10 ) + INSERT INTO x1 SELECT rnddoc(5) FROM ii; +} + +do_execsql_test 4.1 { + SELECT rowid, x, x1 FROM x1 WHERE x1 MATCH '*reads' +} {0 {} 4} + finish_test Index: ext/fts5/tool/loadfts5.tcl ================================================================== --- ext/fts5/tool/loadfts5.tcl +++ ext/fts5/tool/loadfts5.tcl @@ -46,10 +46,11 @@ puts stderr " -limit N (load no more than N documents)" puts stderr " -automerge N (set the automerge parameter to N)" puts stderr " -crisismerge N (set the crisismerge parameter to N)" puts stderr " -prefix PREFIX (comma separated prefix= argument)" puts stderr " -trans N (commit after N inserts - 0 == never)" + puts stderr " -hashsize N (set the fts5 hashsize parameter to N)" exit 1 } set O(vtab) fts5 set O(tok) "" @@ -57,10 +58,11 @@ set O(delete) 0 set O(automerge) -1 set O(crisismerge) -1 set O(prefix) "" set O(trans) 0 +set O(hashsize) -1 if {[llength $argv]<2} usage set nOpt [expr {[llength $argv]-2}] for {set i 0} {$i < $nOpt} {incr i} { set arg [lindex $argv $i] @@ -103,10 +105,15 @@ -prefix { if { [incr i]>=$nOpt } usage set O(prefix) [lindex $argv $i] } + + -hashsize { + if { [incr i]>=$nOpt } usage + set O(hashsize) [lindex $argv $i] + } default { usage } } @@ -124,10 +131,18 @@ if {$O(prefix)!=""} { set pref ", prefix='$O(prefix)'" } catch { db eval "CREATE VIRTUAL TABLE t1 USING $O(vtab) (path, content$O(tok)$pref)" db eval "INSERT INTO t1(t1, rank) VALUES('pgsz', 4050);" } + + if {$O(hashsize)>=0} { + catch { + db eval "INSERT INTO t1(t1, rank) VALUES('hashsize', $O(hashsize));" + } + } + + if {$O(automerge)>=0} { if {$O(vtab) == "fts5"} { db eval { INSERT INTO t1(t1, rank) VALUES('automerge', $O(automerge)) } } else { db eval { INSERT INTO t1(t1) VALUES('automerge=' || $O(automerge)) } @@ -139,8 +154,9 @@ } else { } } load_hierachy [lindex $argv end] db eval COMMIT +puts "" Index: ext/misc/ieee754.c ================================================================== --- ext/misc/ieee754.c +++ ext/misc/ieee754.c @@ -92,20 +92,20 @@ } while( (m>>32)&0xffe00000 ){ m >>= 1; e++; } - while( ((m>>32)&0xfff00000)==0 ){ + while( m!=0 && ((m>>32)&0xfff00000)==0 ){ m <<= 1; e--; } e += 1075; if( e<0 ) e = m = 0; - if( e>0x7ff ) m = 0; + if( e>0x7ff ) e = 0x7ff; a = m & ((((sqlite3_int64)1)<<52)-1); a |= e<<52; - if( isNeg ) a |= ((sqlite3_int64)1)<<63; + if( isNeg ) a |= ((sqlite3_uint64)1)<<63; memcpy(&r, &a, sizeof(r)); sqlite3_result_double(context, r); } } Index: main.mk ================================================================== --- main.mk +++ main.mk @@ -740,11 +740,11 @@ fastfuzztest: fuzzcheck$(EXE) $(FUZZDATA) ./fuzzcheck$(EXE) --limit-mem 100M $(FUZZDATA) valgrindfuzz: fuzzcheck$(EXE) $(FUZZDATA) - valgrind ./fuzzcheck$(EXE) --cell-size-check --limit-mem 10M $(FUZZDATA) + valgrind ./fuzzcheck$(EXE) --cell-size-check --limit-mem 10M --timeout 600 $(FUZZDATA) # A very quick test using only testfixture and omitting all the slower # tests. Designed to run in under 3 minutes on a workstation. # quicktest: ./testfixture$(EXE) Index: src/backup.c ================================================================== --- src/backup.c +++ src/backup.c @@ -291,11 +291,11 @@ */ for(iOff=iEnd-(i64)nSrcPgsz; rc==SQLITE_OK && iOffpDest->pBt) ) continue; - if( SQLITE_OK==(rc = sqlite3PagerGet(pDestPager, iDest, &pDestPg)) + if( SQLITE_OK==(rc = sqlite3PagerGet(pDestPager, iDest, &pDestPg, 0)) && SQLITE_OK==(rc = sqlite3PagerWrite(pDestPg)) ){ const u8 *zIn = &zSrcData[iOff%nSrcPgsz]; u8 *zDestData = sqlite3PagerGetData(pDestPg); u8 *zOut = &zDestData[iOff%nDestPgsz]; @@ -417,12 +417,11 @@ assert( nSrcPage>=0 ); for(ii=0; (nPage<0 || iiiNext<=(Pgno)nSrcPage && !rc; ii++){ const Pgno iSrcPg = p->iNext; /* Source page number */ if( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ){ DbPage *pSrcPg; /* Source page object */ - rc = sqlite3PagerAcquire(pSrcPager, iSrcPg, &pSrcPg, - PAGER_GET_READONLY); + rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg,PAGER_GET_READONLY); if( rc==SQLITE_OK ){ rc = backupOnePage(p, iSrcPg, sqlite3PagerGetData(pSrcPg), 0); sqlite3PagerUnref(pSrcPg); } } @@ -518,11 +517,11 @@ ** journal file. */ sqlite3PagerPagecount(pDestPager, &nDstPage); for(iPg=nDestTruncate; rc==SQLITE_OK && iPg<=(Pgno)nDstPage; iPg++){ if( iPg!=PENDING_BYTE_PAGE(p->pDest->pBt) ){ DbPage *pPg; - rc = sqlite3PagerGet(pDestPager, iPg, &pPg); + rc = sqlite3PagerGet(pDestPager, iPg, &pPg, 0); if( rc==SQLITE_OK ){ rc = sqlite3PagerWrite(pPg); sqlite3PagerUnref(pPg); } } @@ -538,11 +537,11 @@ rc==SQLITE_OK && iOffpPager, iPtrmap, &pDbPage); + rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage, 0); if( rc!=SQLITE_OK ){ *pRC = rc; return; } offset = PTRMAP_PTROFFSET(iPtrmap, key); @@ -978,11 +978,11 @@ int rc; assert( sqlite3_mutex_held(pBt->mutex) ); iPtrmap = PTRMAP_PAGENO(pBt, key); - rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage); + rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage, 0); if( rc!=0 ){ return rc; } pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage); @@ -1905,15 +1905,18 @@ ** Convert a DbPage obtained from the pager into a MemPage used by ** the btree layer. */ static MemPage *btreePageFromDbPage(DbPage *pDbPage, Pgno pgno, BtShared *pBt){ MemPage *pPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); - pPage->aData = sqlite3PagerGetData(pDbPage); - pPage->pDbPage = pDbPage; - pPage->pBt = pBt; - pPage->pgno = pgno; - pPage->hdrOffset = pgno==1 ? 100 : 0; + if( pgno!=pPage->pgno ){ + pPage->aData = sqlite3PagerGetData(pDbPage); + pPage->pDbPage = pDbPage; + pPage->pBt = pBt; + pPage->pgno = pgno; + pPage->hdrOffset = pgno==1 ? 100 : 0; + } + assert( pPage->aData==sqlite3PagerGetData(pDbPage) ); return pPage; } /* ** Get a page from the pager. Initialize the MemPage.pBt and @@ -1935,11 +1938,11 @@ int rc; DbPage *pDbPage; assert( flags==0 || flags==PAGER_GET_NOCONTENT || flags==PAGER_GET_READONLY ); assert( sqlite3_mutex_held(pBt->mutex) ); - rc = sqlite3PagerAcquire(pBt->pPager, pgno, (DbPage**)&pDbPage, flags); + rc = sqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, flags); if( rc ) return rc; *ppPage = btreePageFromDbPage(pDbPage, pgno, pBt); return SQLITE_OK; } @@ -2000,28 +2003,29 @@ if( pgno>btreePagecount(pBt) ){ rc = SQLITE_CORRUPT_BKPT; goto getAndInitPage_error; } - rc = sqlite3PagerAcquire(pBt->pPager, pgno, (DbPage**)&pDbPage, bReadOnly); + rc = sqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, bReadOnly); if( rc ){ goto getAndInitPage_error; } - *ppPage = btreePageFromDbPage(pDbPage, pgno, pBt); + *ppPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); if( (*ppPage)->isInit==0 ){ + btreePageFromDbPage(pDbPage, pgno, pBt); rc = btreeInitPage(*ppPage); if( rc!=SQLITE_OK ){ releasePage(*ppPage); goto getAndInitPage_error; } } + assert( (*ppPage)->pgno==pgno ); + assert( (*ppPage)->aData==sqlite3PagerGetData(pDbPage) ); /* If obtaining a child page for a cursor, we must verify that the page is ** compatible with the root page. */ - if( pCur - && ((*ppPage)->nCell<1 || (*ppPage)->intKey!=pCur->curIntKey) - ){ + if( pCur && ((*ppPage)->nCell<1 || (*ppPage)->intKey!=pCur->curIntKey) ){ rc = SQLITE_CORRUPT_BKPT; releasePage(*ppPage); goto getAndInitPage_error; } return SQLITE_OK; @@ -4618,11 +4622,11 @@ }else #endif { DbPage *pDbPage; - rc = sqlite3PagerAcquire(pBt->pPager, nextPage, &pDbPage, + rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage, ((eOp&0x01)==0 ? PAGER_GET_READONLY : 0) ); if( rc==SQLITE_OK ){ aPayload = sqlite3PagerGetData(pDbPage); nextPage = get4byte(aPayload); @@ -5062,10 +5066,12 @@ ** exactly matches intKey/pIdxKey. ** ** *pRes>0 The cursor is left pointing at an entry that ** is larger than intKey/pIdxKey. ** +** For index tables, the pIdxKey->eqSeen field is set to 1 if there +** exists an entry in the table that exactly matches pIdxKey. */ int sqlite3BtreeMovetoUnpacked( BtCursor *pCur, /* The cursor to be moved */ UnpackedRecord *pIdxKey, /* Unpacked index key */ i64 intKey, /* The table key */ @@ -6985,13 +6991,10 @@ ** enough for all overflow cells. ** ** If aOvflSpace is set to a null pointer, this function returns ** SQLITE_NOMEM. */ -#if defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_M_ARM) -#pragma optimize("", off) -#endif static int balance_nonroot( MemPage *pParent, /* Parent page of siblings being balanced */ int iParentIdx, /* Index of "the page" in pParent */ u8 *aOvflSpace, /* page-size bytes of space for parent ovfl */ int isRoot, /* True if pParent is a root-page */ @@ -7733,13 +7736,10 @@ releasePage(apNew[i]); } return rc; } -#if defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_M_ARM) -#pragma optimize("", on) -#endif /* ** This function is called when the root page of a b-tree structure is ** overfull (has one or more overflow pages). @@ -8934,11 +8934,11 @@ "%d of %d pages missing from overflow list starting at %d", N+1, expected, iFirst); break; } if( checkRef(pCheck, iPage) ) break; - if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage) ){ + if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage, 0) ){ checkAppendMsg(pCheck, "failed to get page %d", iPage); break; } pOvflData = (unsigned char *)sqlite3PagerGetData(pOvflPage); if( isFreeList ){ @@ -9671,19 +9671,17 @@ pBt->btsFlags &= ~BTS_NO_WAL; return rc; } -#ifdef SQLITE_DEBUG /* ** Return true if the cursor has a hint specified. This routine is ** only used from within assert() statements */ int sqlite3BtreeCursorHasHint(BtCursor *pCsr, unsigned int mask){ return (pCsr->hints & mask)!=0; } -#endif /* ** Return true if the given Btree is read-only. */ int sqlite3BtreeIsReadonly(Btree *p){ Index: src/btree.h ================================================================== --- src/btree.h +++ src/btree.h @@ -255,13 +255,11 @@ int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*); void sqlite3BtreeIncrblobCursor(BtCursor *); void sqlite3BtreeClearCursor(BtCursor *); int sqlite3BtreeSetVersion(Btree *pBt, int iVersion); -#ifdef SQLITE_DEBUG int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask); -#endif int sqlite3BtreeIsReadonly(Btree *pBt); int sqlite3HeaderSizeBtree(void); #ifndef NDEBUG int sqlite3BtreeCursorIsValid(BtCursor*); Index: src/btreeInt.h ================================================================== --- src/btreeInt.h +++ src/btreeInt.h @@ -515,11 +515,11 @@ Pgno pgnoRoot; /* The root page of this tree */ int nOvflAlloc; /* Allocated size of aOverflow[] array */ int skipNext; /* Prev() is noop if negative. Next() is noop if positive. ** Error code if eState==CURSOR_FAULT */ u8 curFlags; /* zero or more BTCF_* flags defined below */ - u8 curPagerFlags; /* Flags to send to sqlite3PagerAcquire() */ + u8 curPagerFlags; /* Flags to send to sqlite3PagerGet() */ u8 eState; /* One of the CURSOR_XXX constants (see below) */ u8 hints; /* As configured by CursorSetHints() */ /* All fields above are zeroed when the cursor is allocated. See ** sqlite3BtreeCursorZero(). Fields that follow must be manually ** initialized. */ Index: src/dbstat.c ================================================================== --- src/dbstat.c +++ src/dbstat.c @@ -384,11 +384,11 @@ pCell->aOvfl[0] = sqlite3Get4byte(&aData[iOff+nLocal]); for(j=1; jaOvfl[j-1]; DbPage *pPg = 0; - rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPrev, &pPg); + rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPrev, &pPg, 0); if( rc!=SQLITE_OK ){ assert( pPg==0 ); return rc; } pCell->aOvfl[j] = sqlite3Get4byte(sqlite3PagerGetData(pPg)); @@ -452,11 +452,11 @@ sqlite3PagerPagecount(pPager, &nPage); if( nPage==0 ){ pCsr->isEof = 1; return sqlite3_reset(pCsr->pStmt); } - rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg); + rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg, 0); pCsr->aPage[0].iPgno = iRoot; pCsr->aPage[0].iCell = 0; pCsr->aPage[0].zPath = z = sqlite3_mprintf("/"); pCsr->iPage = 0; if( z==0 ) rc = SQLITE_NOMEM; @@ -512,11 +512,11 @@ if( p->iCell==p->nCell ){ p[1].iPgno = p->iRightChildPg; }else{ p[1].iPgno = p->aCell[p->iCell].iChildPg; } - rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg); + rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg, 0); p[1].iCell = 0; p[1].zPath = z = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell); p->iCell++; if( z==0 ) rc = SQLITE_NOMEM; } Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -2331,11 +2331,11 @@ ** requiring a journal-sync before it is written. */ assert( isSavepnt ); assert( (pPager->doNotSpill & SPILLFLAG_ROLLBACK)==0 ); pPager->doNotSpill |= SPILLFLAG_ROLLBACK; - rc = sqlite3PagerAcquire(pPager, pgno, &pPg, 1); + rc = sqlite3PagerGet(pPager, pgno, &pPg, 1); assert( (pPager->doNotSpill & SPILLFLAG_ROLLBACK)!=0 ); pPager->doNotSpill &= ~SPILLFLAG_ROLLBACK; if( rc!=SQLITE_OK ) return rc; pPg->flags &= ~PGHDR_NEED_READ; sqlite3PcacheMakeDirty(pPg); @@ -4986,11 +4986,11 @@ return rc; } /* ** This function is called to obtain a shared lock on the database file. -** It is illegal to call sqlite3PagerAcquire() until after this function +** It is illegal to call sqlite3PagerGet() until after this function ** has been successfully called. If a shared-lock is already held when ** this function is called, it is a no-op. ** ** The following operations are also performed by this function. ** @@ -5293,11 +5293,11 @@ ** just returns 0. This routine acquires a read-lock the first time it ** has to go to disk, and could also playback an old journal if necessary. ** Since Lookup() never goes to disk, it never has to deal with locks ** or journal files. */ -int sqlite3PagerAcquire( +int sqlite3PagerGet( Pager *pPager, /* The pager open on the database file */ Pgno pgno, /* Page number to fetch */ DbPage **ppPage, /* Write a pointer to the page here */ int flags /* PAGER_GET_XXX flags */ ){ @@ -5882,11 +5882,11 @@ for(ii=0; iipgno || !sqlite3BitvecTest(pPager->pInJournal, pg) ){ if( pg!=PAGER_MJ_PGNO(pPager) ){ - rc = sqlite3PagerGet(pPager, pg, &pPage); + rc = sqlite3PagerGet(pPager, pg, &pPage, 0); if( rc==SQLITE_OK ){ rc = pager_write(pPage); if( pPage->flags&PGHDR_NEED_SYNC ){ needSync = 1; } @@ -6042,11 +6042,11 @@ PgHdr *pPgHdr; /* Reference to page 1 */ assert( !pPager->tempFile && isOpen(pPager->fd) ); /* Open page 1 of the file for writing. */ - rc = sqlite3PagerGet(pPager, 1, &pPgHdr); + rc = sqlite3PagerGet(pPager, 1, &pPgHdr, 0); assert( pPgHdr==0 || rc==SQLITE_OK ); /* If page one was fetched successfully, and this function is not ** operating in direct-mode, make page 1 writable. When not in ** direct mode, page 1 is always held in cache and hence the PagerGet() @@ -6197,11 +6197,11 @@ PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache); PgHdr *pPageOne = 0; if( pList==0 ){ /* Must have at least one page for the WAL commit flag. ** Ticket [2d1a5c67dfc2363e44f29d9bbd57f] 2011-05-18 */ - rc = sqlite3PagerGet(pPager, 1, &pPageOne); + rc = sqlite3PagerGet(pPager, 1, &pPageOne, 0); pList = pPageOne; pList->pDirty = 0; } assert( rc==SQLITE_OK ); if( ALWAYS(pList) ){ @@ -6911,11 +6911,11 @@ ** this transaction, it may be written to the database file before ** it is synced into the journal file. This way, it may end up in ** the journal file twice, but that is not a problem. */ PgHdr *pPgHdr; - rc = sqlite3PagerGet(pPager, needSyncPgno, &pPgHdr); + rc = sqlite3PagerGet(pPager, needSyncPgno, &pPgHdr, 0); if( rc!=SQLITE_OK ){ if( needSyncPgno<=pPager->dbOrigSize ){ assert( pPager->pTmpSpace!=0 ); sqlite3BitvecClear(pPager->pInJournal, needSyncPgno, pPager->pTmpSpace); } Index: src/pager.h ================================================================== --- src/pager.h +++ src/pager.h @@ -77,11 +77,11 @@ #define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */ #define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */ #define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */ /* -** Flags that make up the mask passed to sqlite3PagerAcquire(). +** Flags that make up the mask passed to sqlite3PagerGet(). */ #define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */ #define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */ /* @@ -133,12 +133,11 @@ i64 sqlite3PagerJournalSizeLimit(Pager *, i64); sqlite3_backup **sqlite3PagerBackupPtr(Pager*); int sqlite3PagerFlush(Pager*); /* Functions used to obtain and release page references. */ -int sqlite3PagerAcquire(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag); -#define sqlite3PagerGet(A,B,C) sqlite3PagerAcquire(A,B,C,0) +int sqlite3PagerGet(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag); DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno); void sqlite3PagerRef(DbPage*); void sqlite3PagerUnref(DbPage*); void sqlite3PagerUnrefNotNull(DbPage*); Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -1820,34 +1820,53 @@ u8 *aSortOrder; /* Sort order for each column. */ CollSeq *aColl[1]; /* Collating sequence for each term of the key */ }; /* -** An instance of the following structure holds information about a -** single index record that has already been parsed out into individual -** values. +** This object holds a record which has been parsed out into individual +** fields, for the purposes of doing a comparison. ** ** A record is an object that contains one or more fields of data. ** Records are used to store the content of a table row and to store ** the key of an index. A blob encoding of a record is created by ** the OP_MakeRecord opcode of the VDBE and is disassembled by the ** OP_Column opcode. ** -** This structure holds a record that has already been disassembled -** into its constituent fields. +** An instance of this object serves as a "key" for doing a search on +** an index b+tree. The goal of the search is to find the entry that +** is closed to the key described by this object. This object might hold +** just a prefix of the key. The number of fields is given by +** pKeyInfo->nField. +** +** The r1 and r2 fields are the values to return if this key is less than +** or greater than a key in the btree, respectively. These are normally +** -1 and +1 respectively, but might be inverted to +1 and -1 if the b-tree +** is in DESC order. +** +** The key comparison functions actually return default_rc when they find +** an equals comparison. default_rc can be -1, 0, or +1. If there are +** multiple entries in the b-tree with the same key (when only looking +** at the first pKeyInfo->nFields,) then default_rc can be set to -1 to +** cause the search to find the last match, or +1 to cause the search to +** find the first match. ** -** The r1 and r2 member variables are only used by the optimized comparison -** functions vdbeRecordCompareInt() and vdbeRecordCompareString(). +** The key comparison functions will set eqSeen to true if they ever +** get and equal results when comparing this structure to a b-tree record. +** When default_rc!=0, the search might end up on the record immediately +** before the first match or immediately after the last match. The +** eqSeen field will indicate whether or not an exact match exists in the +** b-tree. */ struct UnpackedRecord { KeyInfo *pKeyInfo; /* Collation and sort-order information */ + Mem *aMem; /* Values */ u16 nField; /* Number of entries in apMem[] */ i8 default_rc; /* Comparison result if keys are equal */ u8 errCode; /* Error detected by xRecordCompare (CORRUPT or NOMEM) */ - Mem *aMem; /* Values */ - int r1; /* Value to return if (lhs > rhs) */ - int r2; /* Value to return if (rhs < lhs) */ + i8 r1; /* Value to return if (lhs > rhs) */ + i8 r2; /* Value to return if (rhs < lhs) */ + u8 eqSeen; /* True if an equality comparison has been seen */ }; /* ** Each SQL index is represented in memory by an Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -7236,10 +7236,11 @@ { "sqlite3_stmt_scanstatus_reset", test_stmt_scanstatus_reset, 0 }, #endif }; static int bitmask_size = sizeof(Bitmask)*8; + static int longdouble_size = sizeof(LONGDOUBLE_TYPE); int i; extern int sqlite3_sync_count, sqlite3_fullsync_count; extern int sqlite3_opentemp_count; extern int sqlite3_like_count; extern int sqlite3_xferopt_count; @@ -7336,10 +7337,12 @@ (char*)&sqlite3_temp_directory, TCL_LINK_STRING); Tcl_LinkVar(interp, "sqlite_data_directory", (char*)&sqlite3_data_directory, TCL_LINK_STRING); Tcl_LinkVar(interp, "bitmask_size", (char*)&bitmask_size, TCL_LINK_INT|TCL_LINK_READ_ONLY); + Tcl_LinkVar(interp, "longdouble_size", + (char*)&longdouble_size, TCL_LINK_INT|TCL_LINK_READ_ONLY); Tcl_LinkVar(interp, "sqlite_sync_count", (char*)&sqlite3_sync_count, TCL_LINK_INT); Tcl_LinkVar(interp, "sqlite_fullsync_count", (char*)&sqlite3_fullsync_count, TCL_LINK_INT); #if defined(SQLITE_ENABLE_FTS3) && defined(SQLITE_TEST) Index: src/test2.c ================================================================== --- src/test2.c +++ src/test2.c @@ -320,11 +320,11 @@ } pPager = sqlite3TestTextToPtr(argv[1]); if( Tcl_GetInt(interp, argv[2], &pgno) ) return TCL_ERROR; rc = sqlite3PagerSharedLock(pPager); if( rc==SQLITE_OK ){ - rc = sqlite3PagerGet(pPager, pgno, &pPage); + rc = sqlite3PagerGet(pPager, pgno, &pPage, 0); } if( rc!=SQLITE_OK ){ Tcl_AppendResult(interp, sqlite3ErrName(rc), 0); return TCL_ERROR; } Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -3595,10 +3595,17 @@ ** that are used as an unpacked index key. ** ** Reposition cursor P1 so that it points to the smallest entry that ** is greater than or equal to the key value. If there are no records ** greater than or equal to the key and P2 is not zero, then jump to P2. +** +** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this +** opcode will always land on a record that equally equals the key, or +** else jump immediately to P2. When the cursor is OPFLAG_SEEKEQ, this +** opcode must be followed by an IdxLE opcode with the same arguments. +** The IdxLE opcode will be skipped if this opcode succeeds, but the +** IdxLE opcode will be used on subsequent loop iterations. ** ** This opcode leaves the cursor configured to move in forward order, ** from the beginning toward the end. In other words, the cursor is ** configured to use Next, not Prev. ** @@ -3654,22 +3661,30 @@ ** ** This opcode leaves the cursor configured to move in reverse order, ** from the end toward the beginning. In other words, the cursor is ** configured to use Prev, not Next. ** +** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this +** opcode will always land on a record that equally equals the key, or +** else jump immediately to P2. When the cursor is OPFLAG_SEEKEQ, this +** opcode must be followed by an IdxGE opcode with the same arguments. +** The IdxGE opcode will be skipped if this opcode succeeds, but the +** IdxGE opcode will be used on subsequent loop iterations. +** ** See also: Found, NotFound, SeekGt, SeekGe, SeekLt */ case OP_SeekLT: /* jump, in3 */ case OP_SeekLE: /* jump, in3 */ case OP_SeekGE: /* jump, in3 */ case OP_SeekGT: { /* jump, in3 */ - int res; - int oc; - VdbeCursor *pC; - UnpackedRecord r; - int nField; - i64 iKey; /* The rowid we are to seek to */ + int res; /* Comparison result */ + int oc; /* Opcode */ + VdbeCursor *pC; /* The cursor to seek */ + UnpackedRecord r; /* The key to seek for */ + int nField; /* Number of columns or fields in the key */ + i64 iKey; /* The rowid we are to seek to */ + int eqOnly; /* Only interested in == results */ assert( pOp->p1>=0 && pOp->p1nCursor ); assert( pOp->p2!=0 ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); @@ -3678,31 +3693,20 @@ assert( OP_SeekGE == OP_SeekLT+2 ); assert( OP_SeekGT == OP_SeekLT+3 ); assert( pC->isOrdered ); assert( pC->pCursor!=0 ); oc = pOp->opcode; + eqOnly = 0; pC->nullRow = 0; #ifdef SQLITE_DEBUG pC->seekOp = pOp->opcode; #endif - /* For a cursor with the BTREE_SEEK_EQ hint, only the OP_SeekGE and - ** OP_SeekLE opcodes are allowed, and these must be immediately followed - ** by an OP_IdxGT or OP_IdxLT opcode, respectively, with the same key. - */ -#ifdef SQLITE_DEBUG - if( sqlite3BtreeCursorHasHint(pC->pCursor, BTREE_SEEK_EQ) ){ - assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE ); - assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT ); - assert( pOp[1].p1==pOp[0].p1 ); - assert( pOp[1].p2==pOp[0].p2 ); - assert( pOp[1].p3==pOp[0].p3 ); - assert( pOp[1].p4.i==pOp[0].p4.i ); - } -#endif - if( pC->isTable ){ + /* The BTREE_SEEK_EQ flag is only set on index cursors */ + assert( sqlite3BtreeCursorHasHint(pC->pCursor, BTREE_SEEK_EQ)==0 ); + /* The input value in P3 might be of any type: integer, real, string, ** blob, or NULL. But it needs to be an integer before we can do ** the seek, so convert it. */ pIn3 = &aMem[pOp->p3]; if( (pIn3->flags & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ @@ -3747,10 +3751,24 @@ pC->movetoTarget = iKey; /* Used by OP_Delete */ if( rc!=SQLITE_OK ){ goto abort_due_to_error; } }else{ + /* For a cursor with the BTREE_SEEK_EQ hint, only the OP_SeekGE and + ** OP_SeekLE opcodes are allowed, and these must be immediately followed + ** by an OP_IdxGT or OP_IdxLT opcode, respectively, with the same key. + */ + if( sqlite3BtreeCursorHasHint(pC->pCursor, BTREE_SEEK_EQ) ){ + eqOnly = 1; + assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE ); + assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT ); + assert( pOp[1].p1==pOp[0].p1 ); + assert( pOp[1].p2==pOp[0].p2 ); + assert( pOp[1].p3==pOp[0].p3 ); + assert( pOp[1].p4.i==pOp[0].p4.i ); + } + nField = pOp->p4.i; assert( pOp->p4type==P4_INT32 ); assert( nField>0 ); r.pKeyInfo = pC->pKeyInfo; r.nField = (u16)nField; @@ -3771,13 +3789,18 @@ r.aMem = &aMem[pOp->p3]; #ifdef SQLITE_DEBUG { int i; for(i=0; ipCursor, &r, 0, 0, &res); if( rc!=SQLITE_OK ){ goto abort_due_to_error; + } + if( eqOnly && r.eqSeen==0 ){ + assert( res!=0 ); + goto seek_not_found; } } pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; #ifdef SQLITE_TEST @@ -3802,14 +3825,18 @@ ** see if this is the case. */ res = sqlite3BtreeEof(pC->pCursor); } } +seek_not_found: assert( pOp->p2>0 ); VdbeBranchTaken(res!=0,2); if( res ){ goto jump_to_p2; + }else if( eqOnly ){ + assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT ); + pOp++; /* Skip the OP_IdxLt or OP_IdxGT that follows */ } break; } /* Opcode: Seek P1 P2 * * * Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -3319,10 +3319,14 @@ } case 4: { /* 4-byte signed integer */ /* EVIDENCE-OF: R-01849-26079 Value is a big-endian 32-bit ** twos-complement integer. */ pMem->u.i = FOUR_BYTE_INT(buf); +#ifdef __HP_cc + /* Work around a sign-extension bug in the HP compiler for HP/UX */ + if( buf[0]&0x80 ) pMem->u.i |= 0xffffffff80000000LL; +#endif pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); return 4; } case 5: { /* 6-byte signed integer */ @@ -3634,10 +3638,38 @@ int c = memcmp(pB1->z, pB2->z, pB1->n>pB2->n ? pB2->n : pB1->n); if( c ) return c; return pB1->n - pB2->n; } +/* +** Do a comparison between a 64-bit signed integer and a 64-bit floating-point +** number. Return negative, zero, or positive if the first (i64) is less than, +** equal to, or greater than the second (double). +*/ +static int sqlite3IntFloatCompare(i64 i, double r){ + if( sizeof(LONGDOUBLE_TYPE)>8 ){ + LONGDOUBLE_TYPE x = (LONGDOUBLE_TYPE)i; + if( xr ) return +1; + return 0; + }else{ + i64 y; + double s; + if( r<-9223372036854775808.0 ) return +1; + if( r>9223372036854775807.0 ) return -1; + y = (i64)r; + if( iy ){ + if( y==SMALLEST_INT64 && r>0.0 ) return -1; + return +1; + } + s = (double)i; + if( sr ) return +1; + return 0; + } +} /* ** Compare the values contained by the two memory cells, returning ** negative, zero or positive if pMem1 is less than, equal to, or greater ** than pMem2. Sorting order is NULL's first, followed by numbers (integers @@ -3660,38 +3692,38 @@ */ if( combined_flags&MEM_Null ){ return (f2&MEM_Null) - (f1&MEM_Null); } - /* If one value is a number and the other is not, the number is less. - ** If both are numbers, compare as reals if one is a real, or as integers - ** if both values are integers. - */ - if( combined_flags&(MEM_Int|MEM_Real) ){ - double r1, r2; - if( (f1 & f2 & MEM_Int)!=0 ){ - if( pMem1->u.i < pMem2->u.i ) return -1; - if( pMem1->u.i > pMem2->u.i ) return 1; - return 0; - } - if( (f1&MEM_Real)!=0 ){ - r1 = pMem1->u.r; - }else if( (f1&MEM_Int)!=0 ){ - r1 = (double)pMem1->u.i; - }else{ - return 1; - } - if( (f2&MEM_Real)!=0 ){ - r2 = pMem2->u.r; - }else if( (f2&MEM_Int)!=0 ){ - r2 = (double)pMem2->u.i; - }else{ - return -1; - } - if( r1r2 ) return 1; - return 0; + /* At least one of the two values is a number + */ + if( combined_flags&(MEM_Int|MEM_Real) ){ + if( (f1 & f2 & MEM_Int)!=0 ){ + if( pMem1->u.i < pMem2->u.i ) return -1; + if( pMem1->u.i > pMem2->u.i ) return +1; + return 0; + } + if( (f1 & f2 & MEM_Real)!=0 ){ + if( pMem1->u.r < pMem2->u.r ) return -1; + if( pMem1->u.r > pMem2->u.r ) return +1; + return 0; + } + if( (f1&MEM_Int)!=0 ){ + if( (f2&MEM_Real)!=0 ){ + return sqlite3IntFloatCompare(pMem1->u.i, pMem2->u.r); + }else{ + return -1; + } + } + if( (f1&MEM_Real)!=0 ){ + if( (f2&MEM_Int)!=0 ){ + return -sqlite3IntFloatCompare(pMem2->u.i, pMem1->u.r); + }else{ + return -1; + } + } + return +1; } /* If one value is a string and the other is a blob, the string is less. ** If both are strings, compare using the collating functions. */ @@ -3838,17 +3870,12 @@ if( serial_type>=10 ){ rc = +1; }else if( serial_type==0 ){ rc = -1; }else if( serial_type==7 ){ - double rhs = (double)pRhs->u.i; sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); - if( mem1.u.rrhs ){ - rc = +1; - } + rc = -sqlite3IntFloatCompare(pRhs->u.i, mem1.u.r); }else{ i64 lhs = vdbeRecordDecodeInt(serial_type, &aKey1[d1]); i64 rhs = pRhs->u.i; if( lhsu.r; - double lhs; sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); if( serial_type==7 ){ - lhs = mem1.u.r; + if( mem1.u.ru.r ){ + rc = -1; + }else if( mem1.u.r>pRhs->u.r ){ + rc = +1; + } }else{ - lhs = (double)mem1.u.i; - } - if( lhsrhs ){ - rc = +1; + rc = sqlite3IntFloatCompare(mem1.u.i, pRhs->u.r); } } } /* RHS is a string */ @@ -3969,10 +3993,11 @@ ** value. */ assert( CORRUPT_DB || vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, pPKey2->default_rc) || pKeyInfo->db->mallocFailed ); + pPKey2->eqSeen = 1; return pPKey2->default_rc; } int sqlite3VdbeRecordCompare( int nKey1, const void *pKey1, /* Left key */ UnpackedRecord *pPKey2 /* Right key */ @@ -4068,10 +4093,11 @@ res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); }else{ /* The first fields of the two keys are equal and there are no trailing ** fields. Return pPKey2->default_rc in this case. */ res = pPKey2->default_rc; + pPKey2->eqSeen = 1; } assert( vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, res) ); return res; } @@ -4088,10 +4114,11 @@ ){ const u8 *aKey1 = (const u8*)pKey1; int serial_type; int res; + assert( pPKey2->aMem[0].flags & MEM_Str ); vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); getVarint32(&aKey1[1], serial_type); if( serial_type<12 ){ res = pPKey2->r1; /* (pKey1/nKey1) is a number or a null */ }else if( !(serial_type & 0x01) ){ @@ -4114,10 +4141,11 @@ if( res==0 ){ if( pPKey2->nField>1 ){ res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); }else{ res = pPKey2->default_rc; + pPKey2->eqSeen = 1; } }else if( res>0 ){ res = pPKey2->r2; }else{ res = pPKey2->r1; Index: test/atof1.test ================================================================== --- test/atof1.test +++ test/atof1.test @@ -13,11 +13,11 @@ # set testdir [file dirname $argv0] source $testdir/tester.tcl -if {![info exists __GNUC__] || [regexp arm $tcl_platform(machine)]} { +if {$::longdouble_size<=8} { finish_test return } expr srand(1) Index: test/bc_common.tcl ================================================================== --- test/bc_common.tcl +++ test/bc_common.tcl @@ -5,15 +5,17 @@ # Search for binaries to test against. Any executable files that match # our naming convention are assumed to be testfixture binaries to test # against. # set binaries [list] - set pattern "[file tail [info nameofexec]]?*" + set self [file tail [info nameofexec]] + set pattern "$self?*" if {$::tcl_platform(platform)=="windows"} { set pattern [string map {\.exe {}} $pattern] } foreach file [glob -nocomplain $pattern] { + if {$file==$self} continue if {[file executable $file] && [file isfile $file]} {lappend binaries $file} } if {[llength $binaries]==0} { puts "WARNING: No historical binaries to test against." Index: test/collate4.test ================================================================== --- test/collate4.test +++ test/collate4.test @@ -350,11 +350,11 @@ CREATE INDEX collate4i1 ON collate4t1(a); } count { SELECT * FROM collate4t2, collate4t1 WHERE a = b; } -} {A a A A 5} +} {A a A A 4} do_test collate4-2.1.3 { count { SELECT * FROM collate4t2, collate4t1 WHERE b = a; } } {A A 19} @@ -370,11 +370,11 @@ } {A a A A 19} do_test collate4-2.1.5 { count { SELECT * FROM collate4t2, collate4t1 WHERE b = a; } -} {A A 4} +} {A A 3} ifcapable subquery { do_test collate4-2.1.6 { count { SELECT a FROM collate4t1 WHERE a IN (SELECT * FROM collate4t2) ORDER BY rowid @@ -387,16 +387,16 @@ } count { SELECT a FROM collate4t1 WHERE a IN (SELECT * FROM collate4t2) ORDER BY rowid } - } {a A 6} + } {a A 5} do_test collate4-2.1.8 { count { SELECT a FROM collate4t1 WHERE a IN ('z', 'a'); } - } {a A 5} + } {a A 4} do_test collate4-2.1.9 { execsql { DROP INDEX collate4i1; CREATE INDEX collate4i1 ON collate4t1(a COLLATE TEXT); } Index: test/e_uri.test ================================================================== --- test/e_uri.test +++ test/e_uri.test @@ -24,11 +24,13 @@ set ::uri_open [list] set DB [sqlite3_open_v2 $uri { SQLITE_OPEN_READWRITE SQLITE_OPEN_CREATE SQLITE_OPEN_WAL } tvfs] + set fileName [sqlite3_db_filename $DB main] sqlite3_close $DB + forcedelete $fileName tvfs delete tvfs2 delete set ::uri_open } ADDED test/ieee754.test Index: test/ieee754.test ================================================================== --- /dev/null +++ test/ieee754.test @@ -0,0 +1,56 @@ +# 2015-11-06 +# +# 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. +# +#*********************************************************************** +# +# Tests of the iee754 extension +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +load_static_extension db ieee754 + +foreach {id float rep} { + 1 1.0 1,0 + 2 2.0 2,0 + 3 0.5 1,-1 + 4 1.5 3,-1 + 5 0.0 0,-1075 + 6 4.9406564584124654e-324 4503599627370497,-1075 + 7 2.2250738585072009e-308 9007199254740991,-1075 + 8 2.2250738585072014e-308 1,-1022 +} { + do_test ieee754-100-$id-1 { + db eval "SELECT ieee754($float);" + } "ieee754($rep)" + do_test ieee754-100-$id-2 { + db eval "SELECT ieee754($rep)==$float;" + } {1} + if {$float!=0.0} { + do_test ieee754-100-$id-3 { + db eval "SELECT ieee754(-$float);" + } "ieee754(-$rep)" + do_test ieee754-100-$id-4 { + db eval "SELECT ieee754(-$rep)==-$float;" + } {1} + } +} + +do_execsql_test ieee754-110 { + SELECT ieee754(1,1024), ieee754(4503599627370495,972); +} {Inf 1.79769313486232e+308} +do_execsql_test ieee754-111 { + SELECT ieee754(-1,1024), ieee754(-4503599627370495,972); +} {-Inf -1.79769313486232e+308} +do_execsql_test ieee754-112 { + SELECT ieee754(4503599627370495,973) is null; +} {1} + +finish_test ADDED test/numindex1.test Index: test/numindex1.test ================================================================== --- /dev/null +++ test/numindex1.test @@ -0,0 +1,79 @@ +# 2015-11-05 +# +# 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 tests for indexes on large numeric values. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + + +# Test cases from Zsbán Ambrus: +# +do_execsql_test numindex1-1.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + CREATE INDEX t1b ON t1(b); + INSERT INTO t1(a,b) VALUES(100, 356282677878746339); + INSERT INTO t1(a,b) VALUES(50, 356282677878746339.0); + INSERT INTO t1(a,b) VALUES(0, 356282677878746340); + DELETE FROM t1 WHERE a=50; + PRAGMA integrity_check; +} {ok} + +do_execsql_test numindex1-1.2 { + CREATE TABLE t2(a,b); + INSERT INTO t2(a,b) VALUES('b', 1<<58), + ('c', (1<<58)+1e-7), ('d', (1<<58)+1); + SELECT a, b, typeof(b), '|' FROM t2 ORDER BY +a; +} {b 288230376151711744 integer | c 2.88230376151712e+17 real | d 288230376151711745 integer |} + +do_execsql_test numindex1-1.3 { + SELECT x.a || CASE WHEN x.b==y.b THEN '==' ELSE '<>' END || y.a + FROM t2 AS x, t2 AS y + ORDER BY +x.a, +x.b; +} {b==b b==c b<>d c==b c==c c<>d d<>b d<>c d==d} + +# New test cases +# +do_execsql_test numindex1-2.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY,b); + CREATE INDEX t1b ON t1(b); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100) + INSERT INTO t1(a,b) SELECT x, 10000000000000004.0 FROM c + WHERE x NOT IN (23,37); + INSERT INTO t1(a,b) VALUES(23,10000000000000005); + INSERT INTO t1(a,b) VALUES(37,10000000000000003); + DELETE FROM t1 WHERE a NOT IN (23,37); + PRAGMA integrity_check; +} {ok} + +do_execsql_test numindex1-3.1 { + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY,b); + CREATE INDEX t1b ON t1(b); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<20) + INSERT INTO t1(a,b) SELECT x, 100000000000000005.0 + FROM c WHERE x NOT IN (3,5,7,11,13,17,19); + INSERT INTO t1(a,b) VALUES(3,100000000000000005); + INSERT INTO t1(a,b) VALUES(5,100000000000000000); + INSERT INTO t1(a,b) VALUES(7,100000000000000008); + INSERT INTO t1(a,b) VALUES(11,100000000000000006); + INSERT INTO t1(a,b) VALUES(13,100000000000000001); + INSERT INTO t1(a,b) VALUES(17,100000000000000004); + INSERT INTO t1(a,b) VALUES(19,100000000000000003); + PRAGMA integrity_check; +} {ok} + +do_execsql_test numindex1-3.2 { + SELECT a FROM t1 ORDER BY b; +} {1 2 4 5 6 8 9 10 12 14 15 16 18 20 13 19 17 3 11 7} + +finish_test Index: test/permutations.test ================================================================== --- test/permutations.test +++ test/permutations.test @@ -176,11 +176,11 @@ Run the "veryquick" test suite with a couple of multi-process tests (that fail under valgrind) omitted. } -files [ test_set $allquicktests -exclude *malloc* *ioerr* *fault* wal.test \ shell*.test crash8.test atof1.test selectG.test \ - tkt-fc62af4523.test + tkt-fc62af4523.test numindex1.test ] -initialize { set ::G(valgrind) 1 } -shutdown { unset -nocomplain ::G(valgrind) } @@ -705,11 +705,11 @@ Run tests with an in-memory journal file. } -presql { pragma journal_mode = 'memory' } -files [test_set $::allquicktests -exclude { # Exclude all tests that simulate IO errors. - autovacuum_ioerr2.test incrvacuum_ioerr.test ioerr.test + autovacuum_ioerr2.test cffault.test incrvacuum_ioerr.test ioerr.test ioerr.test ioerr2.test ioerr3.test ioerr4.test ioerr5.test vacuum3.test incrblob_err.test diskfull.test backup_ioerr.test e_fts3.test fts3cov.test fts3malloc.test fts3rnd.test fts3snippet.test mmapfault.test @@ -721,11 +721,11 @@ pager1.test async4.test corrupt.test filefmt.test pager2.test corrupt5.test corruptA.test pageropt.test # Exclude stmt.test, which expects sub-journals to use temporary files. - stmt.test + stmt.test symlink.test zerodamage.test # WAL mode is different. wal* tkt-2d1a5c67d.test backcompat.test e_wal* rowallock.test Index: test/releasetest.tcl ================================================================== --- test/releasetest.tcl +++ test/releasetest.tcl @@ -17,10 +17,12 @@ --msvc (Use MSVC as the compiler) --buildonly (Just build testfixture - do not run) --dryrun (Print what would have happened) --info (Show diagnostic info) --with-tcl=DIR (Use TCL build at DIR) + --jobs N (Use N processes - default 1) + --progress (Show progress messages) The default value for --srcdir is the parent of the directory holding this script. The script determines the default value for --platform using the @@ -29,10 +31,16 @@ "Darwin-x86_64", "Windows NT-intel", and "Windows NT-amd64". Every test begins with a fresh run of the configure script at the top of the SQLite source tree. } + +# Return a timestamp of the form HH:MM:SS +# +proc now {} { + return [clock format [clock seconds] -format %H:%M:%S] +} # Omit comments (text between # and \n) in a long multi-line string. # proc strip_comments {in} { regsub -all {#[^\n]*\n} $in {} out @@ -118,10 +126,11 @@ -DSQLITE_ENABLE_FTS4=1 -DSQLITE_ENABLE_RTREE=1 -DSQLITE_ENABLE_STAT4 -DSQLITE_ENABLE_RBU -DSQLITE_MAX_ATTACHED=125 + -DLONGDOUBLE_TYPE=double } "Device-One" { -O2 -DSQLITE_DEBUG=1 -DSQLITE_DEFAULT_AUTOVACUUM=1 @@ -221,10 +230,11 @@ }] array set ::Platforms [strip_comments { Linux-x86_64 { "Check-Symbols" checksymbols + "Fast-One" fuzztest "Debug-One" "mptest test" "Have-Not" test "Secure-Delete" test "Unlock-Notify" "QUICKTEST_INCLUDE=notify2.test test" "Update-Delete-Limit" test @@ -231,14 +241,13 @@ "Extra-Robustness" test "Device-Two" test "No-lookaside" test "Devkit" test "Sanitize" {QUICKTEST_OMIT=func4.test,nan.test test} - "Fast-One" fuzztest - "Valgrind" valgrindtest + "Device-One" fulltest "Default" "threadtest fulltest" - "Device-One" fulltest + "Valgrind" valgrindtest } Linux-i686 { "Devkit" test "Have-Not" test "Unlock-Notify" "QUICKTEST_INCLUDE=notify2.test test" @@ -255,16 +264,16 @@ "Locking-Style" "mptest test" "Have-Not" test "OS-X" "threadtest fulltest" } "Windows NT-intel" { - "Default" "mptest fulltestonly" "Have-Not" test + "Default" "mptest fulltestonly" } "Windows NT-amd64" { - "Default" "mptest fulltestonly" "Have-Not" test + "Default" "mptest fulltestonly" } # The Failure-Detection platform runs various tests that deliberately # fail. This is used as a test of this script to verify that this script # correctly identifies failures. @@ -283,10 +292,13 @@ # End of configuration section. ######################################################################### ######################################################################### +# Configuration verification: Check that each entry in the list of configs +# specified for each platforms exists. +# foreach {key value} [array get ::Platforms] { foreach {v t} $value { if {0==[info exists ::Configs($v)]} { puts stderr "No such configuration: \"$v\"" exit -1 @@ -370,13 +382,12 @@ } } } close $fd if {$::BUILDONLY} { - if {$rc==0} { - set errmsg "Build complete" - } else { + incr ::NTESTCASE + if {$rc!=0} { set errmsg "Build failed" } } elseif {!$seen} { set rc 1 set errmsg "Test did not complete" @@ -384,16 +395,173 @@ append errmsg " - core file exists" } } } -proc run_test_suite {name testtarget config} { +#-------------------------------------------------------------------------- +# This command is invoked as the [main] routine for scripts run with the +# "--slave" option. +# +# For each test (i.e. "configure && make test" execution), the master +# process spawns a process with the --slave option. It writes two lines +# to the slaves stdin. The first contains a single boolean value - the +# value of ::TRACE to use in the slave script. The second line contains a +# list in the same format as each element of the list passed to the +# [run_all_test_suites] command in the master process. +# +# The slave then runs the "configure && make test" commands specified. It +# exits successfully if the tests passes, or with a non-zero error code +# otherwise. +# +proc run_slave_test {} { + # Read global vars configuration from stdin. + set V [gets stdin] + foreach {::TRACE ::MSVC ::DRYRUN} $V {} + + # Read the test-suite configuration from stdin. + set T [gets stdin] + foreach {title dir configOpts testtarget makeOpts cflags opts} $T {} + + # Create and switch to the test directory. + trace_cmd file mkdir $dir + trace_cmd cd $dir + catch {file delete core} + catch {file delete test.log} + + # Run the "./configure && make" commands. + set rc 0 + set rc [catch [configureCommand $configOpts]] + if {!$rc} { + if {[info exists ::env(TCLSH_CMD)]} { + set savedEnv(TCLSH_CMD) $::env(TCLSH_CMD) + } else { + unset -nocomplain savedEnv(TCLSH_CMD) + } + set ::env(TCLSH_CMD) [file nativename [info nameofexecutable]] + set rc [catch [makeCommand $testtarget $makeOpts $cflags $opts]] + if {[info exists savedEnv(TCLSH_CMD)]} { + set ::env(TCLSH_CMD) $savedEnv(TCLSH_CMD) + } else { + unset -nocomplain ::env(TCLSH_CMD) + } + } + + # Exis successfully if the test passed, or with a non-zero error code + # otherwise. + exit $rc +} + +# This command is invoked in the master process each time a slave +# file-descriptor is readable. +# +proc slave_fileevent {fd T tm1} { + global G + foreach {title dir configOpts testtarget makeOpts cflags opts} $T {} + + if {[eof $fd]} { + fconfigure $fd -blocking 1 + set rc [catch { close $fd }] + + set errmsg {} + set logfile [file join $dir test.log] + if {[file exists $logfile]} { + count_tests_and_errors [file join $dir test.log] rc errmsg + } elseif {$rc==0 && !$::DRYRUN} { + set rc 1 + set errmsg "no test.log file..." + } + + if {!$::TRACE} { + set tm2 [clock seconds] + set hours [expr {($tm2-$tm1)/3600}] + set minutes [expr {(($tm2-$tm1)/60)%60}] + set seconds [expr {($tm2-$tm1)%60}] + set tm [format (%02d:%02d:%02d) $hours $minutes $seconds] + + if {$rc} { + set status FAIL + incr ::NERR + } else { + set status Ok + } + + set n [string length $title] + if {$::PROGRESS_MSGS} { + PUTS "finished: ${title}[string repeat . [expr {53-$n}]] $status $tm" + } else { + PUTS "${title}[string repeat . [expr {63-$n}]] $status $tm" + } + if {$errmsg!=""} {PUTS " $errmsg"} + flush stdout + } + + incr G(nJob) -1 + } else { + set line [gets $fd] + if {[string trim $line] != ""} { + puts "Trace : $title - \"$line\"" + } + } +} + +#-------------------------------------------------------------------------- +# The only argument passed to this function is a list of test-suites to +# run. Each "test-suite" is itself a list consisting of the following +# elements: +# +# * Test title (for display). +# * The name of the directory to run the test in. +# * The argument for [configureCommand] +# * The first argument for [makeCommand] +# * The second argument for [makeCommand] +# * The third argument for [makeCommand] +# +proc run_all_test_suites {alltests} { + global G + set tests $alltests + + set G(nJob) 0 + + while {[llength $tests]>0 || $G(nJob)>0} { + if {$G(nJob)>=$::JOBS || [llength $tests]==0} { + vwait G(nJob) + } + + if {[llength $tests]>0} { + set T [lindex $tests 0] + set tests [lrange $tests 1 end] + foreach {title dir configOpts testtarget makeOpts cflags opts} $T {} + if {$::PROGRESS_MSGS && !$::TRACE} { + set n [string length $title] + PUTS "starting: ${title} at [now]" + flush stdout + } + + # Run the job. + # + set tm1 [clock seconds] + incr G(nJob) + set script [file normalize [info script]] + set fd [open "|[info nameofexecutable] $script --slave" r+] + fconfigure $fd -blocking 0 + fileevent $fd readable [list slave_fileevent $fd $T $tm1] + puts $fd [list $::TRACE $::MSVC $::DRYRUN] + puts $fd [list {*}$T] + flush $fd + } + } +} + +proc add_test_suite {listvar name testtarget config} { + upvar $listvar alltests + # Tcl variable $opts is used to build up the value used to set the # OPTS Makefile variable. Variable $cflags holds the value for # CFLAGS. The makefile will pass OPTS to both gcc and lemon, but # CFLAGS is only passed to gcc. # + set makeOpts "" set cflags [expr {$::MSVC ? "-Zi" : "-g"}] set opts "" set title ${name}($testtarget) set configOpts $::WITHTCL @@ -402,71 +570,78 @@ if {[regexp {^-[UD]} $arg]} { lappend opts $arg } elseif {[regexp {^[A-Z]+=} $arg]} { lappend testtarget $arg } elseif {[regexp {^--(enable|disable)-} $arg]} { + if {$::MSVC} { + if {$arg eq "--disable-amalgamation"} { + lappend makeOpts USE_AMALGAMATION=0 + continue + } + if {$arg eq "--disable-shared"} { + lappend makeOpts USE_CRT_DLL=0 DYNAMIC_SHELL=0 + continue + } + if {$arg eq "--enable-fts5"} { + lappend opts -DSQLITE_ENABLE_FTS5 + continue + } + if {$arg eq "--enable-json1"} { + lappend opts -DSQLITE_ENABLE_JSON1 + continue + } + if {$arg eq "--enable-shared"} { + lappend makeOpts USE_CRT_DLL=1 DYNAMIC_SHELL=1 + continue + } + } lappend configOpts $arg } else { + if {$::MSVC} { + if {$arg eq "-g"} { + lappend cflags -Zi + continue + } + if {[regexp -- {^-O(\d+)$} $arg all level]} then { + lappend makeOpts OPTIMIZATIONS=$level + continue + } + } lappend cflags $arg } } - set cflags [join $cflags " "] - set opts [join $opts " "] - append opts " -DSQLITE_NO_SYNC=1" + # Disable sync to make testing faster. + # + lappend opts -DSQLITE_NO_SYNC=1 # Some configurations already set HAVE_USLEEP; in that case, skip it. # - if {![regexp { -DHAVE_USLEEP$} $opts] - && ![regexp { -DHAVE_USLEEP[ =]+} $opts]} { - append opts " -DHAVE_USLEEP=1" + if {[lsearch -regexp $opts {^-DHAVE_USLEEP(?:=|$)}]==-1} { + lappend opts -DHAVE_USLEEP=1 + } + + # Add the define for this platform. + # + if {$::tcl_platform(platform)=="windows"} { + lappend opts -DSQLITE_OS_WIN=1 + } else { + lappend opts -DSQLITE_OS_UNIX=1 } # Set the sub-directory to use. # set dir [string tolower [string map {- _ " " _} $name]] - if {$::tcl_platform(platform)=="windows"} { - append opts " -DSQLITE_OS_WIN=1" - } else { - append opts " -DSQLITE_OS_UNIX=1" - } - - if {!$::TRACE} { - set n [string length $title] - PUTS -nonewline "${title}[string repeat . [expr {63-$n}]]" - flush stdout - } - - set rc 0 - set tm1 [clock seconds] - set origdir [pwd] - trace_cmd file mkdir $dir - trace_cmd cd $dir - set errmsg {} - catch {file delete core} - set rc [catch [configureCommand $configOpts]] - if {!$rc} { - set rc [catch [makeCommand $testtarget $cflags $opts]] - count_tests_and_errors test.log rc errmsg - } - trace_cmd cd $origdir - set tm2 [clock seconds] - - if {!$::TRACE} { - set hours [expr {($tm2-$tm1)/3600}] - set minutes [expr {(($tm2-$tm1)/60)%60}] - set seconds [expr {($tm2-$tm1)%60}] - set tm [format (%02d:%02d:%02d) $hours $minutes $seconds] - if {$rc} { - PUTS " FAIL $tm" - incr ::NERR - } else { - PUTS " Ok $tm" - } - if {$errmsg!=""} {PUTS " $errmsg"} - } + # Join option lists into strings, using space as delimiter. + # + set makeOpts [join $makeOpts " "] + set cflags [join $cflags " "] + set opts [join $opts " "] + + lappend alltests [list \ + $title $dir $configOpts $testtarget $makeOpts $cflags $opts] } # The following procedure returns the "configure" command to be exectued for # the current platform, which may be Windows (via MinGW, etc). # @@ -482,19 +657,23 @@ } # The following procedure returns the "make" command to be executed for the # specified targets, compiler flags, and options. # -proc makeCommand { targets cflags opts } { +proc makeCommand { targets makeOpts cflags opts } { set result [list trace_cmd exec] if {$::MSVC} { set nmakeDir [file nativename $::SRCDIR] - set nmakeFile [file join $nmakeDir Makefile.msc] - lappend result nmake /f $nmakeFile TOP=$nmakeDir clean + set nmakeFile [file nativename [file join $nmakeDir Makefile.msc]] + lappend result nmake /f $nmakeFile TOP=$nmakeDir } else { - lappend result make clean + lappend result make + } + foreach makeOpt $makeOpts { + lappend result $makeOpt } + lappend result clean foreach target $targets { lappend result $target } lappend result CFLAGS=$cflags OPTS=$opts >>& test.log } @@ -505,13 +684,15 @@ # proc trace_cmd {args} { if {$::TRACE} { PUTS $args } + set res "" if {!$::DRYRUN} { - uplevel 1 $args + set res [uplevel 1 $args] } + return $res } # This proc processes the command line options passed to this script. # Currently the only option supported is "-makefile", default @@ -518,33 +699,48 @@ # "releasetest.mk". Set the ::MAKEFILE variable to the value of this # option. # proc process_options {argv} { set ::SRCDIR [file normalize [file dirname [file dirname $::argv0]]] - set ::QUICK 0 - set ::MSVC 0 - set ::BUILDONLY 0 - set ::DRYRUN 0 - set ::EXEC exec - set ::TRACE 0 - set ::WITHTCL {} + set ::QUICK 0 + set ::MSVC 0 + set ::BUILDONLY 0 + set ::DRYRUN 0 + set ::TRACE 0 + set ::JOBS 1 + set ::PROGRESS_MSGS 0 + set ::WITHTCL {} set config {} set platform $::tcl_platform(os)-$::tcl_platform(machine) for {set i 0} {$i < [llength $argv]} {incr i} { set x [lindex $argv $i] if {[regexp {^--[a-z]} $x]} {set x [string range $x 1 end]} switch -glob -- $x { + -slave { + run_slave_test + exit + } + -srcdir { incr i set ::SRCDIR [file normalize [lindex $argv $i]] } -platform { incr i set platform [lindex $argv $i] } + + -jobs { + incr i + set ::JOBS [lindex $argv $i] + } + + -progress { + set ::PROGRESS_MSGS 1 + } -quick { set ::QUICK 1 } -veryquick { @@ -595,15 +791,11 @@ } exit } -g { - if {$::MSVC} { - lappend ::EXTRACONFIG -Zi - } else { - lappend ::EXTRACONFIG [lindex $argv $i] - } + lappend ::EXTRACONFIG [lindex $argv $i] } -with-tcl=* { set ::WITHTCL -$x } @@ -638,22 +830,32 @@ if {$config!=""} { if {[llength $config]==1} {lappend config fulltest} set ::CONFIGLIST $config } else { - set ::CONFIGLIST $::Platforms($platform) + if {$::JOBS>1} { + set ::CONFIGLIST {} + foreach {target zConfig} [lreverse $::Platforms($platform)] { + append ::CONFIGLIST [format " %-25s %s\n" \ + [list $zConfig] [list $target]] + } + } else { + set ::CONFIGLIST $::Platforms($platform) + } } PUTS "Running the following test configurations for $platform:" PUTS " [string trim $::CONFIGLIST]" PUTS -nonewline "Flags:" + if {$::PROGRESS_MSGS} {PUTS -nonewline " --progress"} if {$::DRYRUN} {PUTS -nonewline " --dryrun"} if {$::BUILDONLY} {PUTS -nonewline " --buildonly"} if {$::MSVC} {PUTS -nonewline " --msvc"} switch -- $::QUICK { 1 {PUTS -nonewline " --quick"} 2 {PUTS -nonewline " --veryquick"} } + if {$::JOBS>1} {PUTS -nonewline " --jobs $::JOBS"} PUTS "" } # Main routine. # @@ -681,17 +883,19 @@ 1 {set target quicktest} 2 {set target smoketest} } if {$::BUILDONLY} { set target testfixture - if {$::MSVC} {append target .exe} + if {$::tcl_platform(platform)=="windows"} { + append target .exe + } } } set config_options [concat $::Configs($zConfig) $::EXTRACONFIG] incr NTEST - run_test_suite $zConfig $target $config_options + add_test_suite all $zConfig $target $config_options # If the configuration included the SQLITE_DEBUG option, then remove # it and run veryquick.test. If it did not include the SQLITE_DEBUG option # add it and run veryquick.test. if {$target!="checksymbols" && $target!="valgrindtest" @@ -701,19 +905,21 @@ regsub -all {fulltest[a-z]*} $xtarget test xtarget regsub -all {fuzzoomtest} $xtarget fuzztest xtarget if {$debug_idx < 0} { incr NTEST append config_options " -DSQLITE_DEBUG=1" - run_test_suite "${zConfig}_debug" $xtarget $config_options + add_test_suite all "${zConfig}_debug" $xtarget $config_options } else { incr NTEST regsub { *-DSQLITE_MEMDEBUG[^ ]* *} $config_options { } config_options regsub { *-DSQLITE_DEBUG[^ ]* *} $config_options { } config_options - run_test_suite "${zConfig}_ndebug" $xtarget $config_options + add_test_suite all "${zConfig}_ndebug" $xtarget $config_options } } } + + run_all_test_suites $all set elapsetime [expr {[clock seconds]-$STARTTIME}] set hr [expr {$elapsetime/3600}] set min [expr {($elapsetime/60)%60}] set sec [expr {$elapsetime%60}] Index: test/where.test ================================================================== --- test/where.test +++ test/where.test @@ -410,26 +410,26 @@ } {1 0 4 2 1 9 3 1 16 102} do_test where-5.3a { count { SELECT * FROM t1 WHERE w IN (-1,1,2,3) order by 1; } - } {1 0 4 2 1 9 3 1 16 13} + } {1 0 4 2 1 9 3 1 16 12} do_test where-5.3b { count { SELECT * FROM t1 WHERE w IN (3,-1,1,2) order by 1; } - } {1 0 4 2 1 9 3 1 16 13} + } {1 0 4 2 1 9 3 1 16 12} do_test where-5.3c { count { SELECT * FROM t1 WHERE w IN (3,2,-1,1,2) order by 1; } - } {1 0 4 2 1 9 3 1 16 13} + } {1 0 4 2 1 9 3 1 16 12} do_test where-5.3d { count { SELECT * FROM t1 WHERE w IN (-1,1,2,3) order by 1 DESC; } - } {3 1 16 2 1 9 1 0 4 12} + } {3 1 16 2 1 9 1 0 4 11} do_test where-5.4 { count { SELECT * FROM t1 WHERE w+0 IN (-1,1,2,3) order by 1; } } {1 0 4 2 1 9 3 1 16 102} @@ -463,11 +463,11 @@ } {2 1 9 4 2 25 103} do_test where-5.9 { count { SELECT * FROM t1 WHERE x IN (1,7) ORDER BY 1; } - } {2 1 9 3 1 16 7} + } {2 1 9 3 1 16 6} do_test where-5.10 { count { SELECT * FROM t1 WHERE x+0 IN (1,7) ORDER BY 1; } } {2 1 9 3 1 16 199} @@ -483,21 +483,21 @@ } {79 6 6400 89 6 8100 7} do_test where-5.13 { count { SELECT * FROM t1 WHERE x IN (1,7) AND y NOT IN (6400,8100) ORDER BY 1; } - } {2 1 9 3 1 16 7} + } {2 1 9 3 1 16 6} do_test where-5.14 { count { SELECT * FROM t1 WHERE x IN (1,7) AND y IN (9,10) ORDER BY 1; } - } {2 1 9 8} + } {2 1 9 5} do_test where-5.15 { count { SELECT * FROM t1 WHERE x IN (1,7) AND y IN (9,16) ORDER BY 1; } - } {2 1 9 3 1 16 11} + } {2 1 9 3 1 16 9} do_test where-5.100 { db eval { SELECT w, x, y FROM t1 WHERE x IN (1,5) AND y IN (9,8,3025,1000,3969) ORDER BY x, y } Index: test/where4.test ================================================================== --- test/where4.test +++ test/where4.test @@ -89,11 +89,11 @@ do_test where4-1.10 { count {SELECT rowid FROM t1 WHERE w=x'78' AND x IS NULL} } {6 2} do_test where4-1.11 { count {SELECT rowid FROM t1 WHERE w=x'78' AND x IS NULL AND y=123} -} {1} +} {0} do_test where4-1.12 { count {SELECT rowid FROM t1 WHERE w=x'78' AND x IS NULL AND y=x'7A'} } {6 2} do_test where4-1.13 { count {SELECT rowid FROM t1 WHERE w IS NULL AND x IS NULL}