diff --git a/Makefile.in b/Makefile.in index 0ea9498be0..c375daa232 100644 --- a/Makefile.in +++ b/Makefile.in @@ -169,6 +169,7 @@ LIBOBJS0 = alter.lo analyze.lo attach.lo auth.lo \ expr.lo fault.lo fkey.lo \ fts3.lo fts3_aux.lo fts3_expr.lo fts3_hash.lo fts3_icu.lo \ fts3_porter.lo fts3_snippet.lo fts3_tokenizer.lo fts3_tokenizer1.lo \ + fts3_tokenize_vtab.lo \ fts3_unicode.lo fts3_unicode2.lo fts3_write.lo \ func.lo global.lo hash.lo \ icu.lo insert.lo journal.lo legacy.lo loadext.lo \ @@ -319,6 +320,7 @@ SRC += \ $(TOP)/ext/fts3/fts3_tokenizer.h \ $(TOP)/ext/fts3/fts3_tokenizer.c \ $(TOP)/ext/fts3/fts3_tokenizer1.c \ + $(TOP)/ext/fts3/fts3_tokenize_vtab.c \ $(TOP)/ext/fts3/fts3_unicode.c \ $(TOP)/ext/fts3/fts3_unicode2.c \ $(TOP)/ext/fts3/fts3_write.c @@ -511,6 +513,11 @@ sqlite3$(TEXE): $(TOP)/src/shell.c libsqlite3.la sqlite3.h -o $@ $(TOP)/src/shell.c libsqlite3.la \ $(LIBREADLINE) $(TLIBS) -rpath "$(libdir)" +mptester$(EXE): sqlite3.c $(TOP)/mptest/mptest.c + $(LTLINK) -o $@ -I. $(TOP)/mptest/mptest.c sqlite3.c \ + $(TLIBS) -rpath "$(libdir)" + + # This target creates a directory named "tsrc" and fills it with # copies of all of the C source code and header files needed to # build on the target system. Some of the C source code and header @@ -863,6 +870,9 @@ fts3_tokenizer.lo: $(TOP)/ext/fts3/fts3_tokenizer.c $(HDR) $(EXTHDR) fts3_tokenizer1.lo: $(TOP)/ext/fts3/fts3_tokenizer1.c $(HDR) $(EXTHDR) $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_tokenizer1.c +fts3_tokenizer_vtab.lo: $(TOP)/ext/fts3/fts3_tokenizer_vtab.c $(HDR) $(EXTHDR) + $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_tokenizer_vtab.c + fts3_unicode.lo: $(TOP)/ext/fts3/fts3_unicode.c $(HDR) $(EXTHDR) $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_unicode.c @@ -959,8 +969,11 @@ clean: rm -f testfixture$(TEXE) test.db rm -f sqlite3.dll sqlite3.lib sqlite3.exp sqlite3.def rm -f sqlite3.c + rm -f sqlite3rc.h + rm -f shell.c sqlite3ext.h rm -f sqlite3_analyzer$(TEXE) sqlite3_analyzer.c rm -f sqlite-*-output.vsix + rm -f mptester mptester.exe distclean: clean rm -f config.log config.status libtool Makefile sqlite3.pc diff --git a/Makefile.msc b/Makefile.msc index 8f34c6cc2d..db3ec3f2f8 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -482,7 +482,7 @@ LIBOBJS0 = alter.lo analyze.lo attach.lo auth.lo \ expr.lo fault.lo fkey.lo \ fts3.lo fts3_aux.lo fts3_expr.lo fts3_hash.lo fts3_icu.lo \ fts3_porter.lo fts3_snippet.lo fts3_tokenizer.lo fts3_tokenizer1.lo \ - fts3_unicode.lo fts3_unicode2.lo fts3_write.lo \ + fts3_tokenize_vtab.lo fts3_unicode.lo fts3_unicode2.lo fts3_write.lo \ func.lo global.lo hash.lo \ icu.lo insert.lo journal.lo legacy.lo loadext.lo \ main.lo malloc.lo mem0.lo mem1.lo mem2.lo mem3.lo mem5.lo \ @@ -643,6 +643,7 @@ SRC = $(SRC) \ $(TOP)\ext\fts3\fts3_tokenizer.h \ $(TOP)\ext\fts3\fts3_tokenizer.c \ $(TOP)\ext\fts3\fts3_tokenizer1.c \ + $(TOP)\ext\fts3\fts3_tokenize_vtab.c \ $(TOP)\ext\fts3\fts3_unicode.c \ $(TOP)\ext\fts3\fts3_unicode2.c \ $(TOP)\ext\fts3\fts3_write.c @@ -758,6 +759,7 @@ TESTSRC2 = \ $(TOP)\ext\fts3\fts3_aux.c \ $(TOP)\ext\fts3\fts3_expr.c \ $(TOP)\ext\fts3\fts3_tokenizer.c \ + $(TOP)\ext\fts3\fts3_tokenize_vtab.c \ $(TOP)\ext\fts3\fts3_unicode.c \ $(TOP)\ext\fts3\fts3_unicode2.c \ $(TOP)\ext\fts3\fts3_write.c \ @@ -832,6 +834,10 @@ sqlite3.exe: $(TOP)\src\shell.c libsqlite3.lib $(LIBRESOBJS) sqlite3.h $(TOP)\src\shell.c \ /link $(LTLINKOPTS) $(LTLIBPATHS) libsqlite3.lib $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS) +mptester.exe: $(TOP)\mptest\mptest.c libsqlite3.lib $(LIBRESOBJS) sqlite3.h + $(LTLINK) $(TOP)\mptest\mptest.c \ + /link $(LTLINKOPTS) $(LTLIBPATHS) libsqlite3.lib $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS) + # This target creates a directory named "tsrc" and fills it with # copies of all of the C source code and header files needed to # build on the target system. Some of the C source code and header @@ -1192,6 +1198,9 @@ fts3_tokenizer.lo: $(TOP)\ext\fts3\fts3_tokenizer.c $(HDR) $(EXTHDR) fts3_tokenizer1.lo: $(TOP)\ext\fts3\fts3_tokenizer1.c $(HDR) $(EXTHDR) $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)\ext\fts3\fts3_tokenizer1.c +fts3_tokenize_vtab.lo: $(TOP)\ext\fts3\fts3_tokenize_vtab.c $(HDR) $(EXTHDR) + $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)\ext\fts3\fts3_tokenize_vtab.c + fts3_unicode.lo: $(TOP)\ext\fts3\fts3_unicode.c $(HDR) $(EXTHDR) $(LTCOMPILE) -DSQLITE_CORE -c $(TOP)\ext\fts3\fts3_unicode.c @@ -1273,8 +1282,10 @@ clean: del /Q sqlite3.dll sqlite3.lib sqlite3.exp sqlite3.def del /Q sqlite3.c del /Q sqlite3rc.h + del /Q shell.c sqlite3ext.h del /Q sqlite3_analyzer.exe sqlite3_analyzer.exp sqlite3_analyzer.c del /Q sqlite-*-output.vsix + del /Q mptester.exe # Dynamic link library section. # diff --git a/Makefile.vxworks b/Makefile.vxworks index d04028833c..c9058da3e5 100644 --- a/Makefile.vxworks +++ b/Makefile.vxworks @@ -662,4 +662,6 @@ clean: rm -rf tsrc target_source rm -f testloadext.dll libtestloadext.so rm -f sqlite3.c fts?amal.c tclsqlite3.c + rm -f sqlite3rc.h + rm -f shell.c sqlite3ext.h rm -f $(SHPREFIX)sqlite3.$(SO) diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 6b3a7b1565..0c7e07740d 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -3593,6 +3593,9 @@ int sqlite3Fts3Init(sqlite3 *db){ rc = sqlite3Fts3InitAux(db); if( rc!=SQLITE_OK ) return rc; + rc = sqlite3Fts3InitTok(db); + if( rc!=SQLITE_OK ) return rc; + sqlite3Fts3SimpleTokenizerModule(&pSimple); sqlite3Fts3PorterTokenizerModule(&pPorter); diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h index 77ca4704e8..a11c18a3b7 100644 --- a/ext/fts3/fts3Int.h +++ b/ext/fts3/fts3Int.h @@ -549,6 +549,9 @@ int sqlite3Fts3EvalPhrasePoslist(Fts3Cursor *, Fts3Expr *, int iCol, char **); int sqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *); int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr); +/* fts3_tokenize_vtab.c */ +int sqlite3Fts3InitTok(sqlite3*); + /* fts3_unicode2.c (functions generated by parsing unicode text files) */ #ifdef SQLITE_ENABLE_FTS4_UNICODE61 int sqlite3FtsUnicodeFold(int, int); diff --git a/ext/fts3/fts3_aux.c b/ext/fts3/fts3_aux.c index a2bff2e1d3..9b582fcf8b 100644 --- a/ext/fts3/fts3_aux.c +++ b/ext/fts3/fts3_aux.c @@ -70,17 +70,26 @@ static int fts3auxConnectMethod( UNUSED_PARAMETER(pUnused); - /* The user should specify a single argument - the name of an fts3 table. */ - if( argc!=4 ){ - *pzErr = sqlite3_mprintf( - "wrong number of arguments to fts4aux constructor" - ); - return SQLITE_ERROR; - } + /* The user should invoke this in one of two forms: + ** + ** CREATE VIRTUAL TABLE xxx USING fts4aux(fts4-table); + ** CREATE VIRTUAL TABLE xxx USING fts4aux(fts4-table-db, fts4-table); + */ + if( argc!=4 && argc!=5 ) goto bad_args; zDb = argv[1]; nDb = (int)strlen(zDb); - zFts3 = argv[3]; + if( argc==5 ){ + if( nDb==4 && 0==sqlite3_strnicmp("temp", zDb, 4) ){ + zDb = argv[3]; + nDb = (int)strlen(zDb); + zFts3 = argv[4]; + }else{ + goto bad_args; + } + }else{ + zFts3 = argv[3]; + } nFts3 = (int)strlen(zFts3); rc = sqlite3_declare_vtab(db, FTS3_TERMS_SCHEMA); @@ -103,6 +112,10 @@ static int fts3auxConnectMethod( *ppVtab = (sqlite3_vtab *)p; return SQLITE_OK; + + bad_args: + *pzErr = sqlite3_mprintf("invalid arguments to fts4aux constructor"); + return SQLITE_ERROR; } /* diff --git a/ext/fts3/fts3_tokenize_vtab.c b/ext/fts3/fts3_tokenize_vtab.c new file mode 100644 index 0000000000..9ec3b32aad --- /dev/null +++ b/ext/fts3/fts3_tokenize_vtab.c @@ -0,0 +1,464 @@ +/* +** 2013 Apr 22 +** +** 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 contains code for the "fts3tokenize" virtual table module. +** An fts3tokenize virtual table is created as follows: +** +** CREATE VIRTUAL TABLE USING fts3tokenize( +** , , ... +** ); +** +** The table created has the following schema: +** +** CREATE TABLE (input, token, start, end, position) +** +** When queried, the query must include a WHERE clause of type: +** +** input = +** +** The virtual table module tokenizes this , using the FTS3 +** tokenizer specified by the arguments to the CREATE VIRTUAL TABLE +** statement and returns one row for each token in the result. With +** fields set as follows: +** +** input: Always set to a copy of +** token: A token from the input. +** start: Byte offset of the token within the input . +** end: Byte offset of the byte immediately following the end of the +** token within the input string. +** pos: Token offset of token within input. +** +*/ +#include "fts3Int.h" +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) + +#include +#include + +typedef struct Fts3tokTable Fts3tokTable; +typedef struct Fts3tokCursor Fts3tokCursor; + +/* +** Virtual table structure. +*/ +struct Fts3tokTable { + sqlite3_vtab base; /* Base class used by SQLite core */ + const sqlite3_tokenizer_module *pMod; + sqlite3_tokenizer *pTok; +}; + +/* +** Virtual table cursor structure. +*/ +struct Fts3tokCursor { + sqlite3_vtab_cursor base; /* Base class used by SQLite core */ + char *zInput; /* Input string */ + sqlite3_tokenizer_cursor *pCsr; /* Cursor to iterate through zInput */ + int iRowid; /* Current 'rowid' value */ + const char *zToken; /* Current 'token' value */ + int nToken; /* Size of zToken in bytes */ + int iStart; /* Current 'start' value */ + int iEnd; /* Current 'end' value */ + int iPos; /* Current 'pos' value */ +}; + +/* +** Query FTS for the tokenizer implementation named zName. +*/ +static int fts3tokQueryTokenizer( + sqlite3 *db, + const char *zName, + const sqlite3_tokenizer_module **pp +){ + int rc; + sqlite3_stmt *pStmt; + const char *zSql = "SELECT fts3_tokenizer(?)"; + + *pp = 0; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ + return rc; + } + + sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){ + memcpy((void*)pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp)); + } + } + + return sqlite3_finalize(pStmt); +} + +/* +** The second argument, argv[], is an array of pointers to nul-terminated +** strings. This function makes a copy of the array and strings into a +** single block of memory. It then dequotes any of the strings that appear +** to be quoted. +** +** If successful, output parameter *pazDequote is set to point at the +** array of dequoted strings and SQLITE_OK is returned. The caller is +** responsible for eventually calling sqlite3_free() to free the array +** in this case. Or, if an error occurs, an SQLite error code is returned. +** The final value of *pazDequote is undefined in this case. +*/ +static int fts3tokDequoteArray( + int argc, /* Number of elements in argv[] */ + const char * const *argv, /* Input array */ + char ***pazDequote /* Output array */ +){ + int rc = SQLITE_OK; /* Return code */ + if( argc==0 ){ + *pazDequote = 0; + }else{ + int i; + int nByte = 0; + char **azDequote; + + for(i=0; ixCreate((nDequote>1 ? nDequote-1 : 0), azArg, &pTok); + } + + if( rc==SQLITE_OK ){ + pTab = (Fts3tokTable *)sqlite3_malloc(sizeof(Fts3tokTable)); + if( pTab==0 ){ + rc = SQLITE_NOMEM; + } + } + + if( rc==SQLITE_OK ){ + memset(pTab, 0, sizeof(Fts3tokTable)); + pTab->pMod = pMod; + pTab->pTok = pTok; + *ppVtab = &pTab->base; + }else{ + if( pTok ){ + pMod->xDestroy(pTok); + } + } + + sqlite3_free(azDequote); + return rc; +} + +/* +** This function does the work for both the xDisconnect and xDestroy methods. +** These tables have no persistent representation of their own, so xDisconnect +** and xDestroy are identical operations. +*/ +static int fts3tokDisconnectMethod(sqlite3_vtab *pVtab){ + Fts3tokTable *pTab = (Fts3tokTable *)pVtab; + + pTab->pMod->xDestroy(pTab->pTok); + sqlite3_free(pTab); + return SQLITE_OK; +} + +/* +** xBestIndex - Analyze a WHERE and ORDER BY clause. +*/ +static int fts3tokBestIndexMethod( + sqlite3_vtab *pVTab, + sqlite3_index_info *pInfo +){ + int i; + UNUSED_PARAMETER(pVTab); + + for(i=0; inConstraint; i++){ + if( pInfo->aConstraint[i].usable + && pInfo->aConstraint[i].iColumn==0 + && pInfo->aConstraint[i].op==SQLITE_INDEX_CONSTRAINT_EQ + ){ + pInfo->idxNum = 1; + pInfo->aConstraintUsage[i].argvIndex = 1; + pInfo->aConstraintUsage[i].omit = 1; + pInfo->estimatedCost = 1; + return SQLITE_OK; + } + } + + pInfo->idxNum = 0; + assert( pInfo->estimatedCost>1000000.0 ); + + return SQLITE_OK; +} + +/* +** xOpen - Open a cursor. +*/ +static int fts3tokOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ + Fts3tokCursor *pCsr; + UNUSED_PARAMETER(pVTab); + + pCsr = (Fts3tokCursor *)sqlite3_malloc(sizeof(Fts3tokCursor)); + if( pCsr==0 ){ + return SQLITE_NOMEM; + } + memset(pCsr, 0, sizeof(Fts3tokCursor)); + + *ppCsr = (sqlite3_vtab_cursor *)pCsr; + return SQLITE_OK; +} + +/* +** Reset the tokenizer cursor passed as the only argument. As if it had +** just been returned by fts3tokOpenMethod(). +*/ +static void fts3tokResetCursor(Fts3tokCursor *pCsr){ + if( pCsr->pCsr ){ + Fts3tokTable *pTab = (Fts3tokTable *)(pCsr->base.pVtab); + pTab->pMod->xClose(pCsr->pCsr); + pCsr->pCsr = 0; + } + sqlite3_free(pCsr->zInput); + pCsr->zInput = 0; + pCsr->zToken = 0; + pCsr->nToken = 0; + pCsr->iStart = 0; + pCsr->iEnd = 0; + pCsr->iPos = 0; + pCsr->iRowid = 0; +} + +/* +** xClose - Close a cursor. +*/ +static int fts3tokCloseMethod(sqlite3_vtab_cursor *pCursor){ + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + + fts3tokResetCursor(pCsr); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** xNext - Advance the cursor to the next row, if any. +*/ +static int fts3tokNextMethod(sqlite3_vtab_cursor *pCursor){ + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + Fts3tokTable *pTab = (Fts3tokTable *)(pCursor->pVtab); + int rc; /* Return code */ + + pCsr->iRowid++; + rc = pTab->pMod->xNext(pCsr->pCsr, + &pCsr->zToken, &pCsr->nToken, + &pCsr->iStart, &pCsr->iEnd, &pCsr->iPos + ); + + if( rc!=SQLITE_OK ){ + fts3tokResetCursor(pCsr); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + } + + return rc; +} + +/* +** xFilter - Initialize a cursor to point at the start of its data. +*/ +static int fts3tokFilterMethod( + sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ + int idxNum, /* Strategy index */ + const char *idxStr, /* Unused */ + int nVal, /* Number of elements in apVal */ + sqlite3_value **apVal /* Arguments for the indexing scheme */ +){ + int rc = SQLITE_ERROR; + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + Fts3tokTable *pTab = (Fts3tokTable *)(pCursor->pVtab); + UNUSED_PARAMETER(idxStr); + UNUSED_PARAMETER(nVal); + + fts3tokResetCursor(pCsr); + if( idxNum==1 ){ + const char *zByte = (const char *)sqlite3_value_text(apVal[0]); + int nByte = sqlite3_value_bytes(apVal[0]); + pCsr->zInput = sqlite3_malloc(nByte+1); + if( pCsr->zInput==0 ){ + rc = SQLITE_NOMEM; + }else{ + memcpy(pCsr->zInput, zByte, nByte); + pCsr->zInput[nByte] = 0; + rc = pTab->pMod->xOpen(pTab->pTok, pCsr->zInput, nByte, &pCsr->pCsr); + if( rc==SQLITE_OK ){ + pCsr->pCsr->pTokenizer = pTab->pTok; + } + } + } + + if( rc!=SQLITE_OK ) return rc; + return fts3tokNextMethod(pCursor); +} + +/* +** xEof - Return true if the cursor is at EOF, or false otherwise. +*/ +static int fts3tokEofMethod(sqlite3_vtab_cursor *pCursor){ + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + return (pCsr->zToken==0); +} + +/* +** xColumn - Return a column value. +*/ +static int fts3tokColumnMethod( + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ + sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */ + int iCol /* Index of column to read value from */ +){ + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + + /* CREATE TABLE x(input, token, start, end, position) */ + switch( iCol ){ + case 0: + sqlite3_result_text(pCtx, pCsr->zInput, -1, SQLITE_TRANSIENT); + break; + case 1: + sqlite3_result_text(pCtx, pCsr->zToken, pCsr->nToken, SQLITE_TRANSIENT); + break; + case 2: + sqlite3_result_int(pCtx, pCsr->iStart); + break; + case 3: + sqlite3_result_int(pCtx, pCsr->iEnd); + break; + default: + assert( iCol==4 ); + sqlite3_result_int(pCtx, pCsr->iPos); + break; + } + return SQLITE_OK; +} + +/* +** xRowid - Return the current rowid for the cursor. +*/ +static int fts3tokRowidMethod( + sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */ + sqlite_int64 *pRowid /* OUT: Rowid value */ +){ + Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor; + *pRowid = (sqlite3_int64)pCsr->iRowid; + return SQLITE_OK; +} + +/* +** Register the fts3tok module with database connection db. Return SQLITE_OK +** if successful or an error code if sqlite3_create_module() fails. +*/ +int sqlite3Fts3InitTok(sqlite3 *db){ + static const sqlite3_module fts3tok_module = { + 0, /* iVersion */ + fts3tokConnectMethod, /* xCreate */ + fts3tokConnectMethod, /* xConnect */ + fts3tokBestIndexMethod, /* xBestIndex */ + fts3tokDisconnectMethod, /* xDisconnect */ + fts3tokDisconnectMethod, /* xDestroy */ + fts3tokOpenMethod, /* xOpen */ + fts3tokCloseMethod, /* xClose */ + fts3tokFilterMethod, /* xFilter */ + fts3tokNextMethod, /* xNext */ + fts3tokEofMethod, /* xEof */ + fts3tokColumnMethod, /* xColumn */ + fts3tokRowidMethod, /* xRowid */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindFunction */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0 /* xRollbackTo */ + }; + int rc; /* Return code */ + + rc = sqlite3_create_module(db, "fts3tokenize", &fts3tok_module, 0); + return rc; +} + +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test index e3c7d68e82..275b13264f 100644 --- a/ext/rtree/rtree1.test +++ b/ext/rtree/rtree1.test @@ -17,6 +17,7 @@ if {![info exists testdir]} { } source [file join [file dirname [info script]] rtree_util.tcl] source $testdir/tester.tcl +set testprefix rtree1 # Test plan: # diff --git a/ext/rtree/rtree5.test b/ext/rtree/rtree5.test index 8990772356..8ff90b0cb7 100644 --- a/ext/rtree/rtree5.test +++ b/ext/rtree/rtree5.test @@ -61,7 +61,7 @@ do_test rtree5-1.9 { do_test rtree5-1.10 { execsql { SELECT (1<<31)-5, (1<<31)-1, -1*(1<<31), -1*(1<<31)+5 } } {2147483643 2147483647 -2147483648 -2147483643} -do_test rtree5-1.10 { +do_test rtree5-1.11 { execsql { INSERT INTO t1 VALUES(2, (1<<31)-5, (1<<31)-1, -1*(1<<31), -1*(1<<31)+5) } diff --git a/main.mk b/main.mk index 5faf2fb8e9..57435a1730 100644 --- a/main.mk +++ b/main.mk @@ -56,6 +56,7 @@ LIBOBJ+= alter.o analyze.o attach.o auth.o \ callback.o complete.o ctime.o date.o delete.o expr.o fault.o fkey.o \ fts3.o fts3_aux.o fts3_expr.o fts3_hash.o fts3_icu.o fts3_porter.o \ fts3_snippet.o fts3_tokenizer.o fts3_tokenizer1.o \ + fts3_tokenize_vtab.o \ fts3_unicode.o fts3_unicode2.o \ fts3_write.o func.o global.o hash.o \ icu.o insert.o journal.o legacy.o loadext.o \ @@ -198,6 +199,7 @@ SRC += \ $(TOP)/ext/fts3/fts3_tokenizer.h \ $(TOP)/ext/fts3/fts3_tokenizer.c \ $(TOP)/ext/fts3/fts3_tokenizer1.c \ + $(TOP)/ext/fts3/fts3_tokenize_vtab.c \ $(TOP)/ext/fts3/fts3_unicode.c \ $(TOP)/ext/fts3/fts3_unicode2.c \ $(TOP)/ext/fts3/fts3_write.c @@ -371,6 +373,10 @@ sqlite3$(EXE): $(TOP)/src/shell.c libsqlite3.a sqlite3.h $(TOP)/src/shell.c \ libsqlite3.a $(LIBREADLINE) $(TLIBS) $(THREADLIB) +mptester$(EXE): sqlite3.c $(TOP)/mptest/mptest.c + $(TCCX) -o $@ -I. $(TOP)/mptest/mptest.c sqlite3.c \ + $(TLIBS) $(THREADLIB) + sqlite3.o: sqlite3.c $(TCCX) -c sqlite3.c @@ -519,6 +525,9 @@ fts3_tokenizer.o: $(TOP)/ext/fts3/fts3_tokenizer.c $(HDR) $(EXTHDR) fts3_tokenizer1.o: $(TOP)/ext/fts3/fts3_tokenizer1.c $(HDR) $(EXTHDR) $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_tokenizer1.c +fts3_tokenize_vtab.o: $(TOP)/ext/fts3/fts3_tokenize_vtab.c $(HDR) $(EXTHDR) + $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_tokenize_vtab.c + fts3_unicode.o: $(TOP)/ext/fts3/fts3_unicode.c $(HDR) $(EXTHDR) $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_unicode.c @@ -633,5 +642,8 @@ clean: rm -f testfixture testfixture.exe rm -f threadtest3 threadtest3.exe rm -f sqlite3.c fts?amal.c tclsqlite3.c + rm -f sqlite3rc.h + rm -f shell.c sqlite3ext.h rm -f sqlite3_analyzer sqlite3_analyzer.exe sqlite3_analyzer.c rm -f sqlite-*-output.vsix + rm -f mptester mptester.exe diff --git a/manifest b/manifest index cad0902a3d..2dc92ae73d 100644 --- a/manifest +++ b/manifest @@ -1,10 +1,10 @@ -C Fix\sthe\sxCheckReservedLock()\smethod\son\sthe\swindows\sVFS\sso\sthat\sit\scannot\sreturn\sa\sfalse\spositive\swhen\stwo\sor\smore\sprocesses\suse\sit\sat\sthe\ssame\stime\son\sthe\ssame\sfile.\sTicket\s[7ff3120e4fa54abb55].\s\sUpdate\sto\sversion\s3.7.16.2. -D 2013-04-12T13:53:50.045 +C Merge\sthe\slatest\strunk\schanges\sinto\sthe\ssessions\sbranch. +D 2013-04-22T23:59:06.075 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f -F Makefile.in 174bfca74e57f38699e3412a984f6b38106750fa +F Makefile.in 4db477715e5d66fdcbb4f7a0870d10b0adbe007e F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 -F Makefile.msc 0ad7d4278a3b7e0c56d3ca7cc607b34acc1df516 -F Makefile.vxworks b18ad88e9a8c6a001f5cf4a389116a4f1a7ab45f +F Makefile.msc 95b9e9992abcb32dda9ad7460bb1c4a3e0985909 +F Makefile.vxworks db21ed42a01d5740e656b16f92cb5d8d5e5dd315 F README cd04a36fbc7ea56932a4052d7d0b7f09f27c33d6 F VERSION 0dee4d2e0c64791ff0085277424fb5c07d79fc9a F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 @@ -55,10 +55,10 @@ F ext/fts3/README.content fdc666a70d5257a64fee209f97cf89e0e6e32b51 F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.tokenizers e0a8b81383ea60d0334d274fadf305ea14a8c314 F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c 0eaedfc6d2eb22563ef1d044dcfed93b70ec79f2 +F ext/fts3/fts3.c 784aadfb4c2a217c3eb1feaecac924989f29728f F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe -F ext/fts3/fts3Int.h 1e58825246b56259267382d2f9902774c049460a -F ext/fts3/fts3_aux.c 5205182bd8f372782597888156404766edf5781e +F ext/fts3/fts3Int.h 352c8a83ee4c6a14ced1759a39dd890ab947cbe0 +F ext/fts3/fts3_aux.c b02632f6dd0e375ce97870206d914ea6d8df5ccd F ext/fts3/fts3_expr.c 6cb4410f87676ae633bd7923bbc78526cb839c4d F ext/fts3/fts3_hash.c 8dd2d06b66c72c628c2732555a32bc0943114914 F ext/fts3/fts3_hash.h 39cf6874dc239d6b4e30479b1975fe5b22a3caaf @@ -67,6 +67,7 @@ F ext/fts3/fts3_porter.c a465b49fcb8249a755792f87516eff182efa42b3 F ext/fts3/fts3_snippet.c 5fcfcafff46a2a3a63b8e59fcb51987d01c74695 F ext/fts3/fts3_term.c a521f75132f9a495bdca1bdd45949b3191c52763 F ext/fts3/fts3_test.c f9a1a1702db1bfad3e2d0064746eeb808f125489 +F ext/fts3/fts3_tokenize_vtab.c a29f126b9e6c6a6f1021a8f7440bf125e68af1f9 F ext/fts3/fts3_tokenizer.c bbdc731bc91338050675c6d1da9ab82147391e16 F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c 5c98225a53705e5ee34824087478cf477bdb7004 @@ -85,11 +86,11 @@ F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/rtree.c 757abea591d4ff67c0ff4e8f9776aeda86b18c14 F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e -F ext/rtree/rtree1.test e474a2b5eff231496dbd073fe67e5fbaf7f444c9 +F ext/rtree/rtree1.test cf679265ecafff494a768ac9c2f43a70915a6290 F ext/rtree/rtree2.test acbb3a4ce0f4fbc2c304d2b4b784cfa161856bba F ext/rtree/rtree3.test a494da55c30ee0bc9b01a91c80c81b387b22d2dc F ext/rtree/rtree4.test c8fe384f60ebd49540a5fecc990041bf452eb6e0 -F ext/rtree/rtree5.test 9a229678a00f40e6aedb40cb3a07ec5444af892c +F ext/rtree/rtree5.test 6a510494f12454bf57ef28f45bc7764ea279431e F ext/rtree/rtree6.test 3ff9113b4a872fa935309e3511cd9b7cdb4d2472 F ext/rtree/rtree7.test 1fa710b9e6bf997a0c1a537b81be7bb6fded1971 F ext/rtree/rtree8.test 9772e16da71e17e02bdebf0a5188590f289ab37d @@ -115,13 +116,19 @@ F ext/session/sqlite3session.h f374c9c4c96e08f67ac418871c29d423245c7673 F ext/session/test_session.c ea4dc9b4a1895c8e6bddcbfe3838d7eb57df2d99 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 -F main.mk 01f02b625cc1b8609690aaddb00e2fbb1edbeec5 +F main.mk 88fb64131032933d96fd65039a6dd9140beb2566 F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac F mkopcodec.awk f6fccee29e68493bfd90a2e0466ede5fa94dd2fc F mkopcodeh.awk 29b84656502eee5f444c3147f331ee686956ab0e F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 +F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 +F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 +F mptest/crash01.test a5f31998ed48de8267d6620e8af107ec148e5f12 +F mptest/crash02.subtest f4ef05adcd15d60e5d2bd654204f2c008b519df8 +F mptest/mptest.c 499a74af4be293b7c1c7c3d40f332b67227dd739 +F mptest/multiwrite01.test 81fbc17657964889b60750bd7bbb1deffe8f4d42 F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 6be1ad09113570e1fc8dcaff84c9b0b337db5ffc @@ -130,86 +137,86 @@ F src/alter.c f8db986c03eb0bfb221523fc9bbb9d0b70de3168 F src/analyze.c d5f895810e8ff9737c9ec7b76abc3dcff5860335 F src/attach.c 1816f5a9eea8d2010fc2b22b44f0f63eb3a62704 F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34 -F src/backup.c b2cac9f7993f3f9588827b824b1501d0c820fa68 +F src/backup.c b266767351ae2d847716c56fcb2a1fea7c761c03 F src/bitvec.c 19a4ba637bd85f8f63fc8c9bae5ade9fb05ec1cb F src/btmutex.c 976f45a12e37293e32cae0281b15a21d48a8aaa7 -F src/btree.c 62ba5954765efc711c873a20a53f60d9fc2843ba -F src/btree.h 3ad7964d6c5b1c7bff569aab6adfa075f8bf06cd +F src/btree.c 480a6d255cc4f066029daf23dd54acf152cd0e13 +F src/btree.h d9490cd37aaeb530a41b07f06e1262950b1be916 F src/btreeInt.h eecc84f02375b2bb7a44abbcbbe3747dde73edb2 F src/build.c 083da8466fd7e481cb8bd5264398f537507f6176 F src/callback.c d7e46f40c3cf53c43550b7da7a1d0479910b62cc F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac -F src/ctime.c 2a5f251fcd7393808df77ccfc817e7058df08c4c +F src/ctime.c 4262c227bc91cecc61ae37ed3a40f08069cfa267 F src/date.c 067a81c9942c497aafd2c260e13add8a7d0c7dd4 F src/delete.c 39a770e9729b1acd2de347f8f614584841d0083e F src/expr.c 48048fca951eedbc74aa32262154410d56c83812 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb F src/fkey.c e16942bd5c8a868ac53287886464a5ed0e72b179 -F src/func.c 48987c025d69399f59a1c2a553cea5da41bf105d -F src/global.c e59ecd2c553ad0d4bfbc84ca71231336f8993a7a +F src/func.c d3fdcff9274bc161152e67ed3f626841c247f4b9 +F src/global.c 5caf4deab621abb45b4c607aad1bd21c20aac759 F src/hash.c ac3470bbf1ca4ae4e306a8ecb0fdf1731810ffe4 F src/hash.h 8890a25af81fb85a9ad7790d32eedab4b994da22 F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08 F src/insert.c 02f8a1867088cb654eb756f98389f10441a65216 F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d -F src/legacy.c a199d7683d60cef73089e892409113e69c23a99f +F src/legacy.c 0df0b1550b9cc1f58229644735e317ac89131f12 F src/lempar.c cdf0a000315332fc9b50b62f3b5e22e080a0952b -F src/loadext.c 1422eba4aa2b1fb5f7b3aef574752272477d21e2 -F src/main.c 557e456f41646c67ccfe3dae3fa945b5bfb788c5 +F src/loadext.c c48f7f3f170e502fe0cc20748e03c6e0b5a016c2 +F src/main.c f93769cf9fc75c4a5bfa642149c7c9128bc68811 F src/malloc.c fe085aa851b666b7c375c1ff957643dc20a04bf6 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 437c7c4af964895d4650f29881df63535caaa1fa F src/mem2.c e307323e86b5da1853d7111b68fd6b84ad6f09cf F src/mem3.c 61c9d47b792908c532ca3a62b999cf21795c6534 F src/mem5.c c2c63b7067570b00bf33d751c39af24182316f7f -F src/memjournal.c 0ebce851677a7ac035ba1512a7e65851b34530c6 +F src/memjournal.c 41a598445c8f249bd9b26ecae700c60dcf34cbf3 F src/mutex.c d3b66a569368015e0fcb1ac15f81c119f504d3bc F src/mutex.h 5bc526e19dccc412b7ff04642f6fdad3fdfdabea F src/mutex_noop.c 7682796b7d8d39bf1c138248858efcd10c9e1553 F src/mutex_unix.c c3a4e00f96ba068a8dbef34084465979aaf369cc F src/mutex_w32.c 32a9b3841e2d757355f0012b860b1bc5e01eafa0 F src/notify.c 976dd0f6171d4588e89e874fcc765e92914b6d30 -F src/os.c e1acdc09ff3ac2412945cca9766e2dcf4675f31c -F src/os.h 027491c77d2404c0a678bb3fb06286f331eb9b57 +F src/os.c b4ad71336fd96f97776f75587cd9e8218288f5be +F src/os.h 4a46270a64e9193af4a0aaa3bc2c66dc07c29b3f F src/os_common.h 92815ed65f805560b66166e3583470ff94478f04 -F src/os_unix.c 21a36fa0b3753609b6606b30d9338d4bb6b24696 -F src/os_win.c c96990935e7fd799b43a7f0ee74798f45f06e21a -F src/pager.c 3e9a15939684b0af441325f05335331b15979c9d -F src/pager.h 1109a06578ec5574dc2c74cf8d9f69daf36fe3e0 +F src/os_unix.c 5a9ac4a566fb566a2ff9b9e3a9d723075d9d26a7 +F src/os_win.c 673b3e3d1fa3040d8d95a7f1f5e0e553aed56cfb +F src/pager.c 6c3a8a5d665498b0344395a2c9f82d5abc4cc771 +F src/pager.h 5cb78b8e1adfd5451e600be7719f5a99d87ac3b1 F src/parse.y 5d5e12772845805fdfeb889163516b84fbb9ae95 F src/pcache.c f8043b433a57aba85384a531e3937a804432a346 -F src/pcache.h 1b5dcc3dc8103d03e625b177023ee67764fa6b7c +F src/pcache.h a5e4f5d9f5d592051d91212c5949517971ae6222 F src/pcache1.c 9fd22671c270b35131ef480bbc00392b8b5f8ab9 -F src/pragma.c 9f0ee3d74a7f33eeeff40a4b014fc3abf8182ce2 -F src/prepare.c 310eaff1ee5f3c700b3545afb095cfe9346efc3a +F src/pragma.c 3eacf001cbf4becbd494f8d82d08fdf1648cf8cb +F src/prepare.c 743e484233c51109666d402f470523553b41797c F src/printf.c 4a9f882f1c1787a8b494a2987765acf9d97ac21f F src/random.c cd4a67b3953b88019f8cd4ccd81394a8ddfaba50 -F src/resolve.c 9079da7d59aed2bb14ec8315bc7f720dd85b5b65 +F src/resolve.c 10a1b332e3eb36e5d561085e18c58a8578cd7d73 F src/rowset.c 64655f1a627c9c212d9ab497899e7424a34222e0 F src/select.c 01540bcd3df3c8f1187158e77986028b1c667258 -F src/shell.c 7c41bfcd9e5bf9d96b9215f79b03a5b2b44a3bca -F src/sqlite.h.in ba60664eedbe33fb5f68f17b4cbbcd59af8fc6ad +F src/shell.c aca9d94653decd4496846dee0c7ba83eaf96a46d +F src/sqlite.h.in eddda5f1967e84336e11f3a5c6fd3be3337d66c1 F src/sqlite3.rc fea433eb0a59f4c9393c8e6d76a6e2596b1fe0c0 -F src/sqlite3ext.h 7183ab832e23db0f934494f16928da127a571d75 -F src/sqliteInt.h a0a6b155f5e9748dfb5876d892e03a07eb056d7b +F src/sqlite3ext.h d936f797812c28b81b26ed18345baf8db28a21a5 +F src/sqliteInt.h a9f727c0d568f64f06ae430e55a074d8dd1ccde4 F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d F src/status.c bedc37ec1a6bb9399944024d63f4c769971955a9 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e F src/tclsqlite.c a15550a334ca07ac2bc5d32c5f97e3d61be886e8 -F src/test1.c ff3e68eedfbd858c9b89cf03e3db233cd29be1d0 -F src/test2.c 4178056dd1e7d70f954ad8a1e3edb71a2a784daf -F src/test3.c 3c3c2407fa6ec7a19e24ae23f7cb439d0275a60d -F src/test4.c bf9fa9bece01de08e6f5e02314e4af5c13590dfa +F src/test1.c 6784fdacb35c33ba564ef749b62c4718fe515484 +F src/test2.c 29e7154112f7448d64204e8d31179cf497ecf425 +F src/test3.c 96aed72a8e1d542fed127e3e8350ae357712fa82 +F src/test4.c cea2c55110241e4674e66d476d29c914627999f5 F src/test5.c a6d1ac55ac054d0b2b8f37b5e655b6c92645a013 F src/test6.c a437f76f9874d2563352a7e6cd0d43217663c220 -F src/test7.c 2e0781754905c8adc3268d8f0967e7633af58843 +F src/test7.c f4b894b7931f8cf9f5cbf37cfa0727703f526a40 F src/test8.c 58ea1d9698f3947e4662107ef98f429e84ae20e0 F src/test9.c bea1e8cf52aa93695487badedd6e1886c321ea60 F src/test_async.c 0612a752896fad42d55c3999a5122af10dcf22ad -F src/test_autoext.c 30e7bd98ab6d70a62bb9ba572e4c7df347fe645e +F src/test_autoext.c 5c95b5d435eaa09d6c0e7d90371c5ca8cd567701 F src/test_backup.c c129c91127e9b46e335715ae2e75756e25ba27de F src/test_btree.c 5b89601dcb42a33ba8b820a6b763cc9cb48bac16 -F src/test_config.c 8437cba146aa12c2fddfa5d1a73eb4f5fe0ee8e6 +F src/test_config.c 6b614c603cb4db1c996f1b192ca0a46ef0d152cd F src/test_demovfs.c 20a4975127993f4959890016ae9ce5535a880094 F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc F src/test_fs.c 8f786bfd0ad48030cf2a06fb1f050e9c60a150d7 @@ -235,13 +242,13 @@ F src/test_rtree.c aba603c949766c4193f1068b91c787f57274e0d9 F src/test_schema.c 8c06ef9ddb240c7a0fcd31bc221a6a2aade58bf0 F src/test_server.c 2f99eb2837dfa06a4aacf24af24c6affdf66a84f F src/test_spellfix.c 56dfa6d583ac34f61af0834d7b58d674e7e18e13 -F src/test_sqllog.c bc50e5afeb7fb50e77b4594e42302df9d05446aa +F src/test_sqllog.c c1c1bbedbcaf82b93d83e4f9dd990e62476a680e F src/test_stat.c d1569c7a4839f13e80187e2c26b2ab4da2d03935 F src/test_superlock.c 2b97936ca127d13962c3605dbc9a4ef269c424cd -F src/test_syscall.c a992d8c80ea91fbf21fb2dd570db40e77dd7e6ae +F src/test_syscall.c 7e8293e4e6971b0f44c7f7f37b1315a8cc9f6018 F src/test_tclvar.c f4dc67d5f780707210d6bb0eb6016a431c04c7fa F src/test_thread.c e286f2173563f2a1747c24bcda6b9d030bf4f4e4 -F src/test_vfs.c fb16b2d9938cf0c1afc5a423b55b952fcc024275 +F src/test_vfs.c 8e6087a8b3dcc260282074b0efba14b76311120c F src/test_vfstrace.c 34b544e80ba7fb77be15395a609c669df2e660a2 F src/test_wholenumber.c 3d2b9ed1505c40ad5c5ca2ad16ae7a289d6cc251 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 @@ -251,20 +258,20 @@ F src/update.c beef58f5fd66153ac9cdf6e9f6551f09ee68976c F src/utf.c 8d819e2e5104a430fc2005f018db14347c95a38f F src/util.c f566b5138099a2df8533b190d0dcc74b7dfbe0c9 F src/vacuum.c 2727bdd08847fcb6b2d2da6d14f018910e8645d3 -F src/vdbe.c fee31825a8e287bb6ed2dacc33fb276c5e2ab7a5 +F src/vdbe.c 349798f630ce49c2e21a6c30863f195c484cfec5 F src/vdbe.h 1223e2548e0970cf96f573ff6b99f804a36ad683 -F src/vdbeInt.h bd76014fa5051b9f33223e4cc6a76d00ce031944 -F src/vdbeapi.c 8245e8c2cdcf105871a4ccae365d3bd29bfca6d1 +F src/vdbeInt.h a6b7a1fbb2b335fd8c3b4b8a696b1ba28eae2191 +F src/vdbeapi.c 5899f359cb51c6949aeff50a806275c94fe73fce F src/vdbeaux.c 426263e901f19d8fe6bc7124ee5dafc78a2feac3 -F src/vdbeblob.c 11248c6362389569764682eb0f59ce910f3cc381 +F src/vdbeblob.c 1268e0bcb8e21fa32520b0fc376e1bcdfaa0c642 F src/vdbemem.c 833005f1cbbf447289f1973dba2a0c2228c7b8ab F src/vdbesort.c 4fad64071ae120c25f39dcac572d716b9cadeb7f -F src/vdbetrace.c 8bd5da325fc90f28464335e4cc4ad1407fe30835 +F src/vdbetrace.c 3ad1b4e92b60c082a02ac563da4a2735cc7d297c F src/vtab.c b05e5f1f4902461ba9f5fc49bb7eb7c3a0741a83 -F src/wal.c f5c7b5027d0ed0e9bc9afeb4a3a8dfea762ec7d2 -F src/wal.h 29c197540b19044e6cd73487017e5e47a1d3dac6 +F src/wal.c 436bfceb141b9423c45119e68e444358ee0ed35d +F src/wal.h a4d3da523d55a226a0b28e9058ef88d0a8051887 F src/walker.c 3d75ba73de15e0f8cd0737643badbeb0e002f07b -F src/where.c 4ad2329c439a30ddb915a780f6f80bdffafe3a64 +F src/where.c d54e63087b52c309550aa2defdb20ef27add9f9a F test/8_3_names.test 631ea964a3edb091cf73c3b540f6bcfdb36ce823 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/aggnested.test 45c0201e28045ad38a530b5a144b73cd4aa2cfd6 @@ -326,6 +333,7 @@ F test/boundary3.tcl 8901d6a503d0bf64251dd81cc74e5ad3add4b119 F test/boundary3.test 56ef82096b4329aca2be74fa1e2b0f762ea0eb45 F test/boundary4.tcl 0bb4b1a94f4fc5ae59b79b9a2b7a140c405e2983 F test/boundary4.test 89e02fa66397b8a325d5eb102b5806f961f8ec4b +F test/btreefault.test 06899a377f31a8c1a3048ec69831522d4e5c6045 F test/busy.test 76b4887f8b9160ba903c1ac22e8ff406ad6ae2f0 F test/cache.test 13bc046b26210471ca6f2889aceb1ea52dc717de F test/capi2.test e8b18cc61090b6e5e388f54d6b125d711d1b265a @@ -381,7 +389,7 @@ F test/cse.test 277350a26264495e86b1785f34d2d0c8600e021c F test/ctime.test 7bd009071e242aac4f18521581536b652b789a47 F test/date.test f3228180c87bbe5d39c9397bf001c0095c3821b9 F test/dbstatus.test 207e5b63fcb7b9c3bb8e1fdf38ebd4654ad0e54b -F test/dbstatus2.test bf7396af964b89e39435babbcdf296ae8fc5f10a +F test/dbstatus2.test 10418e62b3db5dca070f0c3eef3ea13946f339c2 F test/default.test 6faf23ccb300114924353007795aa9a8ec0aa9dc F test/delete.test a065b05d2ebf60fd16639c579a4adfb7c381c701 F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa @@ -415,7 +423,7 @@ F test/eqp.test 46aa946dd55c90635327898275d3e533d23a9845 F test/errmsg.test 050717f1c6a5685de9c79f5f9f6b83d7c592f73a F test/eval.test bc269c365ba877554948441e91ad5373f9f91be3 F test/exclusive.test a1b324cb21834a490cd052d409d34789cfef57cb -F test/exclusive2.test 372be98f6de44dd78734e364b7b626ea211761a6 +F test/exclusive2.test 881193eccec225cfed9d7f744b65e57f26adee25 F test/exec.test e949714dc127eaa5ecc7d723efec1ec27118fdd7 F test/exists.test 8f7b27b61c2fbe5822f0a1f899c715d14e416e30 F test/expr.test 67c9fd6f8f829e239dc8b0f4a08a73c08b09196d @@ -483,7 +491,7 @@ F test/fts3an.test a49ccadc07a2f7d646ec1b81bc09da2d85a85b18 F test/fts3ao.test e7b80272efcced57d1d087a9da5c690dd7c21fd9 F test/fts3atoken.test fb398ab50aa232489e2a17f9b29d7ad3a3885f36 F test/fts3auto.test 74315a7377403a57ba82a652a33704197fe1e4be -F test/fts3aux1.test 0b02743955d56fc0d4d66236a26177bd1b726de0 +F test/fts3aux1.test 03cec2dea379931c219dd4406817485caa69d1d8 F test/fts3b.test e93bbb653e52afde110ad53bbd793f14fe7a8984 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958 F test/fts3comp1.test a0f5b16a2df44dd0b15751787130af2183167c0c @@ -511,6 +519,8 @@ F test/fts3rnd.test 1320d8826a845e38a96e769562bf83d7a92a15d0 F test/fts3shared.test 8bb266521d7c5495c0ae522bb4d376ad5387d4a2 F test/fts3snippet.test 8e956051221a34c7daeb504f023cb54d5fa5a8b2 F test/fts3sort.test 95be0b19d7e41c44b29014f13ea8bddd495fd659 +F test/fts3tok1.test 05ff5c57098282bacba7192507702b17f4061295 +F test/fts3tok_err.test 41e5413139c2ef536ffadfcd1584ee50b1665ec0 F test/fts4aa.test 95f448fb02c4a976968b08d1b4ce134e720946ae F test/fts4check.test 66fa274cab2b615f2fb338b257713aba8fad88a8 F test/fts4content.test 6efc53b4fd03cab167e6998d2b0b7d4b7d419ee6 @@ -520,7 +530,7 @@ F test/fts4merge2.test 5faa558d1b672f82b847d2a337465fa745e46891 F test/fts4merge3.test aab02a09f50fe6baaddc2e159c3eabc116d45fc7 F test/fts4unicode.test 25ccad45896f8e50f6a694cff738a35f798cdb40 F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d -F test/func.test b058483c17952eff7797b837bbb61e27e6b05606 +F test/func.test b0fc34fdc36897769651975a2b0a606312753643 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f F test/func3.test 001021e5b88bd02a3b365a5c5fd8f6f49d39744a F test/fuzz-oss1.test 4912e528ec9cf2f42134456933659d371c9e0d74 @@ -538,7 +548,7 @@ F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75 F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0 F test/in4.test 64f3cc1acde1b9161ccdd8e5bde3daefdb5b2617 F test/in5.test 99f9a40af01711b06d2d614ecfe96129f334fba3 -F test/incrblob.test e7ef2a6094d9b5eb7284c21af2c07644eefffe7d +F test/incrblob.test e81846d214f3637622620fbde7cd526781cfe328 F test/incrblob2.test edc3a96e557bd61fb39acc8d2edd43371fbbaa19 F test/incrblob3.test aedbb35ea1b6450c33b98f2b6ed98e5020be8dc7 F test/incrblob4.test 09be37d3dd996a31ea6993bba7837ece549414a8 @@ -590,7 +600,7 @@ F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200 F test/like.test 0e5412f4dac4a849f613e1ef8b529d56a6e31d08 F test/like2.test 3b2ee13149ba4a8a60b59756f4e5d345573852da F test/limit.test cc0ab63385239b63c72452b0e93700bf5e8f0b99 -F test/loadext.test 2b5e249c51c986a5aff1f0950cf7ba30976c8f22 +F test/loadext.test 92e6dfefd1229c3ef4aaabd87419efd8fa57a7a5 F test/loadext2.test 0bcaeb4d81cd5b6e883fdfea3c1bdbe1f173cbca F test/lock.test 87af515b0c4cf928576d0f89946d67d7c265dfb4 F test/lock2.test 5242d8ac4e2d59c403aebff606af449b455aceff @@ -623,7 +633,7 @@ F test/mallocH.test 79b65aed612c9b3ed2dcdaa727c85895fd1bfbdb F test/mallocI.test a88c2b9627c8506bf4703d8397420043a786cdb6 F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e F test/mallocK.test d79968641d1b70d88f6c01bdb9a7eb4a55582cc9 -F test/malloc_common.tcl 2930895b0962823ec679853e67e58dd6d8198b3c +F test/malloc_common.tcl 9a98856549bfb3fab205edbc1317216edc52e70d F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f F test/memdb.test 708a028d6d373e5b3842e4bdc8ba80998c9a4da6 @@ -642,6 +652,8 @@ F test/misc5.test 528468b26d03303b1f047146e5eefc941b9069f5 F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91 F test/misc7.test dd82ec9250b89178b96cd28b2aca70639d21e5b3 F test/misuse.test ba4fb5d1a6101d1c171ea38b3c613d0661c83054 +F test/mmap1.test 0b5802cf64acaa509ea889b3c708196cc6eb9d31 +F test/mmap2.test a5ba639f90b5fc487400a49e158e14e465943e98 F test/multiplex.test e08cc7177bd6d85990ee1d71100bb6c684c02256 F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a F test/multiplex3.test d228f59eac91839a977eac19f21d053f03e4d101 @@ -660,17 +672,17 @@ F test/orderby2.test bc11009f7cd99d96b1b11e57b199b00633eb5b04 F test/orderby3.test 8619d06a3debdcd80a27c0fdea5c40b468854b99 F test/orderby4.test 4d39bfbaaa3ae64d026ca2ff166353d2edca4ba4 F test/oserror.test 50417780d0e0d7cd23cf12a8277bb44024765df3 -F test/pager1.test 31c04bec797dda1bde337810b52efa08d1f1f08e -F test/pager2.test 745b911dde3d1f24ae0870bd433dfa83d7c658c1 +F test/pager1.test 30e63afd425fea12285e9ec5fa1fd000808031f1 +F test/pager2.test 67b8f40ae98112bcdba1f2b2d03ea83266418c71 F test/pager3.test 3856d9c80839be0668efee1b74811b1b7f7fc95f -F test/pagerfault.test 452f2cc23e3bfcfa935f4442aec1da4fe1dc0442 +F test/pagerfault.test 8483e65d33d5636e5b7656204bb274d826e728d9 F test/pagerfault2.test 1f79ea40d1133b2683a2f811b00f2399f7ec2401 F test/pagerfault3.test f16e2efcb5fc9996d1356f7cbc44c998318ae1d7 -F test/pageropt.test 9191867ed19a2b3db6c42d1b36b6fbc657cd1ab0 +F test/pageropt.test 6b8f6a123a5572c195ad4ae40f2987007923bbd6 F test/pagesize.test 1dd51367e752e742f58e861e65ed7390603827a0 F test/pcache.test 065aa286e722ab24f2e51792c1f093bf60656b16 F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025 -F test/permutations.test 379cfbb9a5eea2499d05008c04d18ddb4f2c01a9 +F test/permutations.test a19a70a80836b5e183e46f4043fb7626446ab5e0 F test/pragma.test 60d29cd3d8098a2c20bf4c072810f99e3bf2757a F test/pragma2.test 3a55f82b954242c642f8342b17dffc8b47472947 F test/printf.test ec9870c4dce8686a37818e0bf1aba6e6a1863552 @@ -688,6 +700,7 @@ F test/regexp1.test 5cbb6e7122ca51260d71079cf9245b63b8f64e1a F test/reindex.test 44edd3966b474468b823d481eafef0c305022254 F test/releasetest.mk 2eced2f9ae701fd0a29e714a241760503ccba25a F test/releasetest.tcl 06d289d8255794073a58d2850742f627924545ce +F test/resolver01.test d1b487fc567bdb70b5dd09ccaaa877ddc61a233e F test/rollback.test a1b4784b864331eae8b2a98c189efa2a8b11ff07 F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81 F test/rowid.test f777404492adb0e00868fd706a3721328fd3af48 @@ -744,7 +757,7 @@ F test/softheap1.test c16709a16ad79fa43b32929b2e623d1d117ccf53 F test/sort.test 0e4456e729e5a92a625907c63dcdedfbe72c5dc5 F test/speed1.test f2974a91d79f58507ada01864c0e323093065452 F test/speed1p.explain d841e650a04728b39e6740296b852dccdca9b2cb -F test/speed1p.test c4a469f29f135f4d76c55b1f2a52f36e209466cc +F test/speed1p.test b180e98609c7677382cf618c0ec9b69f789033a8 F test/speed2.test 53177056baf6556dcbdcf032bbdfc41c1aa74ded F test/speed3.test d32043614c08c53eafdc80f33191d5bd9b920523 F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 @@ -760,15 +773,15 @@ F test/subselect.test d24fd8757daf97dafd2e889c73ea4c4272dcf4e4 F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a F test/superlock.test 1cde669f68d2dd37d6c9bd35eee1d95491ae3fc2 F test/sync.test a34cd43e98b7fb84eabbf38f7ed8f7349b3f3d85 -F test/syscall.test bea9bf329bff733c791310244617c2a76974e64a -F test/sysfault.test c79441d88d23696fbec7b147dba98d42a04f523f +F test/syscall.test a653783d985108c4912cc64d341ffbbb55ad2806 +F test/sysfault.test fa776e60bf46bdd3ae69f0b73e46ee3977a58ae6 F test/table.test a59d985ca366e39b17b175f387f9d5db5a18d4e2 F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126 F test/tclsqlite.test a7308276aad2e6c0bfb5b0414424dd0d9cc0cad7 F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30 F test/temptrigger.test 26670ed7a39cf2296a7f0a9e0a1d7bdb7abe936d -F test/tester.tcl 4201c6efa80693e5414d4f241bb0f325a124478f +F test/tester.tcl c6dc3ea5b906b3665ab52c82c72237268adf506d F test/thread001.test 9f22fd3525a307ff42a326b6bc7b0465be1745a5 F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7 @@ -858,7 +871,7 @@ F test/tkt2686.test 6ee01c9b9e9c48f6d3a1fdd553b1cc4258f903d6 F test/tkt2767.test 569000d842678f9cf2db7e0d1b27cbc9011381b0 F test/tkt2817.test f31839e01f4243cff7399ef654d3af3558cb8d8d F test/tkt2820.test 39940276b3436d125deb7d8ebeee053e4cf13213 -F test/tkt2822.test f391776423a7c0d0949edfce375708bfb0f3141e +F test/tkt2822.test c3589494fba643e039bcf0bfde7554ff6028406d F test/tkt2832.test a9b0b74a02dca166a04d9e37739c414b10929caa F test/tkt2854.test e432965db29e27e16f539b2ba7f502eb2ccc49af F test/tkt2920.test a8737380e4ae6424e00c0273dc12775704efbebf @@ -965,11 +978,11 @@ F test/vtabF.test fd5ad376f5a34fe0891df1f3cddb4fe7c3eb077e F test/vtab_alter.test 9e374885248f69e251bdaacf480b04a197f125e5 F test/vtab_err.test 0d4d8eb4def1d053ac7c5050df3024fd47a3fbd8 F test/vtab_shared.test 82f463886e18d7f8395a4b6167c91815efe54839 -F test/wal.test a040047d7f2b9f34bc4d597964e5e7c09609c635 +F test/wal.test 0b4837cd5e9283c116d30810a6859bed7425a95e F test/wal2.test d4b470f13c87f6d8268b004380afa04c3c67cb90 F test/wal3.test b22eb662bcbc148c5f6d956eaf94b047f7afe9c0 F test/wal4.test 4744e155cd6299c6bd99d3eab1c82f77db9cdb3c -F test/wal5.test f58ed4b8b542f71c7441da12fbd769d99b362437 +F test/wal5.test 8f888b50f66b78821e61ed0e233ded5de378224b F test/wal6.test 2e3bc767d9c2ce35c47106148d43fcbd072a93b3 F test/wal7.test 2ae8f427d240099cc4b2dfef63cff44e2a68a1bd F test/wal8.test b3ee739fe8f7586aaebdc2367f477ebcf3e3b034 @@ -981,7 +994,7 @@ F test/walcksum.test f5447800a157c9e2234fbb8e80243f0813941bde F test/walcrash.test 4457436593be8c136f9148487c7dccd5e9013af2 F test/walcrash2.test 019d60b89d96c1937adb2b30b850ac7e86e5a142 F test/walcrash3.test 595e44c6197f0d0aa509fc135be2fd0209d11a2c -F test/walfault.test 97394d8de82a99f7abf1c12ed229640607fd0ad2 +F test/walfault.test 54ad6e849c727f4da463964b9eb8c8e8e155cf82 F test/walhook.test ed00a40ba7255da22d6b66433ab61fab16a63483 F test/walmode.test 4022fe03ae6e830583672caa101f046438a0473c F test/walnoshm.test 84ca10c544632a756467336b7c3b864d493ee496 @@ -1007,7 +1020,7 @@ F test/whereD.test 3f3ee93825c94804f1fc91eef2de0d365981759a F test/whereE.test 7bd34945797efef15819368479bacc34215e4e1d F test/whereF.test a0e296643cabe5278379bc1a0aa158cf3c54a1c9 F test/wherelimit.test 5e9fd41e79bb2b2d588ed999d641d9c965619b31 -F test/win32lock.test b2a539e85ae6b2d78475e016a9636b4451dc7fb9 +F test/win32lock.test 7a6bd73a5dcdee39b5bb93e92395e1773a194361 F test/zeroblob.test caaecfb4f908f7bc086ed238668049f96774d688 F test/zerodamage.test e7f77fded01dfcdf92ac2c5400f1e35d7a21463c F tool/build-all-msvc.bat 74fb6e5cca66ebdb6c9bbafb2f8b802f08146d38 x @@ -1025,7 +1038,7 @@ F tool/mkkeywordhash.c bb52064aa614e1426445e4b2b9b00eeecd23cc79 F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 8bce31074e4cbe631bb7676526a048335f4c9f02 -F tool/mksqlite3c.tcl 521b39c2d2987c4257919b97a3cfb3bd4a394bf1 +F tool/mksqlite3c.tcl 3e55715d165688d969392d792c81f79552049add F tool/mksqlite3h.tcl 2d0f1b3768f8d000b7881217d5fd4c776eb27467 F tool/mksqlite3internalh.tcl 3dca7bb5374cee003379b8cbac73714f610ef795 F tool/mkvsix.tcl 0be7f7a591f1e83f9199cb82911b66668ca484c9 @@ -1054,7 +1067,7 @@ F tool/vdbe-compress.tcl f12c884766bd14277f4fcedcae07078011717381 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac -P f1eed92b7b0ef4ee22a11d8bb4f9a572b56ce019 cbea02d93865ce0e06789db95fd9168ebac970c7 -R 0efc127a79bcb850db00f0f48c643639 +P 67b3c0efa7d5e0cb7cc0fc7606ab3f26ea5419fd 1a1cf5aa86734c832d845e07780262a178188d56 +R 732bd62d8f3cf92b7ff68a9a14e06f6a U drh -Z a385c575da3bc80f399700b4e6cb7b5b +Z d4e35c064e319291a70a72e245362c9b diff --git a/manifest.uuid b/manifest.uuid index 4581bba7a0..48e7eca177 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -67b3c0efa7d5e0cb7cc0fc7606ab3f26ea5419fd \ No newline at end of file +6994826c0784280f2e9728dfa4185848846d03df \ No newline at end of file diff --git a/mptest/config01.test b/mptest/config01.test new file mode 100644 index 0000000000..683ee91143 --- /dev/null +++ b/mptest/config01.test @@ -0,0 +1,46 @@ +/* +** Configure five tasks in different ways, then run tests. +*/ +--if vfsname() GLOB 'unix' +PRAGMA page_size=8192; +--task 1 + PRAGMA journal_mode=PERSIST; + PRAGMA mmap_size=0; +--end +--task 2 + PRAGMA journal_mode=TRUNCATE; + PRAGMA mmap_size=28672; +--end +--task 3 + PRAGMA journal_mode=MEMORY; +--end +--task 4 + PRAGMA journal_mode=OFF; +--end +--task 4 + PRAGMA mmap_size(268435456); +--end +--source multiwrite01.test +--wait all +PRAGMA page_size=16384; +VACUUM; +CREATE TABLE pgsz(taskid, sz INTEGER); +--task 1 + INSERT INTO pgsz VALUES(1, eval('PRAGMA page_size')); +--end +--task 2 + INSERT INTO pgsz VALUES(2, eval('PRAGMA page_size')); +--end +--task 3 + INSERT INTO pgsz VALUES(3, eval('PRAGMA page_size')); +--end +--task 4 + INSERT INTO pgsz VALUES(4, eval('PRAGMA page_size')); +--end +--task 5 + INSERT INTO pgsz VALUES(5, eval('PRAGMA page_size')); +--end +--source multiwrite01.test +--wait all +SELECT sz FROM pgsz; +--match 16384 16384 16384 16384 16384 diff --git a/mptest/config02.test b/mptest/config02.test new file mode 100644 index 0000000000..7d4b27898b --- /dev/null +++ b/mptest/config02.test @@ -0,0 +1,123 @@ +/* +** Configure five tasks in different ways, then run tests. +*/ +PRAGMA page_size=512; +--task 1 + PRAGMA mmap_size=0; +--end +--task 2 + PRAGMA mmap_size=28672; +--end +--task 3 + PRAGMA mmap_size=8192; +--end +--task 4 + PRAGMA mmap_size=65536; +--end +--task 5 + PRAGMA mmap_size=268435456; +--end +--source multiwrite01.test +--source crash02.subtest +PRAGMA page_size=1024; +VACUUM; +CREATE TABLE pgsz(taskid, sz INTEGER); +--task 1 + INSERT INTO pgsz VALUES(1, eval('PRAGMA page_size')); +--end +--task 2 + INSERT INTO pgsz VALUES(2, eval('PRAGMA page_size')); +--end +--task 3 + INSERT INTO pgsz VALUES(3, eval('PRAGMA page_size')); +--end +--task 4 + INSERT INTO pgsz VALUES(4, eval('PRAGMA page_size')); +--end +--task 5 + INSERT INTO pgsz VALUES(5, eval('PRAGMA page_size')); +--end +--source multiwrite01.test +--source crash02.subtest +--wait all +SELECT sz FROM pgsz; +--match 1024 1024 1024 1024 1024 +PRAGMA page_size=2048; +VACUUM; +DELETE FROM pgsz; +--task 1 + INSERT INTO pgsz VALUES(1, eval('PRAGMA page_size')); +--end +--task 2 + INSERT INTO pgsz VALUES(2, eval('PRAGMA page_size')); +--end +--task 3 + INSERT INTO pgsz VALUES(3, eval('PRAGMA page_size')); +--end +--task 4 + INSERT INTO pgsz VALUES(4, eval('PRAGMA page_size')); +--end +--task 5 + INSERT INTO pgsz VALUES(5, eval('PRAGMA page_size')); +--end +--source multiwrite01.test +--source crash02.subtest +--wait all +SELECT sz FROM pgsz; +--match 2048 2048 2048 2048 2048 +PRAGMA page_size=8192; +VACUUM; +DELETE FROM pgsz; +--task 1 + INSERT INTO pgsz VALUES(1, eval('PRAGMA page_size')); +--end +--task 2 + INSERT INTO pgsz VALUES(2, eval('PRAGMA page_size')); +--end +--task 3 + INSERT INTO pgsz VALUES(3, eval('PRAGMA page_size')); +--end +--task 4 + INSERT INTO pgsz VALUES(4, eval('PRAGMA page_size')); +--end +--task 5 + INSERT INTO pgsz VALUES(5, eval('PRAGMA page_size')); +--end +--source multiwrite01.test +--source crash02.subtest +--wait all +SELECT sz FROM pgsz; +--match 8192 8192 8192 8192 8192 +PRAGMA page_size=16384; +VACUUM; +DELETE FROM pgsz; +--task 1 + INSERT INTO pgsz VALUES(1, eval('PRAGMA page_size')); +--end +--task 2 + INSERT INTO pgsz VALUES(2, eval('PRAGMA page_size')); +--end +--task 3 + INSERT INTO pgsz VALUES(3, eval('PRAGMA page_size')); +--end +--task 4 + INSERT INTO pgsz VALUES(4, eval('PRAGMA page_size')); +--end +--task 5 + INSERT INTO pgsz VALUES(5, eval('PRAGMA page_size')); +--end +--source multiwrite01.test +--source crash02.subtest +--wait all +SELECT sz FROM pgsz; +--match 16384 16384 16384 16384 16384 +PRAGMA auto_vacuum=FULL; +VACUUM; +--source multiwrite01.test +--source crash02.subtest +--wait all +PRAGMA auto_vacuum=FULL; +PRAGMA page_size=512; +VACUUM; +--source multiwrite01.test +--source crash02.subtest diff --git a/mptest/crash01.test b/mptest/crash01.test new file mode 100644 index 0000000000..d094d72f29 --- /dev/null +++ b/mptest/crash01.test @@ -0,0 +1,102 @@ +/* Test cases involving incomplete transactions that must be rolled back. +*/ +--task 1 + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t1 VALUES(1, randomblob(2000)); + INSERT INTO t1 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t1 SELECT a+2, randomblob(1500) FROM t1; + INSERT INTO t1 SELECT a+4, randomblob(1500) FROM t1; + INSERT INTO t1 SELECT a+8, randomblob(1500) FROM t1; + --sleep 1 + INSERT INTO t1 SELECT a+16, randomblob(1500) FROM t1; + --sleep 1 + INSERT INTO t1 SELECT a+32, randomblob(1500) FROM t1; + SELECT count(*) FROM t1; + --match 64 + SELECT avg(length(b)) FROM t1; + --match 1500.0 + --sleep 2 + UPDATE t1 SET b='x'||a||'y'; + SELECT total(length(b)) FROM t1; + --match 247 + SELECT a FROM t1 WHERE b='x17y'; + --match 17 + CREATE INDEX t1b ON t1(b); + SELECT a FROM t1 WHERE b='x17y'; + --match 17 + SELECT a FROM t1 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end +--wait 1 +--task 2 + CREATE TABLE t2(a INTEGER PRIMARY KEY, b); + INSERT INTO t2 SELECT a, b FROM t1; + UPDATE t1 SET b='x'||a||'y'; + SELECT total(length(b)) FROM t2; + --match 247 + SELECT a FROM t2 WHERE b='x17y'; + --match 17 + CREATE INDEX t2b ON t2(b); + SELECT a FROM t2 WHERE b='x17y'; + --match 17 + SELECT a FROM t2 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end +--task 3 + CREATE TABLE t3(a INTEGER PRIMARY KEY, b); + INSERT INTO t3 SELECT a, b FROM t1; + UPDATE t1 SET b='x'||a||'y'; + SELECT total(length(b)) FROM t3; + --match 247 + SELECT a FROM t3 WHERE b='x17y'; + --match 17 + CREATE INDEX t3b ON t3(b); + SELECT a FROM t3 WHERE b='x17y'; + --match 17 + SELECT a FROM t3 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end +--task 4 + CREATE TABLE t4(a INTEGER PRIMARY KEY, b); + INSERT INTO t4 SELECT a, b FROM t1; + UPDATE t1 SET b='x'||a||'y'; + SELECT total(length(b)) FROM t4; + --match 247 + SELECT a FROM t4 WHERE b='x17y'; + --match 17 + CREATE INDEX t4b ON t4(b); + SELECT a FROM t4 WHERE b='x17y'; + --match 17 + SELECT a FROM t4 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end +--task 5 + CREATE TABLE t5(a INTEGER PRIMARY KEY, b); + INSERT INTO t5 SELECT a, b FROM t1; + UPDATE t1 SET b='x'||a||'y'; + SELECT total(length(b)) FROM t5; + --match 247 + SELECT a FROM t5 WHERE b='x17y'; + --match 17 + CREATE INDEX t5b ON t5(b); + SELECT a FROM t5 WHERE b='x17y'; + --match 17 + SELECT a FROM t5 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + +--wait all +/* After the database file has been set up, run the crash2 subscript +** multiple times. */ +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest diff --git a/mptest/crash02.subtest b/mptest/crash02.subtest new file mode 100644 index 0000000000..86f64ddbe7 --- /dev/null +++ b/mptest/crash02.subtest @@ -0,0 +1,53 @@ +/* +** This script is called from crash01.test and config02.test and perhaps other +** script. After the database file has been set up, make a big rollback +** journal in client 1, then crash client 1. +** Then in the other clients, do an integrity check. +*/ +--task 1 leave-hot-journal + --sleep 5 + --finish + PRAGMA cache_size=10; + BEGIN; + UPDATE t1 SET b=randomblob(20000); + UPDATE t2 SET b=randomblob(20000); + UPDATE t3 SET b=randomblob(20000); + UPDATE t4 SET b=randomblob(20000); + UPDATE t5 SET b=randomblob(20000); + UPDATE t1 SET b=NULL; + UPDATE t2 SET b=NULL; + UPDATE t3 SET b=NULL; + UPDATE t4 SET b=NULL; + UPDATE t5 SET b=NULL; + --print Task one crashing an incomplete transaction + --exit 1 +--end +--task 2 integrity_check-2 + SELECT count(*) FROM t1; + --match 64 + --sleep 100 + PRAGMA integrity_check(10); + --match ok +--end +--task 3 integrity_check-3 + SELECT count(*) FROM t1; + --match 64 + --sleep 100 + PRAGMA integrity_check(10); + --match ok +--end +--task 4 integrity_check-4 + SELECT count(*) FROM t1; + --match 64 + --sleep 100 + PRAGMA integrity_check(10); + --match ok +--end +--task 5 integrity_check-5 + SELECT count(*) FROM t1; + --match 64 + --sleep 100 + PRAGMA integrity_check(10); + --match ok +--end +--wait all diff --git a/mptest/mptest.c b/mptest/mptest.c new file mode 100644 index 0000000000..059ae102fa --- /dev/null +++ b/mptest/mptest.c @@ -0,0 +1,1401 @@ +/* +** 2013-04-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 is a program used for testing SQLite, and specifically for testing +** the ability of independent processes to access the same SQLite database +** concurrently. +** +** Compile this program as follows: +** +** gcc -g -c -Wall sqlite3.c $(OPTS) +** gcc -g -o mptest mptest.c sqlite3.o $(LIBS) +** +** Recommended options: +** +** -DHAVE_USLEEP +** -DSQLITE_NO_SYNC +** -DSQLITE_THREADSAFE=0 +** -DSQLITE_OMIT_LOAD_EXTENSION +** +** Run like this: +** +** ./mptest $database $script +** +** where $database is the database to use for testing and $script is a +** test script. +*/ +#include "sqlite3.h" +#include +#if defined(_WIN32) +# define WIN32_LEAN_AND_MEAN +# include +#else +# include +#endif +#include +#include +#include +#include + +/* The suffix to append to the child command lines, if any */ +#if defined(_WIN32) +# define GETPID (int)GetCurrentProcessId +#else +# define GETPID getpid +#endif + +/* Mark a parameter as unused to suppress compiler warnings */ +#define UNUSED_PARAMETER(x) (void)x + +/* Global data +*/ +static struct Global { + char *argv0; /* Name of the executable */ + const char *zVfs; /* Name of VFS to use. Often NULL meaning "default" */ + char *zDbFile; /* Name of the database */ + sqlite3 *db; /* Open connection to database */ + char *zErrLog; /* Filename for error log */ + FILE *pErrLog; /* Where to write errors */ + char *zLog; /* Name of output log file */ + FILE *pLog; /* Where to write log messages */ + char zName[32]; /* Symbolic name of this process */ + int taskId; /* Task ID. 0 means supervisor. */ + int iTrace; /* Tracing level */ + int bSqlTrace; /* True to trace SQL commands */ + int bIgnoreSqlErrors; /* Ignore errors in SQL statements */ + int nError; /* Number of errors */ + int nTest; /* Number of --match operators */ + int iTimeout; /* Milliseconds until a busy timeout */ + int bSync; /* Call fsync() */ +} g; + +/* Default timeout */ +#define DEFAULT_TIMEOUT 10000 + +/* +** Print a message adding zPrefix[] to the beginning of every line. +*/ +static void printWithPrefix(FILE *pOut, const char *zPrefix, const char *zMsg){ + while( zMsg && zMsg[0] ){ + int i; + for(i=0; zMsg[i] && zMsg[i]!='\n' && zMsg[i]!='\r'; i++){} + fprintf(pOut, "%s%.*s\n", zPrefix, i, zMsg); + zMsg += i; + while( zMsg[0]=='\n' || zMsg[0]=='\r' ) zMsg++; + } +} + +/* +** Compare two pointers to strings, where the pointers might be NULL. +*/ +static int safe_strcmp(const char *a, const char *b){ + if( a==b ) return 0; + if( a==0 ) return -1; + if( b==0 ) return 1; + return strcmp(a,b); +} + +/* +** Return TRUE if string z[] matches glob pattern zGlob[]. +** Return FALSE if the pattern does not match. +** +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** '#' Matches any sequence of one or more digits with an +** optional + or - sign in front +*/ +int strglob(const char *zGlob, const char *z){ + int c, c2; + int invert; + int seen; + + while( (c = (*(zGlob++)))!=0 ){ + if( c=='*' ){ + while( (c=(*(zGlob++))) == '*' || c=='?' ){ + if( c=='?' && (*(z++))==0 ) return 0; + } + if( c==0 ){ + return 1; + }else if( c=='[' ){ + while( *z && strglob(zGlob-1,z) ){ + z++; + } + return (*z)!=0; + } + while( (c2 = (*(z++)))!=0 ){ + while( c2!=c ){ + c2 = *(z++); + if( c2==0 ) return 0; + } + if( strglob(zGlob,z) ) return 1; + } + return 0; + }else if( c=='?' ){ + if( (*(z++))==0 ) return 0; + }else if( c=='[' ){ + int prior_c = 0; + seen = 0; + invert = 0; + c = *(z++); + if( c==0 ) return 0; + c2 = *(zGlob++); + if( c2=='^' ){ + invert = 1; + c2 = *(zGlob++); + } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = *(zGlob++); + } + while( c2 && c2!=']' ){ + if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ + c2 = *(zGlob++); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else{ + if( c==c2 ){ + seen = 1; + } + prior_c = c2; + } + c2 = *(zGlob++); + } + if( c2==0 || (seen ^ invert)==0 ) return 0; + }else if( c=='#' ){ + if( (z[0]=='-' || z[0]=='+') && isdigit(z[1]) ) z++; + if( !isdigit(z[0]) ) return 0; + z++; + while( isdigit(z[0]) ){ z++; } + }else{ + if( c!=(*(z++)) ) return 0; + } + } + return *z==0; +} + +/* +** Close output stream pOut if it is not stdout or stderr +*/ +static void maybeClose(FILE *pOut){ + if( pOut!=stdout && pOut!=stderr ) fclose(pOut); +} + +/* +** Print an error message +*/ +static void errorMessage(const char *zFormat, ...){ + va_list ap; + char *zMsg; + char zPrefix[30]; + va_start(ap, zFormat); + zMsg = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:ERROR: ", g.zName); + if( g.pLog ){ + printWithPrefix(g.pLog, zPrefix, zMsg); + fflush(g.pLog); + } + if( g.pErrLog && safe_strcmp(g.zErrLog,g.zLog) ){ + printWithPrefix(g.pErrLog, zPrefix, zMsg); + fflush(g.pErrLog); + } + sqlite3_free(zMsg); + g.nError++; +} + +/* Forward declaration */ +static int trySql(const char*, ...); + +/* +** Print an error message and then quit. +*/ +static void fatalError(const char *zFormat, ...){ + va_list ap; + char *zMsg; + char zPrefix[30]; + va_start(ap, zFormat); + zMsg = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:FATAL: ", g.zName); + if( g.pLog ){ + printWithPrefix(g.pLog, zPrefix, zMsg); + fflush(g.pLog); + maybeClose(g.pLog); + } + if( g.pErrLog && safe_strcmp(g.zErrLog,g.zLog) ){ + printWithPrefix(g.pErrLog, zPrefix, zMsg); + fflush(g.pErrLog); + maybeClose(g.pErrLog); + } + sqlite3_free(zMsg); + if( g.db ){ + int nTry = 0; + g.iTimeout = 0; + while( trySql("UPDATE client SET wantHalt=1;")==SQLITE_BUSY + && (nTry++)<100 ){ + sqlite3_sleep(10); + } + } + sqlite3_close(g.db); + exit(1); +} + + +/* +** Print a log message +*/ +static void logMessage(const char *zFormat, ...){ + va_list ap; + char *zMsg; + char zPrefix[30]; + va_start(ap, zFormat); + zMsg = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s: ", g.zName); + if( g.pLog ){ + printWithPrefix(g.pLog, zPrefix, zMsg); + fflush(g.pLog); + } + sqlite3_free(zMsg); +} + +/* +** Return the length of a string omitting trailing whitespace +*/ +static int clipLength(const char *z){ + int n = (int)strlen(z); + while( n>0 && isspace(z[n-1]) ){ n--; } + return n; +} + +/* +** Auxiliary SQL function to return the name of the VFS +*/ +static void vfsNameFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + char *zVfs = 0; + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); + sqlite3_file_control(db, "main", SQLITE_FCNTL_VFSNAME, &zVfs); + if( zVfs ){ + sqlite3_result_text(context, zVfs, -1, sqlite3_free); + } +} + +/* +** Busy handler with a g.iTimeout-millisecond timeout +*/ +static int busyHandler(void *pCD, int count){ + UNUSED_PARAMETER(pCD); + if( count*10>g.iTimeout ){ + if( g.iTimeout>0 ) errorMessage("timeout after %dms", g.iTimeout); + return 0; + } + sqlite3_sleep(10); + return 1; +} + +/* +** SQL Trace callback +*/ +static void sqlTraceCallback(void *NotUsed1, const char *zSql){ + UNUSED_PARAMETER(NotUsed1); + logMessage("[%.*s]", clipLength(zSql), zSql); +} + +/* +** SQL error log callback +*/ +static void sqlErrorCallback(void *pArg, int iErrCode, const char *zMsg){ + UNUSED_PARAMETER(pArg); + if( iErrCode==SQLITE_ERROR && g.bIgnoreSqlErrors ) return; + if( (iErrCode&0xff)==SQLITE_SCHEMA && g.iTrace<3 ) return; + if( g.iTimeout==0 && (iErrCode&0xff)==SQLITE_BUSY && g.iTrace<3 ) return; + if( (iErrCode&0xff)==SQLITE_NOTICE ){ + logMessage("(info) %s", zMsg); + }else{ + errorMessage("(errcode=%d) %s", iErrCode, zMsg); + } +} + +/* +** Prepare an SQL statement. Issue a fatal error if unable. +*/ +static sqlite3_stmt *prepareSql(const char *zFormat, ...){ + va_list ap; + char *zSql; + int rc; + sqlite3_stmt *pStmt = 0; + va_start(ap, zFormat); + zSql = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ + sqlite3_finalize(pStmt); + fatalError("%s\n%s\n", sqlite3_errmsg(g.db), zSql); + } + sqlite3_free(zSql); + return pStmt; +} + +/* +** Run arbitrary SQL. Issue a fatal error on failure. +*/ +static void runSql(const char *zFormat, ...){ + va_list ap; + char *zSql; + int rc; + va_start(ap, zFormat); + zSql = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + rc = sqlite3_exec(g.db, zSql, 0, 0, 0); + if( rc!=SQLITE_OK ){ + fatalError("%s\n%s\n", sqlite3_errmsg(g.db), zSql); + } + sqlite3_free(zSql); +} + +/* +** Try to run arbitrary SQL. Return success code. +*/ +static int trySql(const char *zFormat, ...){ + va_list ap; + char *zSql; + int rc; + va_start(ap, zFormat); + zSql = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + rc = sqlite3_exec(g.db, zSql, 0, 0, 0); + sqlite3_free(zSql); + return rc; +} + +/* Structure for holding an arbitrary length string +*/ +typedef struct String String; +struct String { + char *z; /* the string */ + int n; /* Slots of z[] used */ + int nAlloc; /* Slots of z[] allocated */ +}; + +/* Free a string */ +static void stringFree(String *p){ + if( p->z ) sqlite3_free(p->z); + memset(p, 0, sizeof(*p)); +} + +/* Append n bytes of text to a string. If n<0 append the entire string. */ +static void stringAppend(String *p, const char *z, int n){ + if( n<0 ) n = (int)strlen(z); + if( p->n+n>=p->nAlloc ){ + int nAlloc = p->nAlloc*2 + n + 100; + char *z = sqlite3_realloc(p->z, nAlloc); + if( z==0 ) fatalError("out of memory"); + p->z = z; + p->nAlloc = nAlloc; + } + memcpy(p->z+p->n, z, n); + p->n += n; + p->z[p->n] = 0; +} + +/* Reset a string to an empty string */ +static void stringReset(String *p){ + if( p->z==0 ) stringAppend(p, " ", 1); + p->n = 0; + p->z[0] = 0; +} + +/* Append a new token onto the end of the string */ +static void stringAppendTerm(String *p, const char *z){ + int i; + if( p->n ) stringAppend(p, " ", 1); + if( z==0 ){ + stringAppend(p, "nil", 3); + return; + } + for(i=0; z[i] && !isspace(z[i]); i++){} + if( i>0 && z[i]==0 ){ + stringAppend(p, z, i); + return; + } + stringAppend(p, "'", 1); + while( z[0] ){ + for(i=0; z[i] && z[i]!='\''; i++){} + if( z[i] ){ + stringAppend(p, z, i+1); + stringAppend(p, "'", 1); + z += i+1; + }else{ + stringAppend(p, z, i); + break; + } + } + stringAppend(p, "'", 1); +} + +/* +** Callback function for evalSql() +*/ +static int evalCallback(void *pCData, int argc, char **argv, char **azCol){ + String *p = (String*)pCData; + int i; + UNUSED_PARAMETER(azCol); + for(i=0; i0 ); + rc = sqlite3_exec(g.db, zSql, evalCallback, p, &zErrMsg); + sqlite3_free(zSql); + if( rc ){ + char zErr[30]; + sqlite3_snprintf(sizeof(zErr), zErr, "error(%d)", rc); + stringAppendTerm(p, zErr); + if( zErrMsg ){ + stringAppendTerm(p, zErrMsg); + sqlite3_free(zErrMsg); + } + } + return rc; +} + +/* +** Auxiliary SQL function to recursively evaluate SQL. +*/ +static void evalFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + const char *zSql = (const char*)sqlite3_value_text(argv[0]); + String res; + char *zErrMsg = 0; + int rc; + UNUSED_PARAMETER(argc); + memset(&res, 0, sizeof(res)); + rc = sqlite3_exec(db, zSql, evalCallback, &res, &zErrMsg); + if( zErrMsg ){ + sqlite3_result_error(context, zErrMsg, -1); + sqlite3_free(zErrMsg); + }else if( rc ){ + sqlite3_result_error_code(context, rc); + }else{ + sqlite3_result_text(context, res.z, -1, SQLITE_TRANSIENT); + } + stringFree(&res); +} + +/* +** Look up the next task for client iClient in the database. +** Return the task script and the task number and mark that +** task as being under way. +*/ +static int startScript( + int iClient, /* The client number */ + char **pzScript, /* Write task script here */ + int *pTaskId, /* Write task number here */ + char **pzTaskName /* Name of the task */ +){ + sqlite3_stmt *pStmt = 0; + int taskId; + int rc; + int totalTime = 0; + + *pzScript = 0; + g.iTimeout = 0; + while(1){ + rc = trySql("BEGIN IMMEDIATE"); + if( rc==SQLITE_BUSY ){ + sqlite3_sleep(10); + totalTime += 10; + continue; + } + if( rc!=SQLITE_OK ){ + fatalError("in startScript: %s", sqlite3_errmsg(g.db)); + } + if( g.nError || g.nTest ){ + runSql("UPDATE counters SET nError=nError+%d, nTest=nTest+%d", + g.nError, g.nTest); + g.nError = 0; + g.nTest = 0; + } + pStmt = prepareSql("SELECT 1 FROM client WHERE id=%d AND wantHalt",iClient); + rc = sqlite3_step(pStmt); + sqlite3_finalize(pStmt); + if( rc==SQLITE_ROW ){ + runSql("DELETE FROM client WHERE id=%d", iClient); + g.iTimeout = DEFAULT_TIMEOUT; + runSql("COMMIT TRANSACTION;"); + return SQLITE_DONE; + } + pStmt = prepareSql( + "SELECT script, id, name FROM task" + " WHERE client=%d AND starttime IS NULL" + " ORDER BY id LIMIT 1", iClient); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW ){ + int n = sqlite3_column_bytes(pStmt, 0); + *pzScript = sqlite3_malloc(n+1); + strcpy(*pzScript, (const char*)sqlite3_column_text(pStmt, 0)); + *pTaskId = taskId = sqlite3_column_int(pStmt, 1); + *pzTaskName = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 2)); + sqlite3_finalize(pStmt); + runSql("UPDATE task" + " SET starttime=strftime('%%Y-%%m-%%d %%H:%%M:%%f','now')" + " WHERE id=%d;", taskId); + g.iTimeout = DEFAULT_TIMEOUT; + runSql("COMMIT TRANSACTION;"); + return SQLITE_OK; + } + sqlite3_finalize(pStmt); + if( rc==SQLITE_DONE ){ + if( totalTime>30000 ){ + errorMessage("Waited over 30 seconds with no work. Giving up."); + runSql("DELETE FROM client WHERE id=%d; COMMIT;", iClient); + sqlite3_close(g.db); + exit(1); + } + while( trySql("COMMIT")==SQLITE_BUSY ){ + sqlite3_sleep(10); + totalTime += 10; + } + sqlite3_sleep(100); + totalTime += 100; + continue; + } + fatalError("%s", sqlite3_errmsg(g.db)); + } + g.iTimeout = DEFAULT_TIMEOUT; +} + +/* +** Mark a script as having finished. Remove the CLIENT table entry +** if bShutdown is true. +*/ +static int finishScript(int iClient, int taskId, int bShutdown){ + runSql("UPDATE task" + " SET endtime=strftime('%%Y-%%m-%%d %%H:%%M:%%f','now')" + " WHERE id=%d;", taskId); + if( bShutdown ){ + runSql("DELETE FROM client WHERE id=%d", iClient); + } + return SQLITE_OK; +} + +/* +** Start up a client process for iClient, if it is not already +** running. If the client is already running, then this routine +** is a no-op. +*/ +static void startClient(int iClient){ + runSql("INSERT OR IGNORE INTO client VALUES(%d,0)", iClient); + if( sqlite3_changes(g.db) ){ + char *zSys; + int rc; + zSys = sqlite3_mprintf("%s \"%s\" --client %d --trace %d", + g.argv0, g.zDbFile, iClient, g.iTrace); + if( g.bSqlTrace ){ + zSys = sqlite3_mprintf("%z --sqltrace", zSys); + } + if( g.bSync ){ + zSys = sqlite3_mprintf("%z --sync", zSys); + } + if( g.zVfs ){ + zSys = sqlite3_mprintf("%z --vfs \"%s\"", zSys, g.zVfs); + } + if( g.iTrace>=2 ) logMessage("system('%q')", zSys); +#if !defined(_WIN32) + zSys = sqlite3_mprintf("%z &", zSys); + rc = system(zSys); + if( rc ) errorMessage("system() fails with error code %d", rc); +#else + { + STARTUPINFOA startupInfo; + PROCESS_INFORMATION processInfo; + memset(&startupInfo, 0, sizeof(startupInfo)); + startupInfo.cb = sizeof(startupInfo); + memset(&processInfo, 0, sizeof(processInfo)); + rc = CreateProcessA(NULL, zSys, NULL, NULL, FALSE, 0, NULL, NULL, + &startupInfo, &processInfo); + if( rc ){ + CloseHandle(processInfo.hThread); + CloseHandle(processInfo.hProcess); + }else{ + errorMessage("CreateProcessA() fails with error code %lu", + GetLastError()); + } + } +#endif + sqlite3_free(zSys); + } +} + +/* +** Read the entire content of a file into memory +*/ +static char *readFile(const char *zFilename){ + FILE *in = fopen(zFilename, "rb"); + long sz; + char *z; + if( in==0 ){ + fatalError("cannot open \"%s\" for reading", zFilename); + } + fseek(in, 0, SEEK_END); + sz = ftell(in); + rewind(in); + z = sqlite3_malloc( sz+1 ); + sz = (long)fread(z, 1, sz, in); + z[sz] = 0; + fclose(in); + return z; +} + +/* +** Return the length of the next token. +*/ +static int tokenLength(const char *z, int *pnLine){ + int n = 0; + if( isspace(z[0]) || (z[0]=='/' && z[1]=='*') ){ + int inC = 0; + int c; + if( z[0]=='/' ){ + inC = 1; + n = 2; + } + while( (c = z[n++])!=0 ){ + if( c=='\n' ) (*pnLine)++; + if( isspace(c) ) continue; + if( inC && c=='*' && z[n]=='/' ){ + n++; + inC = 0; + }else if( !inC && c=='/' && z[n]=='*' ){ + n++; + inC = 1; + }else if( !inC ){ + break; + } + } + n--; + }else if( z[0]=='-' && z[1]=='-' ){ + for(n=2; z[n] && z[n]!='\n'; n++){} + if( z[n] ){ (*pnLine)++; n++; } + }else if( z[0]=='"' || z[0]=='\'' ){ + int delim = z[0]; + for(n=1; z[n]; n++){ + if( z[n]=='\n' ) (*pnLine)++; + if( z[n]==delim ){ + n++; + if( z[n+1]!=delim ) break; + } + } + }else{ + int c; + for(n=1; (c = z[n])!=0 && !isspace(c) && c!='"' && c!='\'' && c!=';'; n++){} + } + return n; +} + +/* +** Copy a single token into a string buffer. +*/ +static int extractToken(const char *zIn, int nIn, char *zOut, int nOut){ + int i; + if( nIn<=0 ){ + zOut[0] = 0; + return 0; + } + for(i=0; i0 ){ + pStmt = prepareSql( + "SELECT 1 FROM task" + " WHERE client=%d" + " AND client IN (SELECT id FROM client)" + " AND endtime IS NULL", + iClient); + }else{ + pStmt = prepareSql( + "SELECT 1 FROM task" + " WHERE client IN (SELECT id FROM client)" + " AND endtime IS NULL"); + } + g.iTimeout = 0; + while( ((rc = sqlite3_step(pStmt))==SQLITE_BUSY || rc==SQLITE_ROW) + && iTimeout>0 + ){ + sqlite3_reset(pStmt); + sqlite3_sleep(50); + iTimeout -= 50; + } + sqlite3_finalize(pStmt); + g.iTimeout = DEFAULT_TIMEOUT; + if( rc!=SQLITE_DONE ){ + if( zErrPrefix==0 ) zErrPrefix = ""; + if( iClient>0 ){ + errorMessage("%stimeout waiting for client %d", zErrPrefix, iClient); + }else{ + errorMessage("%stimeout waiting for all clients", zErrPrefix); + } + } +} + +/* Return a pointer to the tail of a filename +*/ +static char *filenameTail(char *z){ + int i, j; + for(i=j=0; z[i]; i++) if( z[i]=='/' ) j = i+1; + return z+j; +} + +/* +** Interpret zArg as a boolean value. Return either 0 or 1. +*/ +static int booleanValue(char *zArg){ + int i; + if( zArg==0 ) return 0; + for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){} + if( i>0 && zArg[i]==0 ) return atoi(zArg); + if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){ + return 1; + } + if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){ + return 0; + } + errorMessage("unknown boolean: [%s]", zArg); + return 0; +} + + +/* This routine exists as a convenient place to set a debugger +** breakpoint. +*/ +static void test_breakpoint(void){ static volatile int cnt = 0; cnt++; } + +/* Maximum number of arguments to a --command */ +#define MX_ARG 2 + +/* +** Run a script. +*/ +static void runScript( + int iClient, /* The client number, or 0 for the master */ + int taskId, /* The task ID for clients. 0 for master */ + char *zScript, /* Text of the script */ + char *zFilename /* File from which script was read. */ +){ + int lineno = 1; + int prevLine = 1; + int ii = 0; + int iBegin = 0; + int n, c, j; + int len; + int nArg; + String sResult; + char zCmd[30]; + char zError[1000]; + char azArg[MX_ARG][100]; + + memset(&sResult, 0, sizeof(sResult)); + stringReset(&sResult); + while( (c = zScript[ii])!=0 ){ + prevLine = lineno; + len = tokenLength(zScript+ii, &lineno); + if( isspace(c) || (c=='/' && zScript[ii+1]=='*') ){ + ii += len; + continue; + } + if( c!='-' || zScript[ii+1]!='-' || !isalpha(zScript[ii+2]) ){ + ii += len; + continue; + } + + /* Run any prior SQL before processing the new --command */ + if( ii>iBegin ){ + char *zSql = sqlite3_mprintf("%.*s", ii-iBegin, zScript+iBegin); + evalSql(&sResult, zSql); + sqlite3_free(zSql); + iBegin = ii + len; + } + + /* Parse the --command */ + if( g.iTrace>=2 ) logMessage("%.*s", len, zScript+ii); + n = extractToken(zScript+ii+2, len-2, zCmd, sizeof(zCmd)); + for(nArg=0; n=len-2 ) break; + n += extractToken(zScript+ii+2+n, len-2-n, + azArg[nArg], sizeof(azArg[nArg])); + } + for(j=nArg; j0 then exit without shutting down + ** SQLite. (In other words, simulate a crash.) + */ + if( strcmp(zCmd, "exit")==0 ){ + int rc = atoi(azArg[0]); + finishScript(iClient, taskId, 1); + if( rc==0 ) sqlite3_close(g.db); + exit(rc); + }else + + /* + ** --testcase NAME + ** + ** Begin a new test case. Announce in the log that the test case + ** has begun. + */ + if( strcmp(zCmd, "testcase")==0 ){ + if( g.iTrace==1 ) logMessage("%.*s", len - 1, zScript+ii); + stringReset(&sResult); + }else + + /* + ** --finish + ** + ** Mark the current task as having finished, even if it is not. + ** This can be used in conjunction with --exit to simulate a crash. + */ + if( strcmp(zCmd, "finish")==0 && iClient>0 ){ + finishScript(iClient, taskId, 1); + }else + + /* + ** --reset + ** + ** Reset accumulated results back to an empty string + */ + if( strcmp(zCmd, "reset")==0 ){ + stringReset(&sResult); + }else + + /* + ** --match ANSWER... + ** + ** Check to see if output matches ANSWER. Report an error if not. + */ + if( strcmp(zCmd, "match")==0 ){ + int jj; + char *zAns = zScript+ii; + for(jj=7; jj=0 && zFilename[k]!='/'; k--){} + if( k>0 ){ + zNewFile = zToDel = sqlite3_mprintf("%.*s/%s", k,zFilename,zNewFile); + } + } + zNewScript = readFile(zNewFile); + if( g.iTrace ) logMessage("begin script [%s]\n", zNewFile); + runScript(0, 0, zNewScript, zNewFile); + sqlite3_free(zNewScript); + if( g.iTrace ) logMessage("end script [%s]\n", zNewFile); + sqlite3_free(zToDel); + }else + + /* + ** --print MESSAGE.... + ** + ** Output the remainder of the line to the log file + */ + if( strcmp(zCmd, "print")==0 ){ + int jj; + for(jj=7; jj0 ){ + startClient(iNewClient); + } + }else + + /* + ** --wait CLIENT TIMEOUT + ** + ** Wait until all tasks complete for the given client. If CLIENT is + ** "all" then wait for all clients to complete. Wait no longer than + ** TIMEOUT milliseconds (default 10,000) + */ + if( strcmp(zCmd, "wait")==0 && iClient==0 ){ + int iTimeout = nArg>=2 ? atoi(azArg[1]) : 10000; + sqlite3_snprintf(sizeof(zError),zError,"line %d of %s\n", + prevLine, zFilename); + waitForClient(atoi(azArg[0]), iTimeout, zError); + }else + + /* + ** --task CLIENT + ** + ** --end + ** + ** Assign work to a client. Start the client if it is not running + ** already. + */ + if( strcmp(zCmd, "task")==0 && iClient==0 ){ + int iTarget = atoi(azArg[0]); + int iEnd; + char *zTask; + char *zTName; + iEnd = findEnd(zScript+ii+len, &lineno); + if( iTarget<0 ){ + errorMessage("line %d of %s: bad client number: %d", + prevLine, zFilename, iTarget); + }else{ + zTask = sqlite3_mprintf("%.*s", iEnd, zScript+ii+len); + if( nArg>1 ){ + zTName = sqlite3_mprintf("%s", azArg[1]); + }else{ + zTName = sqlite3_mprintf("%s:%d", filenameTail(zFilename), prevLine); + } + startClient(iTarget); + runSql("INSERT INTO task(client,script,name)" + " VALUES(%d,'%q',%Q)", iTarget, zTask, zTName); + sqlite3_free(zTask); + sqlite3_free(zTName); + } + iEnd += tokenLength(zScript+ii+len+iEnd, &lineno); + len += iEnd; + iBegin = ii+len; + }else + + /* + ** --breakpoint + ** + ** This command calls "test_breakpoint()" which is a routine provided + ** as a convenient place to set a debugger breakpoint. + */ + if( strcmp(zCmd, "breakpoint")==0 ){ + test_breakpoint(); + }else + + /* + ** --show-sql-errors BOOLEAN + ** + ** Turn display of SQL errors on and off. + */ + if( strcmp(zCmd, "show-sql-errors")==0 ){ + g.bIgnoreSqlErrors = nArg>=1 ? !booleanValue(azArg[0]) : 1; + }else + + + /* error */{ + errorMessage("line %d of %s: unknown command --%s", + prevLine, zFilename, zCmd); + } + ii += len; + } + if( iBegin= nArg ) break; + z = azArg[i]; + if( z[0]!='-' ) continue; + z++; + if( z[0]=='-' ){ + if( z[1]==0 ) break; + z++; + } + if( strcmp(z,zOption)==0 ){ + if( hasArg && i==nArg-1 ){ + fatalError("command-line option \"--%s\" requires an argument", z); + } + if( hasArg ){ + zReturn = azArg[i+1]; + }else{ + zReturn = azArg[i]; + } + j = i+1+(hasArg!=0); + while( j0 ){ + printf("With SQLite " SQLITE_VERSION " " SQLITE_SOURCE_ID "\n" ); + for(i=0; (zCOption = sqlite3_compileoption_get(i))!=0; i++){ + printf("-DSQLITE_%s\n", zCOption); + } + fflush(stdout); + } + iClient = 0; + unlink(g.zDbFile); + openFlags |= SQLITE_OPEN_CREATE; + } + rc = sqlite3_open_v2(g.zDbFile, &g.db, openFlags, g.zVfs); + if( rc ) fatalError("cannot open [%s]", g.zDbFile); + sqlite3_enable_load_extension(g.db, 1); + sqlite3_busy_handler(g.db, busyHandler, 0); + sqlite3_create_function(g.db, "vfsname", 0, SQLITE_UTF8, 0, + vfsNameFunc, 0, 0); + sqlite3_create_function(g.db, "eval", 1, SQLITE_UTF8, 0, + evalFunc, 0, 0); + g.iTimeout = DEFAULT_TIMEOUT; + if( g.bSqlTrace ) sqlite3_trace(g.db, sqlTraceCallback, 0); + if( !g.bSync ) trySql("PRAGMA synchronous=OFF"); + if( iClient>0 ){ + if( n>0 ) unrecognizedArguments(argv[0], n, argv+2); + if( g.iTrace ) logMessage("start-client"); + while(1){ + char *zTaskName = 0; + rc = startScript(iClient, &zScript, &taskId, &zTaskName); + if( rc==SQLITE_DONE ) break; + if( g.iTrace ) logMessage("begin %s (%d)", zTaskName, taskId); + runScript(iClient, taskId, zScript, zTaskName); + if( g.iTrace ) logMessage("end %s (%d)", zTaskName, taskId); + finishScript(iClient, taskId, 0); + sqlite3_free(zTaskName); + sqlite3_sleep(10); + } + if( g.iTrace ) logMessage("end-client"); + }else{ + sqlite3_stmt *pStmt; + int iTimeout; + if( n==0 ){ + fatalError("missing script filename"); + } + if( n>1 ) unrecognizedArguments(argv[0], n, argv+2); + runSql( + "CREATE TABLE task(\n" + " id INTEGER PRIMARY KEY,\n" + " name TEXT,\n" + " client INTEGER,\n" + " starttime DATE,\n" + " endtime DATE,\n" + " script TEXT\n" + ");" + "CREATE INDEX task_i1 ON task(client, starttime);\n" + "CREATE INDEX task_i2 ON task(client, endtime);\n" + "CREATE TABLE counters(nError,nTest);\n" + "INSERT INTO counters VALUES(0,0);\n" + "CREATE TABLE client(id INTEGER PRIMARY KEY, wantHalt);\n" + ); + zScript = readFile(argv[2]); + if( g.iTrace ) logMessage("begin script [%s]\n", argv[2]); + runScript(0, 0, zScript, argv[2]); + sqlite3_free(zScript); + if( g.iTrace ) logMessage("end script [%s]\n", argv[2]); + waitForClient(0, 2000, "during shutdown...\n"); + trySql("UPDATE client SET wantHalt=1"); + sqlite3_sleep(10); + g.iTimeout = 0; + iTimeout = 1000; + while( ((rc = trySql("SELECT 1 FROM client"))==SQLITE_BUSY + || rc==SQLITE_ROW) && iTimeout>0 ){ + sqlite3_sleep(10); + iTimeout -= 10; + } + sqlite3_sleep(100); + pStmt = prepareSql("SELECT nError, nTest FROM counters"); + iTimeout = 1000; + while( (rc = sqlite3_step(pStmt))==SQLITE_BUSY && iTimeout>0 ){ + sqlite3_sleep(10); + iTimeout -= 10; + } + if( rc==SQLITE_ROW ){ + g.nError += sqlite3_column_int(pStmt, 0); + g.nTest += sqlite3_column_int(pStmt, 1); + } + sqlite3_finalize(pStmt); + } + sqlite3_close(g.db); + maybeClose(g.pLog); + maybeClose(g.pErrLog); + if( iClient==0 ){ + printf("Summary: %d errors in %d tests\n", g.nError, g.nTest); + } + return g.nError>0; +} diff --git a/mptest/multiwrite01.test b/mptest/multiwrite01.test new file mode 100644 index 0000000000..9b3ccb8473 --- /dev/null +++ b/mptest/multiwrite01.test @@ -0,0 +1,405 @@ +/* +** This script sets up five different tasks all writing and updating +** the database at the same time, but each in its own table. +*/ +--task 1 build-t1 + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t1 VALUES(1, randomblob(2000)); + INSERT INTO t1 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t1 SELECT a+2, randomblob(1500) FROM t1; + INSERT INTO t1 SELECT a+4, randomblob(1500) FROM t1; + INSERT INTO t1 SELECT a+8, randomblob(1500) FROM t1; + --sleep 1 + INSERT INTO t1 SELECT a+16, randomblob(1500) FROM t1; + --sleep 1 + INSERT INTO t1 SELECT a+32, randomblob(1500) FROM t1; + SELECT count(*) FROM t1; + --match 64 + SELECT avg(length(b)) FROM t1; + --match 1500.0 + --sleep 2 + UPDATE t1 SET b='x'||a||'y'; + SELECT total(length(b)) FROM t1; + --match 247 + SELECT a FROM t1 WHERE b='x17y'; + --match 17 + CREATE INDEX t1b ON t1(b); + SELECT a FROM t1 WHERE b='x17y'; + --match 17 + SELECT a FROM t1 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + + +--task 2 build-t2 + DROP TABLE IF EXISTS t2; + CREATE TABLE t2(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t2 VALUES(1, randomblob(2000)); + INSERT INTO t2 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t2 SELECT a+2, randomblob(1500) FROM t2; + INSERT INTO t2 SELECT a+4, randomblob(1500) FROM t2; + INSERT INTO t2 SELECT a+8, randomblob(1500) FROM t2; + --sleep 1 + INSERT INTO t2 SELECT a+16, randomblob(1500) FROM t2; + --sleep 1 + INSERT INTO t2 SELECT a+32, randomblob(1500) FROM t2; + SELECT count(*) FROM t2; + --match 64 + SELECT avg(length(b)) FROM t2; + --match 1500.0 + --sleep 2 + UPDATE t2 SET b='x'||a||'y'; + SELECT total(length(b)) FROM t2; + --match 247 + SELECT a FROM t2 WHERE b='x17y'; + --match 17 + CREATE INDEX t2b ON t2(b); + SELECT a FROM t2 WHERE b='x17y'; + --match 17 + SELECT a FROM t2 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + +--task 3 build-t3 + DROP TABLE IF EXISTS t3; + CREATE TABLE t3(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t3 VALUES(1, randomblob(2000)); + INSERT INTO t3 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t3 SELECT a+2, randomblob(1500) FROM t3; + INSERT INTO t3 SELECT a+4, randomblob(1500) FROM t3; + INSERT INTO t3 SELECT a+8, randomblob(1500) FROM t3; + --sleep 1 + INSERT INTO t3 SELECT a+16, randomblob(1500) FROM t3; + --sleep 1 + INSERT INTO t3 SELECT a+32, randomblob(1500) FROM t3; + SELECT count(*) FROM t3; + --match 64 + SELECT avg(length(b)) FROM t3; + --match 1500.0 + --sleep 2 + UPDATE t3 SET b='x'||a||'y'; + SELECT total(length(b)) FROM t3; + --match 247 + SELECT a FROM t3 WHERE b='x17y'; + --match 17 + CREATE INDEX t3b ON t3(b); + SELECT a FROM t3 WHERE b='x17y'; + --match 17 + SELECT a FROM t3 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + +--task 4 build-t4 + DROP TABLE IF EXISTS t4; + CREATE TABLE t4(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t4 VALUES(1, randomblob(2000)); + INSERT INTO t4 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t4 SELECT a+2, randomblob(1500) FROM t4; + INSERT INTO t4 SELECT a+4, randomblob(1500) FROM t4; + INSERT INTO t4 SELECT a+8, randomblob(1500) FROM t4; + --sleep 1 + INSERT INTO t4 SELECT a+16, randomblob(1500) FROM t4; + --sleep 1 + INSERT INTO t4 SELECT a+32, randomblob(1500) FROM t4; + SELECT count(*) FROM t4; + --match 64 + SELECT avg(length(b)) FROM t4; + --match 1500.0 + --sleep 2 + UPDATE t4 SET b='x'||a||'y'; + SELECT total(length(b)) FROM t4; + --match 247 + SELECT a FROM t4 WHERE b='x17y'; + --match 17 + CREATE INDEX t4b ON t4(b); + SELECT a FROM t4 WHERE b='x17y'; + --match 17 + SELECT a FROM t4 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + +--task 5 build-t5 + DROP TABLE IF EXISTS t5; + CREATE TABLE t5(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t5 VALUES(1, randomblob(2000)); + INSERT INTO t5 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t5 SELECT a+2, randomblob(1500) FROM t5; + INSERT INTO t5 SELECT a+4, randomblob(1500) FROM t5; + INSERT INTO t5 SELECT a+8, randomblob(1500) FROM t5; + --sleep 1 + INSERT INTO t5 SELECT a+16, randomblob(1500) FROM t5; + --sleep 1 + INSERT INTO t5 SELECT a+32, randomblob(1500) FROM t5; + SELECT count(*) FROM t5; + --match 64 + SELECT avg(length(b)) FROM t5; + --match 1500.0 + --sleep 2 + UPDATE t5 SET b='x'||a||'y'; + SELECT total(length(b)) FROM t5; + --match 247 + SELECT a FROM t5 WHERE b='x17y'; + --match 17 + CREATE INDEX t5b ON t5(b); + SELECT a FROM t5 WHERE b='x17y'; + --match 17 + SELECT a FROM t5 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + +--wait all +SELECT count(*), total(length(b)) FROM t1; +--match 64 247 +SELECT count(*), total(length(b)) FROM t2; +--match 64 247 +SELECT count(*), total(length(b)) FROM t3; +--match 64 247 +SELECT count(*), total(length(b)) FROM t4; +--match 64 247 +SELECT count(*), total(length(b)) FROM t5; +--match 64 247 + +--task 1 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 5 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 3 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 2 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 4 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--wait all + +--task 5 + DROP INDEX t5b; + --sleep 5 + PRAGMA integrity_check(10); + --match ok + CREATE INDEX t5b ON t5(b DESC); +--end +--task 3 + DROP INDEX t3b; + --sleep 5 + PRAGMA integrity_check(10); + --match ok + CREATE INDEX t3b ON t3(b DESC); +--end +--task 1 + DROP INDEX t1b; + --sleep 5 + PRAGMA integrity_check(10); + --match ok + CREATE INDEX t1b ON t1(b DESC); +--end +--task 2 + DROP INDEX t2b; + --sleep 5 + PRAGMA integrity_check(10); + --match ok + CREATE INDEX t2b ON t2(b DESC); +--end +--task 4 + DROP INDEX t4b; + --sleep 5 + PRAGMA integrity_check(10); + --match ok + CREATE INDEX t4b ON t4(b DESC); +--end +--wait all + +--task 1 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 5 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 3 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 2 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 4 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--wait all + +VACUUM; +PRAGMA integrity_check(10); +--match ok + +--task 1 + UPDATE t1 SET b=randomblob(20000); + --sleep 5 + UPDATE t1 SET b='x'||a||'y'; + SELECT a FROM t1 WHERE b='x63y'; + --match 63 +--end +--task 2 + UPDATE t2 SET b=randomblob(20000); + --sleep 5 + UPDATE t2 SET b='x'||a||'y'; + SELECT a FROM t2 WHERE b='x63y'; + --match 63 +--end +--task 3 + UPDATE t3 SET b=randomblob(20000); + --sleep 5 + UPDATE t3 SET b='x'||a||'y'; + SELECT a FROM t3 WHERE b='x63y'; + --match 63 +--end +--task 4 + UPDATE t4 SET b=randomblob(20000); + --sleep 5 + UPDATE t4 SET b='x'||a||'y'; + SELECT a FROM t4 WHERE b='x63y'; + --match 63 +--end +--task 5 + UPDATE t5 SET b=randomblob(20000); + --sleep 5 + UPDATE t5 SET b='x'||a||'y'; + SELECT a FROM t5 WHERE b='x63y'; + --match 63 +--end +--wait all + +--task 1 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 5 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 3 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 2 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 4 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--wait all diff --git a/src/backup.c b/src/backup.c index 71a8a1a3e7..252f61cfca 100644 --- a/src/backup.c +++ b/src/backup.c @@ -397,7 +397,8 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){ const Pgno iSrcPg = p->iNext; /* Source page number */ if( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ){ DbPage *pSrcPg; /* Source page object */ - rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg); + rc = sqlite3PagerAcquire(pSrcPager, iSrcPg, &pSrcPg, + PAGER_ACQUIRE_READONLY); if( rc==SQLITE_OK ){ rc = backupOnePage(p, iSrcPg, sqlite3PagerGetData(pSrcPg), 0); sqlite3PagerUnref(pSrcPg); diff --git a/src/btree.c b/src/btree.c index 96140d68c6..616d403681 100644 --- a/src/btree.c +++ b/src/btree.c @@ -1581,13 +1581,17 @@ static int btreeGetPage( BtShared *pBt, /* The btree */ Pgno pgno, /* Number of the page to fetch */ MemPage **ppPage, /* Return the page in this parameter */ - int noContent /* Do not load page content if true */ + int noContent, /* Do not load page content if true */ + int bReadonly /* True if a read-only (mmap) page is ok */ ){ int rc; DbPage *pDbPage; + int flags = (noContent ? PAGER_ACQUIRE_NOCONTENT : 0) + | (bReadonly ? PAGER_ACQUIRE_READONLY : 0); + assert( noContent==0 || bReadonly==0 ); assert( sqlite3_mutex_held(pBt->mutex) ); - rc = sqlite3PagerAcquire(pBt->pPager, pgno, (DbPage**)&pDbPage, noContent); + rc = sqlite3PagerAcquire(pBt->pPager, pgno, (DbPage**)&pDbPage, flags); if( rc ) return rc; *ppPage = btreePageFromDbPage(pDbPage, pgno, pBt); return SQLITE_OK; @@ -1630,9 +1634,10 @@ u32 sqlite3BtreeLastPage(Btree *p){ ** may remain unchanged, or it may be set to an invalid value. */ static int getAndInitPage( - BtShared *pBt, /* The database file */ - Pgno pgno, /* Number of the page to get */ - MemPage **ppPage /* Write the page pointer here */ + BtShared *pBt, /* The database file */ + Pgno pgno, /* Number of the page to get */ + MemPage **ppPage, /* Write the page pointer here */ + int bReadonly /* True if a read-only (mmap) page is ok */ ){ int rc; assert( sqlite3_mutex_held(pBt->mutex) ); @@ -1640,7 +1645,7 @@ static int getAndInitPage( if( pgno>btreePagecount(pBt) ){ rc = SQLITE_CORRUPT_BKPT; }else{ - rc = btreeGetPage(pBt, pgno, ppPage, 0); + rc = btreeGetPage(pBt, pgno, ppPage, 0, bReadonly); if( rc==SQLITE_OK ){ rc = btreeInitPage(*ppPage); if( rc!=SQLITE_OK ){ @@ -1871,6 +1876,7 @@ int sqlite3BtreeOpen( rc = sqlite3PagerOpen(pVfs, &pBt->pPager, zFilename, EXTRA_SIZE, flags, vfsFlags, pageReinit); if( rc==SQLITE_OK ){ + sqlite3PagerSetMmapLimit(pBt->pPager, db->szMmap); rc = sqlite3PagerReadFileheader(pBt->pPager,sizeof(zDbHeader),zDbHeader); } if( rc!=SQLITE_OK ){ @@ -2137,6 +2143,19 @@ int sqlite3BtreeSetCacheSize(Btree *p, int mxPage){ return SQLITE_OK; } +/* +** Change the limit on the amount of the database file that may be +** memory mapped. +*/ +int sqlite3BtreeSetMmapLimit(Btree *p, sqlite3_int64 szMmap){ + BtShared *pBt = p->pBt; + assert( sqlite3_mutex_held(p->db->mutex) ); + sqlite3BtreeEnter(p); + sqlite3PagerSetMmapLimit(pBt->pPager, szMmap); + sqlite3BtreeLeave(p); + return SQLITE_OK; +} + /* ** Change the way data is synced to disk in order to increase or decrease ** how well the database resists damage due to OS crashes and power @@ -2362,7 +2381,7 @@ static int lockBtree(BtShared *pBt){ assert( pBt->pPage1==0 ); rc = sqlite3PagerSharedLock(pBt->pPager); if( rc!=SQLITE_OK ) return rc; - rc = btreeGetPage(pBt, 1, &pPage1, 0); + rc = btreeGetPage(pBt, 1, &pPage1, 0, 0); if( rc!=SQLITE_OK ) return rc; /* Do some checking to help insure the file we opened really is @@ -2921,7 +2940,7 @@ static int relocatePage( ** iPtrPage. */ if( eType!=PTRMAP_ROOTPAGE ){ - rc = btreeGetPage(pBt, iPtrPage, &pPtrPage, 0); + rc = btreeGetPage(pBt, iPtrPage, &pPtrPage, 0, 0); if( rc!=SQLITE_OK ){ return rc; } @@ -3005,7 +3024,7 @@ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){ u8 eMode = BTALLOC_ANY; /* Mode parameter for allocateBtreePage() */ Pgno iNear = 0; /* nearby parameter for allocateBtreePage() */ - rc = btreeGetPage(pBt, iLastPg, &pLastPg, 0); + rc = btreeGetPage(pBt, iLastPg, &pLastPg, 0, 0); if( rc!=SQLITE_OK ){ return rc; } @@ -3097,8 +3116,11 @@ int sqlite3BtreeIncrVacuum(Btree *p){ if( nOrig0 ){ - invalidateAllOverflowCache(pBt); - rc = incrVacuumStep(pBt, nFin, nOrig, 0); + rc = saveAllCursors(pBt, 0, 0); + if( rc==SQLITE_OK ){ + invalidateAllOverflowCache(pBt); + rc = incrVacuumStep(pBt, nFin, nOrig, 0); + } if( rc==SQLITE_OK ){ rc = sqlite3PagerWrite(pBt->pPage1->pDbPage); put4byte(&pBt->pPage1->aData[28], pBt->nPage); @@ -3146,7 +3168,9 @@ static int autoVacuumCommit(BtShared *pBt){ nFree = get4byte(&pBt->pPage1->aData[36]); nFin = finalDbSize(pBt, nOrig, nFree); if( nFin>nOrig ) return SQLITE_CORRUPT_BKPT; - + if( nFinnFin && rc==SQLITE_OK; iFree--){ rc = incrVacuumStep(pBt, nFin, iFree, 1); } @@ -3163,7 +3187,7 @@ static int autoVacuumCommit(BtShared *pBt){ } } - assert( nRef==sqlite3PagerRefcount(pPager) ); + assert( nRef>=sqlite3PagerRefcount(pPager) ); return rc; } @@ -3419,7 +3443,7 @@ int sqlite3BtreeRollback(Btree *p, int tripCode){ /* The rollback may have destroyed the pPage1->aData value. So ** call btreeGetPage() on page 1 again to make ** sure pPage1->aData is set correctly. */ - if( btreeGetPage(pBt, 1, &pPage1, 0)==SQLITE_OK ){ + if( btreeGetPage(pBt, 1, &pPage1, 0, 0)==SQLITE_OK ){ int nPage = get4byte(28+(u8*)pPage1->aData); testcase( nPage==0 ); if( nPage==0 ) sqlite3PagerPagecount(pBt->pPager, &nPage); @@ -3853,7 +3877,7 @@ static int getOverflowPage( assert( next==0 || rc==SQLITE_DONE ); if( rc==SQLITE_OK ){ - rc = btreeGetPage(pBt, ovfl, &pPage, 0); + rc = btreeGetPage(pBt, ovfl, &pPage, 0, (ppPage==0)); assert( rc==SQLITE_OK || pPage==0 ); if( rc==SQLITE_OK ){ next = get4byte(pPage->aData); @@ -4074,7 +4098,9 @@ static int accessPayload( { DbPage *pDbPage; - rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage); + rc = sqlite3PagerAcquire(pBt->pPager, nextPage, &pDbPage, + (eOp==0 ? PAGER_ACQUIRE_READONLY : 0) + ); if( rc==SQLITE_OK ){ aPayload = sqlite3PagerGetData(pDbPage); nextPage = get4byte(aPayload); @@ -4253,10 +4279,11 @@ static int moveToChild(BtCursor *pCur, u32 newPgno){ assert( cursorHoldsMutex(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPageiPage>=0 ); if( pCur->iPage>=(BTCURSOR_MAX_DEPTH-1) ){ return SQLITE_CORRUPT_BKPT; } - rc = getAndInitPage(pBt, newPgno, &pNewPage); + rc = getAndInitPage(pBt, newPgno, &pNewPage, (pCur->wrFlag==0)); if( rc ) return rc; pCur->apPage[i+1] = pNewPage; pCur->aiIdx[i+1] = 0; @@ -4373,7 +4400,7 @@ static int moveToRoot(BtCursor *pCur){ pCur->eState = CURSOR_INVALID; return SQLITE_OK; }else{ - rc = getAndInitPage(pBt, pCur->pgnoRoot, &pCur->apPage[0]); + rc = getAndInitPage(pBt, pCur->pgnoRoot, &pCur->apPage[0], pCur->wrFlag==0); if( rc!=SQLITE_OK ){ pCur->eState = CURSOR_INVALID; return rc; @@ -4987,7 +5014,7 @@ static int allocateBtreePage( if( iTrunk>mxPage ){ rc = SQLITE_CORRUPT_BKPT; }else{ - rc = btreeGetPage(pBt, iTrunk, &pTrunk, 0); + rc = btreeGetPage(pBt, iTrunk, &pTrunk, 0, 0); } if( rc ){ pTrunk = 0; @@ -5051,7 +5078,7 @@ static int allocateBtreePage( goto end_allocate_page; } testcase( iNewTrunk==mxPage ); - rc = btreeGetPage(pBt, iNewTrunk, &pNewTrunk, 0); + rc = btreeGetPage(pBt, iNewTrunk, &pNewTrunk, 0, 0); if( rc!=SQLITE_OK ){ goto end_allocate_page; } @@ -5131,7 +5158,7 @@ static int allocateBtreePage( } put4byte(&aData[4], k-1); noContent = !btreeGetHasContent(pBt, *pPgno); - rc = btreeGetPage(pBt, *pPgno, ppPage, noContent); + rc = btreeGetPage(pBt, *pPgno, ppPage, noContent, 0); if( rc==SQLITE_OK ){ rc = sqlite3PagerWrite((*ppPage)->pDbPage); if( rc!=SQLITE_OK ){ @@ -5179,7 +5206,7 @@ static int allocateBtreePage( MemPage *pPg = 0; TRACE(("ALLOCATE: %d from end of file (pointer-map page)\n", pBt->nPage)); assert( pBt->nPage!=PENDING_BYTE_PAGE(pBt) ); - rc = btreeGetPage(pBt, pBt->nPage, &pPg, bNoContent); + rc = btreeGetPage(pBt, pBt->nPage, &pPg, bNoContent, 0); if( rc==SQLITE_OK ){ rc = sqlite3PagerWrite(pPg->pDbPage); releasePage(pPg); @@ -5193,7 +5220,7 @@ static int allocateBtreePage( *pPgno = pBt->nPage; assert( *pPgno!=PENDING_BYTE_PAGE(pBt) ); - rc = btreeGetPage(pBt, *pPgno, ppPage, bNoContent); + rc = btreeGetPage(pBt, *pPgno, ppPage, bNoContent, 0); if( rc ) return rc; rc = sqlite3PagerWrite((*ppPage)->pDbPage); if( rc!=SQLITE_OK ){ @@ -5261,7 +5288,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ /* If the secure_delete option is enabled, then ** always fully overwrite deleted information with zeros. */ - if( (!pPage && ((rc = btreeGetPage(pBt, iPage, &pPage, 0))!=0) ) + if( (!pPage && ((rc = btreeGetPage(pBt, iPage, &pPage, 0, 0))!=0) ) || ((rc = sqlite3PagerWrite(pPage->pDbPage))!=0) ){ goto freepage_out; @@ -5288,7 +5315,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ u32 nLeaf; /* Initial number of leaf cells on trunk page */ iTrunk = get4byte(&pPage1->aData[32]); - rc = btreeGetPage(pBt, iTrunk, &pTrunk, 0); + rc = btreeGetPage(pBt, iTrunk, &pTrunk, 0, 0); if( rc!=SQLITE_OK ){ goto freepage_out; } @@ -5334,7 +5361,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ ** first trunk in the free-list is full. Either way, the page being freed ** will become the new first trunk page in the free-list. */ - if( pPage==0 && SQLITE_OK!=(rc = btreeGetPage(pBt, iPage, &pPage, 0)) ){ + if( pPage==0 && SQLITE_OK!=(rc = btreeGetPage(pBt, iPage, &pPage, 0, 0)) ){ goto freepage_out; } rc = sqlite3PagerWrite(pPage->pDbPage); @@ -6135,7 +6162,7 @@ static int balance_nonroot( } pgno = get4byte(pRight); while( 1 ){ - rc = getAndInitPage(pBt, pgno, &apOld[i]); + rc = getAndInitPage(pBt, pgno, &apOld[i], 0); if( rc ){ memset(apOld, 0, (i+1)*sizeof(MemPage*)); goto balance_cleanup; @@ -7223,10 +7250,17 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){ u8 eType = 0; Pgno iPtrPage = 0; + /* Save the positions of any open cursors. This is required in + ** case they are holding a reference to an xFetch reference + ** corresponding to page pgnoRoot. */ + rc = saveAllCursors(pBt, 0, 0); releasePage(pPageMove); + if( rc!=SQLITE_OK ){ + return rc; + } /* Move the page currently at pgnoRoot to pgnoMove. */ - rc = btreeGetPage(pBt, pgnoRoot, &pRoot, 0); + rc = btreeGetPage(pBt, pgnoRoot, &pRoot, 0, 0); if( rc!=SQLITE_OK ){ return rc; } @@ -7247,7 +7281,7 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){ if( rc!=SQLITE_OK ){ return rc; } - rc = btreeGetPage(pBt, pgnoRoot, &pRoot, 0); + rc = btreeGetPage(pBt, pgnoRoot, &pRoot, 0, 0); if( rc!=SQLITE_OK ){ return rc; } @@ -7323,7 +7357,7 @@ static int clearDatabasePage( return SQLITE_CORRUPT_BKPT; } - rc = getAndInitPage(pBt, pgno, &pPage); + rc = getAndInitPage(pBt, pgno, &pPage, 0); if( rc ) return rc; for(i=0; inCell; i++){ pCell = findCell(pPage, i); @@ -7425,7 +7459,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ return SQLITE_LOCKED_SHAREDCACHE; } - rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0); + rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0, 0); if( rc ) return rc; rc = sqlite3BtreeClearTable(p, iTable, 0); if( rc ){ @@ -7460,7 +7494,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ */ MemPage *pMove; releasePage(pPage); - rc = btreeGetPage(pBt, maxRootPgno, &pMove, 0); + rc = btreeGetPage(pBt, maxRootPgno, &pMove, 0, 0); if( rc!=SQLITE_OK ){ return rc; } @@ -7470,7 +7504,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ return rc; } pMove = 0; - rc = btreeGetPage(pBt, maxRootPgno, &pMove, 0); + rc = btreeGetPage(pBt, maxRootPgno, &pMove, 0, 0); freePage(pMove, &rc); releasePage(pMove); if( rc!=SQLITE_OK ){ @@ -7882,7 +7916,7 @@ static int checkTreePage( usableSize = pBt->usableSize; if( iPage==0 ) return 0; if( checkRef(pCheck, iPage, zParentContext) ) return 0; - if( (rc = btreeGetPage(pBt, (Pgno)iPage, &pPage, 0))!=0 ){ + if( (rc = btreeGetPage(pBt, (Pgno)iPage, &pPage, 0, 0))!=0 ){ checkAppendMsg(pCheck, zContext, "unable to get the page. error code=%d", rc); return 0; @@ -8354,6 +8388,17 @@ int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){ return SQLITE_ABORT; } + /* Save the positions of all other cursors open on this table. This is + ** required in case any of them are holding references to an xFetch + ** version of the b-tree page modified by the accessPayload call below. + ** + ** Note that pCsr must be open on a BTREE_INTKEY table and saveCursorPosition() + ** and hence saveAllCursors() cannot fail on a BTREE_INTKEY table, hence + ** saveAllCursors can only return SQLITE_OK. + */ + VVA_ONLY(rc =) saveAllCursors(pCsr->pBt, pCsr->pgnoRoot, pCsr); + assert( rc==SQLITE_OK ); + /* Check some assumptions: ** (a) the cursor is open for writing, ** (b) there is a read/write transaction open, diff --git a/src/btree.h b/src/btree.h index d4c9fe37d7..23f6ad7068 100644 --- a/src/btree.h +++ b/src/btree.h @@ -63,6 +63,7 @@ int sqlite3BtreeOpen( int sqlite3BtreeClose(Btree*); int sqlite3BtreeSetCacheSize(Btree*,int); +int sqlite3BtreeSetMmapLimit(Btree*,sqlite3_int64); int sqlite3BtreeSetSafetyLevel(Btree*,int,int,int); int sqlite3BtreeSyncDisabled(Btree*); int sqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix); diff --git a/src/ctime.c b/src/ctime.c index c42454ca71..60595ff88d 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -48,15 +48,15 @@ static const char * const azCompileOpt[] = { #ifdef SQLITE_COVERAGE_TEST "COVERAGE_TEST", #endif -#ifdef SQLITE_CURDIR - "CURDIR", -#endif #ifdef SQLITE_DEBUG "DEBUG", #endif #ifdef SQLITE_DEFAULT_LOCKING_MODE "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE), #endif +#if defined(SQLITE_DEFAULT_MMAP_SIZE) && !defined(SQLITE_DEFAULT_MMAP_SIZE_xc) + "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), +#endif #ifdef SQLITE_DISABLE_DIRSYNC "DISABLE_DIRSYNC", #endif @@ -147,6 +147,9 @@ static const char * const azCompileOpt[] = { #ifdef SQLITE_LOCK_TRACE "LOCK_TRACE", #endif +#if defined(SQLITE_MAX_MMAP_SIZE) && !defined(SQLITE_MAX_MMAP_SIZE_xc) + "MAX_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE), +#endif #ifdef SQLITE_MAX_SCHEMA_RETRY "MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY), #endif @@ -204,11 +207,6 @@ static const char * const azCompileOpt[] = { #ifdef SQLITE_OMIT_CHECK "OMIT_CHECK", #endif -/* // redundant -** #ifdef SQLITE_OMIT_COMPILEOPTION_DIAGS -** "OMIT_COMPILEOPTION_DIAGS", -** #endif -*/ #ifdef SQLITE_OMIT_COMPLETE "OMIT_COMPLETE", #endif @@ -350,13 +348,13 @@ static const char * const azCompileOpt[] = { #ifdef SQLITE_TCL "TCL", #endif -#ifdef SQLITE_TEMP_STORE +#if defined(SQLITE_TEMP_STORE) && !defined(SQLITE_TEMP_STORE_xc) "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE), #endif #ifdef SQLITE_TEST "TEST", #endif -#ifdef SQLITE_THREADSAFE +#if defined(SQLITE_THREADSAFE) "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), #endif #ifdef SQLITE_USE_ALLOCA @@ -382,8 +380,11 @@ int sqlite3_compileoption_used(const char *zOptName){ /* Since ArraySize(azCompileOpt) is normally in single digits, a ** linear search is adequate. No need for a binary search. */ for(i=0; imutex); sqlite3Error(db, SQLITE_OK, 0); - while( (rc==SQLITE_OK || (rc==SQLITE_SCHEMA && (++nRetry)<2)) && zSql[0] ){ + while( rc==SQLITE_OK && zSql[0] ){ int nCol; char **azVals = 0; pStmt = 0; - rc = sqlite3_prepare(db, zSql, -1, &pStmt, &zLeftover); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); assert( rc==SQLITE_OK || pStmt==0 ); if( rc!=SQLITE_OK ){ continue; @@ -108,11 +107,8 @@ int sqlite3_exec( if( rc!=SQLITE_ROW ){ rc = sqlite3VdbeFinalize((Vdbe *)pStmt); pStmt = 0; - if( rc!=SQLITE_SCHEMA ){ - nRetry = 0; - zSql = zLeftover; - while( sqlite3Isspace(zSql[0]) ) zSql++; - } + zSql = zLeftover; + while( sqlite3Isspace(zSql[0]) ) zSql++; break; } } diff --git a/src/loadext.c b/src/loadext.c index 76450f2e8b..cdcf6a93b8 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -415,8 +415,23 @@ static int sqlite3LoadExtension( void *handle; int (*xInit)(sqlite3*,char**,const sqlite3_api_routines*); char *zErrmsg = 0; + const char *zEntry; + char *zAltEntry = 0; void **aHandle; int nMsg = 300 + sqlite3Strlen30(zFile); + int ii; + + /* Shared library endings to try if zFile cannot be loaded as written */ + static const char *azEndings[] = { +#if SQLITE_OS_WIN + "dll" +#elif defined(__APPLE__) + "dylib" +#else + "so" +#endif + }; + if( pzErrMsg ) *pzErrMsg = 0; @@ -433,11 +448,17 @@ static int sqlite3LoadExtension( return SQLITE_ERROR; } - if( zProc==0 ){ - zProc = "sqlite3_extension_init"; - } + zEntry = zProc ? zProc : "sqlite3_extension_init"; handle = sqlite3OsDlOpen(pVfs, zFile); +#if SQLITE_OS_UNIX || SQLITE_OS_WIN + for(ii=0; ii sqlite3_example_init + ** C:/lib/mathfuncs.dll ==> sqlite3_mathfuncs_init + */ + if( xInit==0 && zProc==0 ){ + int iFile, iEntry, c; + int ncFile = sqlite3Strlen30(zFile); + zAltEntry = sqlite3_malloc(ncFile+30); + if( zAltEntry==0 ){ + sqlite3OsDlClose(pVfs, handle); + return SQLITE_NOMEM; + } + memcpy(zAltEntry, "sqlite3_", 8); + for(iFile=ncFile-1; iFile>=0 && zFile[iFile]!='/'; iFile--){} + iFile++; + if( sqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3; + for(iEntry=8; (c = zFile[iFile])!=0 && c!='.'; iFile++){ + if( sqlite3Isalpha(c) ){ + zAltEntry[iEntry++] = (char)sqlite3UpperToLower[(unsigned)c]; + } + } + memcpy(zAltEntry+iEntry, "_init", 6); + zEntry = zAltEntry; + xInit = (int(*)(sqlite3*,char**,const sqlite3_api_routines*)) + sqlite3OsDlSym(pVfs, handle, zEntry); + } if( xInit==0 ){ if( pzErrMsg ){ - nMsg += sqlite3Strlen30(zProc); + nMsg += sqlite3Strlen30(zEntry); *pzErrMsg = zErrmsg = sqlite3_malloc(nMsg); if( zErrmsg ){ sqlite3_snprintf(nMsg, zErrmsg, - "no entry point [%s] in shared library [%s]", zProc,zFile); + "no entry point [%s] in shared library [%s]", zEntry, zFile); sqlite3OsDlError(pVfs, nMsg-1, zErrmsg); } - sqlite3OsDlClose(pVfs, handle); } + sqlite3OsDlClose(pVfs, handle); + sqlite3_free(zAltEntry); return SQLITE_ERROR; - }else if( xInit(db, &zErrmsg, &sqlite3Apis) ){ + } + sqlite3_free(zAltEntry); + if( xInit(db, &zErrmsg, &sqlite3Apis) ){ if( pzErrMsg ){ *pzErrMsg = sqlite3_mprintf("error during initialization: %s", zErrmsg); } diff --git a/src/main.c b/src/main.c index 7d04b89840..c2fb1ee2e2 100644 --- a/src/main.c +++ b/src/main.c @@ -496,6 +496,19 @@ int sqlite3_config(int op, ...){ } #endif + case SQLITE_CONFIG_MMAP_SIZE: { + sqlite3_int64 szMmap = va_arg(ap, sqlite3_int64); + sqlite3_int64 mxMmap = va_arg(ap, sqlite3_int64); + if( mxMmap<0 || mxMmap>SQLITE_MAX_MMAP_SIZE ){ + mxMmap = SQLITE_MAX_MMAP_SIZE; + } + sqlite3GlobalConfig.mxMmap = mxMmap; + if( szMmap<0 ) szMmap = SQLITE_DEFAULT_MMAP_SIZE; + if( szMmap>mxMmap) szMmap = mxMmap; + sqlite3GlobalConfig.szMmap = szMmap; + break; + } + default: { rc = SQLITE_ERROR; break; @@ -2337,6 +2350,7 @@ static int openDatabase( memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit)); db->autoCommit = 1; db->nextAutovac = -1; + db->szMmap = sqlite3GlobalConfig.szMmap; db->nextPagesize = 0; db->flags |= SQLITE_ShortColNames | SQLITE_AutoIndex | SQLITE_EnableTrigger #if SQLITE_DEFAULT_FILE_FORMAT<4 diff --git a/src/memjournal.c b/src/memjournal.c index 3e66e215b2..05725948f6 100644 --- a/src/memjournal.c +++ b/src/memjournal.c @@ -230,7 +230,9 @@ static const struct sqlite3_io_methods MemJournalMethods = { 0, /* xShmMap */ 0, /* xShmLock */ 0, /* xShmBarrier */ - 0 /* xShmUnlock */ + 0, /* xShmUnmap */ + 0, /* xFetch */ + 0 /* xUnfetch */ }; /* diff --git a/src/os.c b/src/os.c index b5e918a727..be2ea4cfc0 100644 --- a/src/os.c +++ b/src/os.c @@ -141,6 +141,26 @@ int sqlite3OsShmMap( return id->pMethods->xShmMap(id, iPage, pgsz, bExtend, pp); } +#if SQLITE_MAX_MMAP_SIZE>0 +/* The real implementation of xFetch and xUnfetch */ +int sqlite3OsFetch(sqlite3_file *id, i64 iOff, int iAmt, void **pp){ + DO_OS_MALLOC_TEST(id); + return id->pMethods->xFetch(id, iOff, iAmt, pp); +} +int sqlite3OsUnfetch(sqlite3_file *id, i64 iOff, void *p){ + return id->pMethods->xUnfetch(id, iOff, p); +} +#else +/* No-op stubs to use when memory-mapped I/O is disabled */ +int sqlite3OsFetch(sqlite3_file *id, i64 iOff, int iAmt, void **pp){ + *pp = 0; + return SQLITE_OK; +} +int sqlite3OsUnfetch(sqlite3_file *id, i64 iOff, void *p){ + return SQLITE_OK; +} +#endif + /* ** The next group of routines are convenience wrappers around the ** VFS methods. diff --git a/src/os.h b/src/os.h index 1ec7d4ba11..070a2ddd17 100644 --- a/src/os.h +++ b/src/os.h @@ -99,14 +99,6 @@ # define SQLITE_OS_WINRT 0 #endif -/* -** When compiled for WinCE or WinRT, there is no concept of the current -** directory. - */ -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT -# define SQLITE_CURDIR 1 -#endif - /* If the SET_FULLSYNC macro is not defined above, then make it ** a no-op */ @@ -259,6 +251,8 @@ int sqlite3OsShmMap(sqlite3_file *,int,int,int,void volatile **); int sqlite3OsShmLock(sqlite3_file *id, int, int, int); void sqlite3OsShmBarrier(sqlite3_file *id); int sqlite3OsShmUnmap(sqlite3_file *id, int); +int sqlite3OsFetch(sqlite3_file *id, i64, int, void **); +int sqlite3OsUnfetch(sqlite3_file *, i64, void *); /* diff --git a/src/os_unix.c b/src/os_unix.c index 84b1594ac3..7448afe4c7 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -225,6 +225,11 @@ struct unixFile { const char *zPath; /* Name of the file */ unixShm *pShm; /* Shared memory segment information */ int szChunk; /* Configured by FCNTL_CHUNK_SIZE */ + int nFetchOut; /* Number of outstanding xFetch refs */ + sqlite3_int64 mmapSize; /* Usable size of mapping at pMapRegion */ + sqlite3_int64 mmapSizeActual; /* Actual size of mapping at pMapRegion */ + sqlite3_int64 mmapSizeMax; /* Configured FCNTL_MMAP_SIZE value */ + void *pMapRegion; /* Memory mapped region */ #ifdef __QNXNTO__ int sectorSize; /* Device sector size */ int deviceCharacteristics; /* Precomputed device characteristics */ @@ -249,7 +254,9 @@ struct unixFile { unsigned char transCntrChng; /* True if the transaction counter changed */ unsigned char dbUpdate; /* True if any part of database file changed */ unsigned char inNormalWrite; /* True if in a normal write operation */ + #endif + #ifdef SQLITE_TEST /* In test mode, increase the size of this structure a bit so that ** it is larger than the struct CrashFile defined in test6.c. @@ -273,6 +280,7 @@ struct unixFile { #define UNIXFILE_DELETE 0x20 /* Delete on close */ #define UNIXFILE_URI 0x40 /* Filename might have query parameters */ #define UNIXFILE_NOLOCK 0x80 /* Do no file locking */ +#define UNIXFILE_WARNED 0x0100 /* verifyDbFile() warnings have been issued */ /* ** Include code that is common to all os_*.c files @@ -306,6 +314,17 @@ struct unixFile { #define threadid 0 #endif +/* +** HAVE_MREMAP defaults to true on Linux and false everywhere else. +*/ +#if !defined(HAVE_MREMAP) +# if defined(__linux__) && defined(_GNU_SOURCE) +# define HAVE_MREMAP 1 +# else +# define HAVE_MREMAP 0 +# endif +#endif + /* ** Different Unix systems declare open() in different ways. Same use ** open(const char*,int,mode_t). Others use open(const char*,int,...). @@ -437,6 +456,19 @@ static struct unix_syscall { { "fchown", (sqlite3_syscall_ptr)posixFchown, 0 }, #define osFchown ((int(*)(int,uid_t,gid_t))aSyscall[20].pCurrent) + { "mmap", (sqlite3_syscall_ptr)mmap, 0 }, +#define osMmap ((void*(*)(void*,size_t,int,int,int,off_t))aSyscall[21].pCurrent) + + { "munmap", (sqlite3_syscall_ptr)munmap, 0 }, +#define osMunmap ((void*(*)(void*,size_t))aSyscall[22].pCurrent) + +#if HAVE_MREMAP + { "mremap", (sqlite3_syscall_ptr)mremap, 0 }, +#else + { "mremap", (sqlite3_syscall_ptr)0, 0 }, +#endif +#define osMremap ((void*(*)(void*,size_t,size_t,int,...))aSyscall[23].pCurrent) + }; /* End of the overrideable system calls */ /* @@ -768,7 +800,6 @@ static int sqliteErrorFromPosixError(int posixError, int sqliteIOErr) { } - /****************************************************************************** ****************** Begin Unique File ID Utility Used By VxWorks *************** ** @@ -1104,7 +1135,6 @@ static int unixLogErrorAtLine( zErr = strerror(iErrno); #endif - assert( errcode!=SQLITE_OK ); if( zPath==0 ) zPath = ""; sqlite3_log(errcode, "os_unix.c:%d: (%d) %s(%s) - %s", @@ -1270,6 +1300,50 @@ static int findInodeInfo( } +/* +** Check a unixFile that is a database. Verify the following: +** +** (1) There is exactly one hard link on the file +** (2) The file is not a symbolic link +** (3) The file has not been renamed or unlinked +** +** Issue sqlite3_log(SQLITE_WARNING,...) messages if anything is not right. +*/ +static void verifyDbFile(unixFile *pFile){ + struct stat buf; + int rc; + if( pFile->ctrlFlags & UNIXFILE_WARNED ){ + /* One or more of the following warnings have already been issued. Do not + ** repeat them so as not to clutter the error log */ + return; + } + rc = osFstat(pFile->h, &buf); + if( rc!=0 ){ + sqlite3_log(SQLITE_WARNING, "cannot fstat db file %s", pFile->zPath); + pFile->ctrlFlags |= UNIXFILE_WARNED; + return; + } + if( buf.st_nlink==0 && (pFile->ctrlFlags & UNIXFILE_DELETE)==0 ){ + sqlite3_log(SQLITE_WARNING, "file unlinked while open: %s", pFile->zPath); + pFile->ctrlFlags |= UNIXFILE_WARNED; + return; + } + if( buf.st_nlink>1 ){ + sqlite3_log(SQLITE_WARNING, "multiple links to file: %s", pFile->zPath); + pFile->ctrlFlags |= UNIXFILE_WARNED; + return; + } + if( pFile->pInode!=0 + && ((rc = osStat(pFile->zPath, &buf))!=0 + || buf.st_ino!=pFile->pInode->fileId.ino) + ){ + sqlite3_log(SQLITE_WARNING, "file renamed while open: %s", pFile->zPath); + pFile->ctrlFlags |= UNIXFILE_WARNED; + return; + } +} + + /* ** This routine checks if there is a RESERVED lock held on the specified ** file by this or any other process. If such a lock is held, set *pResOut @@ -1800,9 +1874,13 @@ end_unlock: ** the requested locking level, this routine is a no-op. */ static int unixUnlock(sqlite3_file *id, int eFileLock){ + assert( eFileLock==SHARED_LOCK || ((unixFile *)id)->nFetchOut==0 ); return posixUnlock(id, eFileLock, 0); } +static int unixMapfile(unixFile *pFd, i64 nByte); +static void unixUnmapfile(unixFile *pFd); + /* ** This function performs the parts of the "close file" operation ** common to all locking schemes. It closes the directory and file @@ -1815,6 +1893,7 @@ static int unixUnlock(sqlite3_file *id, int eFileLock){ */ static int closeUnixFile(sqlite3_file *id){ unixFile *pFile = (unixFile*)id; + unixUnmapfile(pFile); if( pFile->h>=0 ){ robust_close(pFile, pFile->h, __LINE__); pFile->h = -1; @@ -1841,6 +1920,7 @@ static int closeUnixFile(sqlite3_file *id){ static int unixClose(sqlite3_file *id){ int rc = SQLITE_OK; unixFile *pFile = (unixFile *)id; + verifyDbFile(pFile); unixUnlock(id, NO_LOCK); unixEnterMutex(); @@ -3082,6 +3162,23 @@ static int unixRead( ); #endif +#if SQLITE_MAX_MMAP_SIZE>0 + /* Deal with as much of this read request as possible by transfering + ** data from the memory mapping using memcpy(). */ + if( offsetmmapSize ){ + if( offset+amt <= pFile->mmapSize ){ + memcpy(pBuf, &((u8 *)(pFile->pMapRegion))[offset], amt); + return SQLITE_OK; + }else{ + int nCopy = pFile->mmapSize - offset; + memcpy(pBuf, &((u8 *)(pFile->pMapRegion))[offset], nCopy); + pBuf = &((u8 *)pBuf)[nCopy]; + amt -= nCopy; + offset += nCopy; + } + } +#endif + got = seekAndRead(pFile, offset, pBuf, amt); if( got==amt ){ return SQLITE_OK; @@ -3186,6 +3283,23 @@ static int unixWrite( } #endif +#if SQLITE_MAX_MMAP_SIZE>0 + /* Deal with as much of this write request as possible by transfering + ** data from the memory mapping using memcpy(). */ + if( offsetmmapSize ){ + if( offset+amt <= pFile->mmapSize ){ + memcpy(&((u8 *)(pFile->pMapRegion))[offset], pBuf, amt); + return SQLITE_OK; + }else{ + int nCopy = pFile->mmapSize - offset; + memcpy(&((u8 *)(pFile->pMapRegion))[offset], pBuf, nCopy); + pBuf = &((u8 *)pBuf)[nCopy]; + amt -= nCopy; + offset += nCopy; + } + } +#endif + while( amt>0 && (wrote = seekAndWrite(pFile, offset, pBuf, amt))>0 ){ amt -= wrote; offset += wrote; @@ -3468,6 +3582,14 @@ static int unixTruncate(sqlite3_file *id, i64 nByte){ } #endif + /* If the file was just truncated to a size smaller than the currently + ** mapped region, reduce the effective mapping size as well. SQLite will + ** use read() and write() to access data beyond this point from now on. + */ + if( nBytemmapSize ){ + pFile->mmapSize = nByte; + } + return SQLITE_OK; } } @@ -3556,6 +3678,19 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){ } } + if( pFile->mmapSizeMax>0 && nByte>pFile->mmapSize ){ + int rc; + if( pFile->szChunk<=0 ){ + if( robust_ftruncate(pFile->h, nByte) ){ + pFile->lastErrno = errno; + return unixLogError(SQLITE_IOERR_TRUNCATE, "ftruncate", pFile->zPath); + } + } + + rc = unixMapfile(pFile, nByte); + return rc; + } + return SQLITE_OK; } @@ -3623,6 +3758,18 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ } return SQLITE_OK; } + case SQLITE_FCNTL_MMAP_SIZE: { + i64 newLimit = *(i64*)pArg; + if( newLimit>sqlite3GlobalConfig.mxMmap ){ + newLimit = sqlite3GlobalConfig.mxMmap; + } + *(i64*)pArg = pFile->mmapSizeMax; + if( newLimit>=0 ){ + pFile->mmapSizeMax = newLimit; + if( newLimitmmapSize ) pFile->mmapSize = newLimit; + } + return SQLITE_OK; + } #ifdef SQLITE_DEBUG /* The pager calls this method to signal that it has done ** a rollback and that the database is therefore unchanged and @@ -3935,7 +4082,7 @@ static void unixShmPurge(unixFile *pFd){ sqlite3_mutex_free(p->mutex); for(i=0; inRegion; i++){ if( p->h>=0 ){ - munmap(p->apRegion[i], p->szRegion); + osMunmap(p->apRegion[i], p->szRegion); }else{ sqlite3_free(p->apRegion[i]); } @@ -4208,7 +4355,7 @@ static int unixShmMap( while(pShmNode->nRegion<=iRegion){ void *pMem; if( pShmNode->h>=0 ){ - pMem = mmap(0, szRegion, + pMem = osMmap(0, szRegion, pShmNode->isReadonly ? PROT_READ : PROT_READ|PROT_WRITE, MAP_SHARED, pShmNode->h, szRegion*(i64)pShmNode->nRegion ); @@ -4425,6 +4572,236 @@ static int unixShmUnmap( # define unixShmUnmap 0 #endif /* #ifndef SQLITE_OMIT_WAL */ +/* +** If it is currently memory mapped, unmap file pFd. +*/ +static void unixUnmapfile(unixFile *pFd){ + assert( pFd->nFetchOut==0 ); +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFd->pMapRegion ){ + osMunmap(pFd->pMapRegion, pFd->mmapSizeActual); + pFd->pMapRegion = 0; + pFd->mmapSize = 0; + pFd->mmapSizeActual = 0; + } +#endif +} + +#if SQLITE_MAX_MMAP_SIZE>0 +/* +** Return the system page size. +*/ +static int unixGetPagesize(void){ +#if HAVE_MREMAP + return 512; +#elif defined(_BSD_SOURCE) + return getpagesize(); +#else + return (int)sysconf(_SC_PAGESIZE); +#endif +} +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + +#if SQLITE_MAX_MMAP_SIZE>0 +/* +** Attempt to set the size of the memory mapping maintained by file +** descriptor pFd to nNew bytes. Any existing mapping is discarded. +** +** If successful, this function sets the following variables: +** +** unixFile.pMapRegion +** unixFile.mmapSize +** unixFile.mmapSizeActual +** +** If unsuccessful, an error message is logged via sqlite3_log() and +** the three variables above are zeroed. In this case SQLite should +** continue accessing the database using the xRead() and xWrite() +** methods. +*/ +static void unixRemapfile( + unixFile *pFd, /* File descriptor object */ + i64 nNew /* Required mapping size */ +){ + const char *zErr = "mmap"; + int h = pFd->h; /* File descriptor open on db file */ + u8 *pOrig = (u8 *)pFd->pMapRegion; /* Pointer to current file mapping */ + i64 nOrig = pFd->mmapSizeActual; /* Size of pOrig region in bytes */ + u8 *pNew = 0; /* Location of new mapping */ + int flags = PROT_READ; /* Flags to pass to mmap() */ + + assert( pFd->nFetchOut==0 ); + assert( nNew>pFd->mmapSize ); + assert( nNew<=pFd->mmapSizeMax ); + assert( nNew>0 ); + assert( pFd->mmapSizeActual>=pFd->mmapSize ); + assert( MAP_FAILED!=0 ); + + if( (pFd->ctrlFlags & UNIXFILE_RDONLY)==0 ) flags |= PROT_WRITE; + + if( pOrig ){ + const int szSyspage = unixGetPagesize(); + i64 nReuse = (pFd->mmapSize & ~(szSyspage-1)); + u8 *pReq = &pOrig[nReuse]; + + /* Unmap any pages of the existing mapping that cannot be reused. */ + if( nReuse!=nOrig ){ + osMunmap(pReq, nOrig-nReuse); + } + +#if HAVE_MREMAP + pNew = osMremap(pOrig, nReuse, nNew, MREMAP_MAYMOVE); + zErr = "mremap"; +#else + pNew = osMmap(pReq, nNew-nReuse, flags, MAP_SHARED, h, nReuse); + if( pNew!=MAP_FAILED ){ + if( pNew!=pReq ){ + osMunmap(pNew, nNew - nReuse); + pNew = 0; + }else{ + pNew = pOrig; + } + } +#endif + + /* The attempt to extend the existing mapping failed. Free it. */ + if( pNew==MAP_FAILED || pNew==0 ){ + osMunmap(pOrig, nReuse); + } + } + + /* If pNew is still NULL, try to create an entirely new mapping. */ + if( pNew==0 ){ + pNew = osMmap(0, nNew, flags, MAP_SHARED, h, 0); + } + + if( pNew==MAP_FAILED ){ + pNew = 0; + nNew = 0; + unixLogError(SQLITE_OK, zErr, pFd->zPath); + + /* If the mmap() above failed, assume that all subsequent mmap() calls + ** will probably fail too. Fall back to using xRead/xWrite exclusively + ** in this case. */ + pFd->mmapSizeMax = 0; + } + pFd->pMapRegion = (void *)pNew; + pFd->mmapSize = pFd->mmapSizeActual = nNew; +} +#endif + +/* +** Memory map or remap the file opened by file-descriptor pFd (if the file +** is already mapped, the existing mapping is replaced by the new). Or, if +** there already exists a mapping for this file, and there are still +** outstanding xFetch() references to it, this function is a no-op. +** +** If parameter nByte is non-negative, then it is the requested size of +** the mapping to create. Otherwise, if nByte is less than zero, then the +** requested size is the size of the file on disk. The actual size of the +** created mapping is either the requested size or the value configured +** using SQLITE_FCNTL_MMAP_LIMIT, whichever is smaller. +** +** SQLITE_OK is returned if no error occurs (even if the mapping is not +** recreated as a result of outstanding references) or an SQLite error +** code otherwise. +*/ +static int unixMapfile(unixFile *pFd, i64 nByte){ +#if SQLITE_MAX_MMAP_SIZE>0 + i64 nMap = nByte; + int rc; + + assert( nMap>=0 || pFd->nFetchOut==0 ); + if( pFd->nFetchOut>0 ) return SQLITE_OK; + + if( nMap<0 ){ + struct stat statbuf; /* Low-level file information */ + rc = osFstat(pFd->h, &statbuf); + if( rc!=SQLITE_OK ){ + return SQLITE_IOERR_FSTAT; + } + nMap = statbuf.st_size; + } + if( nMap>pFd->mmapSizeMax ){ + nMap = pFd->mmapSizeMax; + } + + if( nMap!=pFd->mmapSize ){ + if( nMap>0 ){ + unixRemapfile(pFd, nMap); + }else{ + unixUnmapfile(pFd); + } + } +#endif + + return SQLITE_OK; +} + +/* +** If possible, return a pointer to a mapping of file fd starting at offset +** iOff. The mapping must be valid for at least nAmt bytes. +** +** If such a pointer can be obtained, store it in *pp and return SQLITE_OK. +** Or, if one cannot but no error occurs, set *pp to 0 and return SQLITE_OK. +** Finally, if an error does occur, return an SQLite error code. The final +** value of *pp is undefined in this case. +** +** If this function does return a pointer, the caller must eventually +** release the reference by calling unixUnfetch(). +*/ +static int unixFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ +#if SQLITE_MAX_MMAP_SIZE>0 + unixFile *pFd = (unixFile *)fd; /* The underlying database file */ +#endif + *pp = 0; + +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFd->mmapSizeMax>0 ){ + if( pFd->pMapRegion==0 ){ + int rc = unixMapfile(pFd, -1); + if( rc!=SQLITE_OK ) return rc; + } + if( pFd->mmapSize >= iOff+nAmt ){ + *pp = &((u8 *)pFd->pMapRegion)[iOff]; + pFd->nFetchOut++; + } + } +#endif + return SQLITE_OK; +} + +/* +** If the third argument is non-NULL, then this function releases a +** reference obtained by an earlier call to unixFetch(). The second +** argument passed to this function must be the same as the corresponding +** argument that was passed to the unixFetch() invocation. +** +** Or, if the third argument is NULL, then this function is being called +** to inform the VFS layer that, according to POSIX, any existing mapping +** may now be invalid and should be unmapped. +*/ +static int unixUnfetch(sqlite3_file *fd, i64 iOff, void *p){ + unixFile *pFd = (unixFile *)fd; /* The underlying database file */ + UNUSED_PARAMETER(iOff); + + /* If p==0 (unmap the entire file) then there must be no outstanding + ** xFetch references. Or, if p!=0 (meaning it is an xFetch reference), + ** then there must be at least one outstanding. */ + assert( (p==0)==(pFd->nFetchOut==0) ); + + /* If p!=0, it must match the iOff value. */ + assert( p==0 || p==&((u8 *)pFd->pMapRegion)[iOff] ); + + if( p ){ + pFd->nFetchOut--; + }else{ + unixUnmapfile(pFd); + } + + assert( pFd->nFetchOut>=0 ); + return SQLITE_OK; +} + /* ** Here ends the implementation of all sqlite3_file methods. ** @@ -4483,7 +4860,9 @@ static const sqlite3_io_methods METHOD = { \ unixShmMap, /* xShmMap */ \ unixShmLock, /* xShmLock */ \ unixShmBarrier, /* xShmBarrier */ \ - unixShmUnmap /* xShmUnmap */ \ + unixShmUnmap, /* xShmUnmap */ \ + unixFetch, /* xFetch */ \ + unixUnfetch, /* xUnfetch */ \ }; \ static const sqlite3_io_methods *FINDER##Impl(const char *z, unixFile *p){ \ UNUSED_PARAMETER(z); UNUSED_PARAMETER(p); \ @@ -4500,7 +4879,7 @@ static const sqlite3_io_methods *(*const FINDER)(const char*,unixFile *p) \ IOMETHODS( posixIoFinder, /* Finder function name */ posixIoMethods, /* sqlite3_io_methods object name */ - 2, /* shared memory is enabled */ + 3, /* shared memory and mmap are enabled */ unixClose, /* xClose method */ unixLock, /* xLock method */ unixUnlock, /* xUnlock method */ @@ -4751,6 +5130,7 @@ static int fillInUnixFile( pNew->pVfs = pVfs; pNew->zPath = zFilename; pNew->ctrlFlags = (u8)ctrlFlags; + pNew->mmapSizeMax = sqlite3GlobalConfig.mxMmap; if( sqlite3_uri_boolean(((ctrlFlags & UNIXFILE_URI) ? zFilename : 0), "psow", SQLITE_POWERSAFE_OVERWRITE) ){ pNew->ctrlFlags |= UNIXFILE_PSOW; @@ -4895,6 +5275,7 @@ static int fillInUnixFile( }else{ pNew->pMethod = pLockingStyle; OpenCounter(+1); + verifyDbFile(pNew); } return rc; } @@ -6988,7 +7369,7 @@ int sqlite3_os_init(void){ /* Double-check that the aSyscall[] array has been constructed ** correctly. See ticket [bb3a86e890c8e96ab] */ - assert( ArraySize(aSyscall)==21 ); + assert( ArraySize(aSyscall)==24 ); /* Register all VFSes defined in the aVfs[] array */ for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){ diff --git a/src/os_win.c b/src/os_win.c index 4fb6b02a86..12eefed8be 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -150,11 +150,20 @@ struct winFile { winceLock local; /* Locks obtained by this instance of winFile */ winceLock *shared; /* Global shared lock memory for the file */ #endif +#if SQLITE_MAX_MMAP_SIZE>0 + int nFetchOut; /* Number of outstanding xFetch references */ + HANDLE hMap; /* Handle for accessing memory mapping */ + void *pMapRegion; /* Area memory mapped */ + sqlite3_int64 mmapSize; /* Usable size of mapped region */ + sqlite3_int64 mmapSizeActual; /* Actual size of mapped region */ + sqlite3_int64 mmapSizeMax; /* Configured FCNTL_MMAP_SIZE value */ +#endif }; /* ** Allowed values for winFile.ctrlFlags */ +#define WINFILE_RDONLY 0x02 /* Connection is read only */ #define WINFILE_PERSIST_WAL 0x04 /* Persistent WAL mode */ #define WINFILE_PSOW 0x10 /* SQLITE_IOCAP_POWERSAFE_OVERWRITE */ @@ -2061,6 +2070,11 @@ static int seekWinFile(winFile *pFile, sqlite3_int64 iOffset){ #endif } +#if SQLITE_MAX_MMAP_SIZE>0 +/* Forward references to VFS methods */ +static int winUnmapfile(winFile*); +#endif + /* ** Close a file. ** @@ -2082,6 +2096,12 @@ static int winClose(sqlite3_file *id){ #endif OSTRACE(("CLOSE %d\n", pFile->h)); assert( pFile->h!=NULL && pFile->h!=INVALID_HANDLE_VALUE ); + +#if SQLITE_MAX_MMAP_SIZE>0 + rc = winUnmapfile(pFile); + if( rc!=SQLITE_OK ) return rc; +#endif + do{ rc = osCloseHandle(pFile->h); /* SimulateIOError( rc=0; cnt=MX_CLOSE_ATTEMPT; ); */ @@ -2130,9 +2150,27 @@ static int winRead( int nRetry = 0; /* Number of retrys */ assert( id!=0 ); + assert( amt>0 ); SimulateIOError(return SQLITE_IOERR_READ); OSTRACE(("READ %d lock=%d\n", pFile->h, pFile->locktype)); +#if SQLITE_MAX_MMAP_SIZE>0 + /* Deal with as much of this read request as possible by transfering + ** data from the memory mapping using memcpy(). */ + if( offsetmmapSize ){ + if( offset+amt <= pFile->mmapSize ){ + memcpy(pBuf, &((u8 *)(pFile->pMapRegion))[offset], amt); + return SQLITE_OK; + }else{ + int nCopy = (int)(pFile->mmapSize - offset); + memcpy(pBuf, &((u8 *)(pFile->pMapRegion))[offset], nCopy); + pBuf = &((u8 *)pBuf)[nCopy]; + amt -= nCopy; + offset += nCopy; + } + } +#endif + #if SQLITE_OS_WINCE if( seekWinFile(pFile, offset) ){ return SQLITE_FULL; @@ -2182,6 +2220,23 @@ static int winWrite( OSTRACE(("WRITE %d lock=%d\n", pFile->h, pFile->locktype)); +#if SQLITE_MAX_MMAP_SIZE>0 + /* Deal with as much of this write request as possible by transfering + ** data from the memory mapping using memcpy(). */ + if( offsetmmapSize ){ + if( offset+amt <= pFile->mmapSize ){ + memcpy(&((u8 *)(pFile->pMapRegion))[offset], pBuf, amt); + return SQLITE_OK; + }else{ + int nCopy = (int)(pFile->mmapSize - offset); + memcpy(&((u8 *)(pFile->pMapRegion))[offset], pBuf, nCopy); + pBuf = &((u8 *)pBuf)[nCopy]; + amt -= nCopy; + offset += nCopy; + } + } +#endif + #if SQLITE_OS_WINCE rc = seekWinFile(pFile, offset); if( rc==0 ){ @@ -2249,6 +2304,7 @@ static int winWrite( static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){ winFile *pFile = (winFile*)id; /* File handle object */ int rc = SQLITE_OK; /* Return code for this function */ + DWORD lastErrno; assert( pFile ); @@ -2267,13 +2323,24 @@ static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){ /* SetEndOfFile() returns non-zero when successful, or zero when it fails. */ if( seekWinFile(pFile, nByte) ){ rc = winLogError(SQLITE_IOERR_TRUNCATE, pFile->lastErrno, - "winTruncate1", pFile->zPath); - }else if( 0==osSetEndOfFile(pFile->h) ){ - pFile->lastErrno = osGetLastError(); + "winTruncate1", pFile->zPath); + }else if( 0==osSetEndOfFile(pFile->h) && + ((lastErrno = osGetLastError())!=ERROR_USER_MAPPED_FILE) ){ + pFile->lastErrno = lastErrno; rc = winLogError(SQLITE_IOERR_TRUNCATE, pFile->lastErrno, - "winTruncate2", pFile->zPath); + "winTruncate2", pFile->zPath); } +#if SQLITE_MAX_MMAP_SIZE>0 + /* If the file was truncated to a size smaller than the currently + ** mapped region, reduce the effective mapping size as well. SQLite will + ** use read() and write() to access data beyond this point from now on. + */ + if( pFile->pMapRegion && nBytemmapSize ){ + pFile->mmapSize = nByte; + } +#endif + OSTRACE(("TRUNCATE %d %lld %s\n", pFile->h, nByte, rc ? "failed" : "ok")); return rc; } @@ -2781,6 +2848,17 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){ } return SQLITE_OK; } +#if SQLITE_MAX_MMAP_SIZE>0 + case SQLITE_FCNTL_MMAP_SIZE: { + i64 newLimit = *(i64*)pArg; + if( newLimit>sqlite3GlobalConfig.mxMmap ){ + newLimit = sqlite3GlobalConfig.mxMmap; + } + *(i64*)pArg = pFile->mmapSizeMax; + if( newLimit>=0 ) pFile->mmapSizeMax = newLimit; + return SQLITE_OK; + } +#endif } return SQLITE_NOTFOUND; } @@ -3451,6 +3529,192 @@ shmpage_out: # define winShmUnmap 0 #endif /* #ifndef SQLITE_OMIT_WAL */ +/* +** Cleans up the mapped region of the specified file, if any. +*/ +#if SQLITE_MAX_MMAP_SIZE>0 +static int winUnmapfile(winFile *pFile){ + assert( pFile!=0 ); + if( pFile->pMapRegion ){ + if( !osUnmapViewOfFile(pFile->pMapRegion) ){ + pFile->lastErrno = osGetLastError(); + return winLogError(SQLITE_IOERR_MMAP, pFile->lastErrno, + "winUnmap1", pFile->zPath); + } + pFile->pMapRegion = 0; + pFile->mmapSize = 0; + pFile->mmapSizeActual = 0; + } + if( pFile->hMap!=NULL ){ + if( !osCloseHandle(pFile->hMap) ){ + pFile->lastErrno = osGetLastError(); + return winLogError(SQLITE_IOERR_MMAP, pFile->lastErrno, + "winUnmap2", pFile->zPath); + } + pFile->hMap = NULL; + } + return SQLITE_OK; +} + +/* +** Memory map or remap the file opened by file-descriptor pFd (if the file +** is already mapped, the existing mapping is replaced by the new). Or, if +** there already exists a mapping for this file, and there are still +** outstanding xFetch() references to it, this function is a no-op. +** +** If parameter nByte is non-negative, then it is the requested size of +** the mapping to create. Otherwise, if nByte is less than zero, then the +** requested size is the size of the file on disk. The actual size of the +** created mapping is either the requested size or the value configured +** using SQLITE_FCNTL_MMAP_SIZE, whichever is smaller. +** +** SQLITE_OK is returned if no error occurs (even if the mapping is not +** recreated as a result of outstanding references) or an SQLite error +** code otherwise. +*/ +static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ + sqlite3_int64 nMap = nByte; + int rc; + + assert( nMap>=0 || pFd->nFetchOut==0 ); + if( pFd->nFetchOut>0 ) return SQLITE_OK; + + if( nMap<0 ){ + rc = winFileSize((sqlite3_file*)pFd, &nMap); + if( rc ){ + return SQLITE_IOERR_FSTAT; + } + } + if( nMap>pFd->mmapSizeMax ){ + nMap = pFd->mmapSizeMax; + } + nMap &= ~(sqlite3_int64)(winSysInfo.dwPageSize - 1); + + if( nMap==0 && pFd->mmapSize>0 ){ + winUnmapfile(pFd); + } + if( nMap!=pFd->mmapSize ){ + void *pNew = 0; + DWORD protect = PAGE_READONLY; + DWORD flags = FILE_MAP_READ; + + winUnmapfile(pFd); + if( (pFd->ctrlFlags & WINFILE_RDONLY)==0 ){ + protect = PAGE_READWRITE; + flags |= FILE_MAP_WRITE; + } +#if SQLITE_OS_WINRT + pFd->hMap = osCreateFileMappingFromApp(pFd->h, NULL, protect, nMap, NULL); +#elif defined(SQLITE_WIN32_HAS_WIDE) + pFd->hMap = osCreateFileMappingW(pFd->h, NULL, protect, + (DWORD)((nMap>>32) & 0xffffffff), + (DWORD)(nMap & 0xffffffff), NULL); +#elif defined(SQLITE_WIN32_HAS_ANSI) + pFd->hMap = osCreateFileMappingA(pFd->h, NULL, protect, + (DWORD)((nMap>>32) & 0xffffffff), + (DWORD)(nMap & 0xffffffff), NULL); +#endif + if( pFd->hMap==NULL ){ + pFd->lastErrno = osGetLastError(); + rc = winLogError(SQLITE_IOERR_MMAP, pFd->lastErrno, + "winMapfile", pFd->zPath); + /* Log the error, but continue normal operation using xRead/xWrite */ + return SQLITE_OK; + } + assert( (nMap % winSysInfo.dwPageSize)==0 ); +#if SQLITE_OS_WINRT + pNew = osMapViewOfFileFromApp(pFd->hMap, flags, 0, nMap); +#else + assert( sizeof(SIZE_T)==sizeof(sqlite3_int64) || nMap<=0xffffffff ); + pNew = osMapViewOfFile(pFd->hMap, flags, 0, 0, (SIZE_T)nMap); +#endif + if( pNew==NULL ){ + osCloseHandle(pFd->hMap); + pFd->hMap = NULL; + pFd->lastErrno = osGetLastError(); + winLogError(SQLITE_IOERR_MMAP, pFd->lastErrno, + "winMapfile", pFd->zPath); + return SQLITE_OK; + } + pFd->pMapRegion = pNew; + pFd->mmapSize = nMap; + pFd->mmapSizeActual = nMap; + } + + return SQLITE_OK; +} +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + +/* +** If possible, return a pointer to a mapping of file fd starting at offset +** iOff. The mapping must be valid for at least nAmt bytes. +** +** If such a pointer can be obtained, store it in *pp and return SQLITE_OK. +** Or, if one cannot but no error occurs, set *pp to 0 and return SQLITE_OK. +** Finally, if an error does occur, return an SQLite error code. The final +** value of *pp is undefined in this case. +** +** If this function does return a pointer, the caller must eventually +** release the reference by calling unixUnfetch(). +*/ +static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ +#if SQLITE_MAX_MMAP_SIZE>0 + winFile *pFd = (winFile*)fd; /* The underlying database file */ +#endif + *pp = 0; + +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFd->mmapSizeMax>0 ){ + if( pFd->pMapRegion==0 ){ + int rc = winMapfile(pFd, -1); + if( rc!=SQLITE_OK ) return rc; + } + if( pFd->mmapSize >= iOff+nAmt ){ + *pp = &((u8 *)pFd->pMapRegion)[iOff]; + pFd->nFetchOut++; + } + } +#endif + return SQLITE_OK; +} + +/* +** If the third argument is non-NULL, then this function releases a +** reference obtained by an earlier call to unixFetch(). The second +** argument passed to this function must be the same as the corresponding +** argument that was passed to the unixFetch() invocation. +** +** Or, if the third argument is NULL, then this function is being called +** to inform the VFS layer that, according to POSIX, any existing mapping +** may now be invalid and should be unmapped. +*/ +static int winUnfetch(sqlite3_file *fd, i64 iOff, void *p){ +#if SQLITE_MAX_MMAP_SIZE>0 + winFile *pFd = (winFile*)fd; /* The underlying database file */ + + /* If p==0 (unmap the entire file) then there must be no outstanding + ** xFetch references. Or, if p!=0 (meaning it is an xFetch reference), + ** then there must be at least one outstanding. */ + assert( (p==0)==(pFd->nFetchOut==0) ); + + /* If p!=0, it must match the iOff value. */ + assert( p==0 || p==&((u8 *)pFd->pMapRegion)[iOff] ); + + if( p ){ + pFd->nFetchOut--; + }else{ + /* FIXME: If Windows truly always prevents truncating or deleting a + ** file while a mapping is held, then the following winUnmapfile() call + ** is unnecessary can can be omitted - potentially improving + ** performance. */ + winUnmapfile(pFd); + } + + assert( pFd->nFetchOut>=0 ); +#endif + return SQLITE_OK; +} + /* ** Here ends the implementation of all sqlite3_file methods. ** @@ -3462,7 +3726,7 @@ shmpage_out: ** sqlite3_file for win32. */ static const sqlite3_io_methods winIoMethod = { - 2, /* iVersion */ + 3, /* iVersion */ winClose, /* xClose */ winRead, /* xRead */ winWrite, /* xWrite */ @@ -3478,7 +3742,9 @@ static const sqlite3_io_methods winIoMethod = { winShmMap, /* xShmMap */ winShmLock, /* xShmLock */ winShmBarrier, /* xShmBarrier */ - winShmUnmap /* xShmUnmap */ + winShmUnmap, /* xShmUnmap */ + winFetch, /* xFetch */ + winUnfetch /* xUnfetch */ }; /**************************************************************************** @@ -3654,9 +3920,7 @@ static int winOpen( int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE); int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE); int isCreate = (flags & SQLITE_OPEN_CREATE); -#ifndef NDEBUG int isReadonly = (flags & SQLITE_OPEN_READONLY); -#endif int isReadWrite = (flags & SQLITE_OPEN_READWRITE); #ifndef NDEBUG @@ -3867,11 +4131,21 @@ static int winOpen( pFile->pMethod = &winIoMethod; pFile->pVfs = pVfs; pFile->h = h; + if( isReadonly ){ + pFile->ctrlFlags |= WINFILE_RDONLY; + } if( sqlite3_uri_boolean(zName, "psow", SQLITE_POWERSAFE_OVERWRITE) ){ pFile->ctrlFlags |= WINFILE_PSOW; } pFile->lastErrno = NO_ERROR; pFile->zPath = zName; +#if SQLITE_MAX_MMAP_SIZE>0 + pFile->hMap = NULL; + pFile->pMapRegion = 0; + pFile->mmapSize = 0; + pFile->mmapSizeActual = 0; + pFile->mmapSizeMax = sqlite3GlobalConfig.mxMmap; +#endif OpenCounter(+1); return rc; @@ -4500,7 +4774,6 @@ int sqlite3_os_init(void){ ** correctly. See ticket [bb3a86e890c8e96ab] */ assert( ArraySize(aSyscall)==74 ); -#ifndef SQLITE_OMIT_WAL /* get memory map allocation granularity */ memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); #if SQLITE_OS_WINRT @@ -4508,8 +4781,8 @@ int sqlite3_os_init(void){ #else osGetSystemInfo(&winSysInfo); #endif - assert(winSysInfo.dwAllocationGranularity > 0); -#endif + assert( winSysInfo.dwAllocationGranularity>0 ); + assert( winSysInfo.dwPageSize>0 ); sqlite3_vfs_register(&winVfs, 1); return SQLITE_OK; diff --git a/src/pager.c b/src/pager.c index dfa179d542..1c8b5df651 100644 --- a/src/pager.c +++ b/src/pager.c @@ -655,6 +655,11 @@ struct Pager { PagerSavepoint *aSavepoint; /* Array of active savepoints */ int nSavepoint; /* Number of elements in aSavepoint[] */ char dbFileVers[16]; /* Changes whenever database file changes */ + + u8 bUseFetch; /* True to use xFetch() */ + int nMmapOut; /* Number of mmap pages currently outstanding */ + sqlite3_int64 szMmap; /* Desired maximum mmap size */ + PgHdr *pMmapFreelist; /* List of free mmap page headers (pDirty) */ /* ** End of the routinely-changing class members ***************************************************************************/ @@ -765,6 +770,16 @@ static const unsigned char aJournalMagic[] = { # define MEMDB pPager->memDb #endif +/* +** The macro USEFETCH is true if we are allowed to use the xFetch and xUnfetch +** interfaces to access the database using memory-mapped I/O. +*/ +#if SQLITE_MAX_MMAP_SIZE>0 +# define USEFETCH(x) ((x)->bUseFetch) +#else +# define USEFETCH(x) 0 +#endif + /* ** The maximum legal page number is (2^31 - 1). */ @@ -2252,7 +2267,7 @@ static int pager_playback_one_page( i64 ofst = (pgno-1)*(i64)pPager->pageSize; testcase( !isSavepnt && pPg!=0 && (pPg->flags&PGHDR_NEED_SYNC)!=0 ); assert( !pagerUseWal(pPager) ); - rc = sqlite3OsWrite(pPager->fd, (u8*)aData, pPager->pageSize, ofst); + rc = sqlite3OsWrite(pPager->fd, (u8 *)aData, pPager->pageSize, ofst); if( pgno>pPager->dbFileSize ){ pPager->dbFileSize = pgno; } @@ -2643,6 +2658,7 @@ static int pager_playback(Pager *pPager, int isHot){ int res = 1; /* Value returned by sqlite3OsAccess() */ char *zMaster = 0; /* Name of master journal file if any */ int needPagerReset; /* True to reset page prior to first page rollback */ + int nPlayback = 0; /* Total number of pages restored from journal */ /* Figure out how many records are in the journal. Abort early if ** the journal is empty. @@ -2743,7 +2759,9 @@ static int pager_playback(Pager *pPager, int isHot){ needPagerReset = 0; } rc = pager_playback_one_page(pPager,&pPager->journalOff,0,1,0); - if( rc!=SQLITE_OK ){ + if( rc==SQLITE_OK ){ + nPlayback++; + }else{ if( rc==SQLITE_DONE ){ pPager->journalOff = szJ; break; @@ -2813,6 +2831,10 @@ end_playback: rc = pager_delmaster(pPager, zMaster); testcase( rc!=SQLITE_OK ); } + if( isHot && nPlayback ){ + sqlite3_log(SQLITE_NOTICE_RECOVER_ROLLBACK, "recovered %d pages from %s", + nPlayback, pPager->zJournal); + } /* The Pager.sectorSize variable may have been updated while rolling ** back a journal created by a process with a different sector size @@ -2834,11 +2856,10 @@ end_playback: ** If an IO error occurs, then the IO error is returned to the caller. ** Otherwise, SQLITE_OK is returned. */ -static int readDbPage(PgHdr *pPg){ +static int readDbPage(PgHdr *pPg, u32 iFrame){ Pager *pPager = pPg->pPager; /* Pager object associated with page pPg */ Pgno pgno = pPg->pgno; /* Page number to read */ int rc = SQLITE_OK; /* Return code */ - int isInWal = 0; /* True if page is in log file */ int pgsz = pPager->pageSize; /* Number of bytes to read */ assert( pPager->eState>=PAGER_READER && !MEMDB ); @@ -2850,11 +2871,10 @@ static int readDbPage(PgHdr *pPg){ return SQLITE_OK; } - if( pagerUseWal(pPager) ){ + if( iFrame ){ /* Try to pull the page from the write-ahead log. */ - rc = sqlite3WalRead(pPager->pWal, pgno, &isInWal, pgsz, pPg->pData); - } - if( rc==SQLITE_OK && !isInWal ){ + rc = sqlite3WalReadFrame(pPager->pWal, iFrame, pgsz, pPg->pData); + }else{ i64 iOffset = (pgno-1)*(i64)pPager->pageSize; rc = sqlite3OsRead(pPager->fd, pPg->pData, pgsz, iOffset); if( rc==SQLITE_IOERR_SHORT_READ ){ @@ -2933,12 +2953,17 @@ static int pagerUndoCallback(void *pCtx, Pgno iPg){ Pager *pPager = (Pager *)pCtx; PgHdr *pPg; + assert( pagerUseWal(pPager) ); pPg = sqlite3PagerLookup(pPager, iPg); if( pPg ){ if( sqlite3PcachePageRefcount(pPg)==1 ){ sqlite3PcacheDrop(pPg); }else{ - rc = readDbPage(pPg); + u32 iFrame = 0; + rc = sqlite3WalFindFrame(pPager->pWal, pPg->pgno, &iFrame); + if( rc==SQLITE_OK ){ + rc = readDbPage(pPg, iFrame); + } if( rc==SQLITE_OK ){ pPager->xReiniter(pPg); } @@ -3082,6 +3107,7 @@ static int pagerBeginReadTransaction(Pager *pPager){ rc = sqlite3WalBeginReadTransaction(pPager->pWal, &changed); if( rc!=SQLITE_OK || changed ){ pager_reset(pPager); + if( USEFETCH(pPager) ) sqlite3OsUnfetch(pPager->fd, 0, 0); } return rc; @@ -3343,6 +3369,29 @@ void sqlite3PagerSetCachesize(Pager *pPager, int mxPage){ sqlite3PcacheSetCachesize(pPager->pPCache, mxPage); } +/* +** Invoke SQLITE_FCNTL_MMAP_SIZE based on the current value of szMmap. +*/ +static void pagerFixMaplimit(Pager *pPager){ +#if SQLITE_MAX_MMAP_SIZE>0 + sqlite3_file *fd = pPager->fd; + if( isOpen(fd) ){ + sqlite3_int64 sz; + pPager->bUseFetch = (fd->pMethods->iVersion>=3) && pPager->szMmap>0; + sz = pPager->szMmap; + sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_MMAP_SIZE, &sz); + } +#endif +} + +/* +** Change the maximum size of any memory mapping made of the database file. +*/ +void sqlite3PagerSetMmapLimit(Pager *pPager, sqlite3_int64 szMmap){ + pPager->szMmap = szMmap; + pagerFixMaplimit(pPager); +} + /* ** Free as much memory as possible from the pager. */ @@ -3578,6 +3627,7 @@ int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nReserve){ assert( nReserve>=0 && nReserve<1000 ); pPager->nReserve = (i16)nReserve; pagerReportSize(pPager); + pagerFixMaplimit(pPager); } return rc; } @@ -3803,6 +3853,81 @@ static int pagerSyncHotJournal(Pager *pPager){ return rc; } +/* +** Obtain a reference to a memory mapped page object for page number pgno. +** The new object will use the pointer pData, obtained from xFetch(). +** If successful, set *ppPage to point to the new page reference +** and return SQLITE_OK. Otherwise, return an SQLite error code and set +** *ppPage to zero. +** +** Page references obtained by calling this function should be released +** by calling pagerReleaseMapPage(). +*/ +static int pagerAcquireMapPage( + Pager *pPager, /* Pager object */ + Pgno pgno, /* Page number */ + void *pData, /* xFetch()'d data for this page */ + PgHdr **ppPage /* OUT: Acquired page object */ +){ + PgHdr *p; /* Memory mapped page to return */ + + if( pPager->pMmapFreelist ){ + *ppPage = p = pPager->pMmapFreelist; + pPager->pMmapFreelist = p->pDirty; + p->pDirty = 0; + memset(p->pExtra, 0, pPager->nExtra); + }else{ + *ppPage = p = (PgHdr *)sqlite3MallocZero(sizeof(PgHdr) + pPager->nExtra); + if( p==0 ){ + sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1) * pPager->pageSize, pData); + return SQLITE_NOMEM; + } + p->pExtra = (void *)&p[1]; + p->flags = PGHDR_MMAP; + p->nRef = 1; + p->pPager = pPager; + } + + assert( p->pExtra==(void *)&p[1] ); + assert( p->pPage==0 ); + assert( p->flags==PGHDR_MMAP ); + assert( p->pPager==pPager ); + assert( p->nRef==1 ); + + p->pgno = pgno; + p->pData = pData; + pPager->nMmapOut++; + + return SQLITE_OK; +} + +/* +** Release a reference to page pPg. pPg must have been returned by an +** earlier call to pagerAcquireMapPage(). +*/ +static void pagerReleaseMapPage(PgHdr *pPg){ + Pager *pPager = pPg->pPager; + pPager->nMmapOut--; + pPg->pDirty = pPager->pMmapFreelist; + pPager->pMmapFreelist = pPg; + + assert( pPager->fd->pMethods->iVersion>=3 ); + sqlite3OsUnfetch(pPager->fd, (i64)(pPg->pgno-1)*pPager->pageSize, pPg->pData); +} + +/* +** Free all PgHdr objects stored in the Pager.pMmapFreelist list. +*/ +static void pagerFreeMapHdrs(Pager *pPager){ + PgHdr *p; + PgHdr *pNext; + for(p=pPager->pMmapFreelist; p; p=pNext){ + pNext = p->pDirty; + sqlite3_free(p); + } +} + + /* ** Shutdown the page cache. Free all memory and close all files. ** @@ -3823,6 +3948,7 @@ int sqlite3PagerClose(Pager *pPager){ assert( assert_pager_state(pPager) ); disable_simulated_io_errors(); sqlite3BeginBenignMalloc(); + pagerFreeMapHdrs(pPager); /* pPager->errCode = 0; */ pPager->exclusiveMode = 0; #ifndef SQLITE_OMIT_WAL @@ -4084,7 +4210,9 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ ** file size will be. */ assert( rc!=SQLITE_OK || isOpen(pPager->fd) ); - if( rc==SQLITE_OK && pPager->dbSize>pPager->dbHintSize ){ + if( rc==SQLITE_OK + && (pList->pDirty ? pPager->dbSize : pList->pgno+1)>pPager->dbHintSize + ){ sqlite3_int64 szFile = pPager->pageSize * (sqlite3_int64)pPager->dbSize; sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_SIZE_HINT, &szFile); pPager->dbHintSize = pPager->dbSize; @@ -4638,6 +4766,7 @@ int sqlite3PagerOpen( /* pPager->pBusyHandlerArg = 0; */ pPager->xReiniter = xReinit; /* memset(pPager->aHash, 0, sizeof(pPager->aHash)); */ + /* pPager->szMmap = SQLITE_DEFAULT_MMAP_SIZE // will be set by btree.c */ *ppPager = pPager; return SQLITE_OK; @@ -4929,9 +5058,11 @@ int sqlite3PagerSharedLock(Pager *pPager){ ); } - if( !pPager->tempFile - && (pPager->pBackup || sqlite3PcachePagecount(pPager->pPCache)>0) - ){ + if( !pPager->tempFile && ( + pPager->pBackup + || sqlite3PcachePagecount(pPager->pPCache)>0 + || USEFETCH(pPager) + )){ /* The shared-lock has just been acquired on the database file ** and there are already pages in the cache (from a previous ** read or write transaction). Check to see if the database @@ -4957,7 +5088,7 @@ int sqlite3PagerSharedLock(Pager *pPager){ if( nPage>0 ){ IOTRACE(("CKVERS %p %d\n", pPager, sizeof(dbFileVers))); rc = sqlite3OsRead(pPager->fd, &dbFileVers, sizeof(dbFileVers), 24); - if( rc!=SQLITE_OK ){ + if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){ goto failed; } }else{ @@ -4966,6 +5097,16 @@ int sqlite3PagerSharedLock(Pager *pPager){ if( memcmp(pPager->dbFileVers, dbFileVers, sizeof(dbFileVers))!=0 ){ pager_reset(pPager); + + /* Unmap the database file. It is possible that external processes + ** may have truncated the database file and then extended it back + ** to its original size while this process was not holding a lock. + ** In this case there may exist a Pager.pMap mapping that appears + ** to be the right size but is not actually valid. Avoid this + ** possibility by unmapping the db here. */ + if( USEFETCH(pPager) ){ + sqlite3OsUnfetch(pPager->fd, 0, 0); + } } } @@ -5007,7 +5148,7 @@ int sqlite3PagerSharedLock(Pager *pPager){ ** nothing to rollback, so this routine is a no-op. */ static void pagerUnlockIfUnused(Pager *pPager){ - if( (sqlite3PcacheRefCount(pPager->pPCache)==0) ){ + if( pPager->nMmapOut==0 && (sqlite3PcacheRefCount(pPager->pPCache)==0) ){ pagerUnlockAndRollback(pPager); } } @@ -5066,13 +5207,27 @@ int sqlite3PagerAcquire( 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 noContent /* Do not bother reading content from disk if true */ + int flags /* PAGER_ACQUIRE_XXX flags */ ){ - int rc; - PgHdr *pPg; + int rc = SQLITE_OK; + PgHdr *pPg = 0; + u32 iFrame = 0; /* Frame to read from WAL file */ + const int noContent = (flags & PAGER_ACQUIRE_NOCONTENT); + + /* It is acceptable to use a read-only (mmap) page for any page except + ** page 1 if there is no write-transaction open or the ACQUIRE_READONLY + ** flag was specified by the caller. And so long as the db is not a + ** temporary or in-memory database. */ + const int bMmapOk = (pgno!=1 && USEFETCH(pPager) + && (pPager->eState==PAGER_READER || (flags & PAGER_ACQUIRE_READONLY)) +#ifdef SQLITE_HAS_CODEC + && pPager->xCodec==0 +#endif + ); assert( pPager->eState>=PAGER_READER ); assert( assert_pager_state(pPager) ); + assert( noContent==0 || bMmapOk==0 ); if( pgno==0 ){ return SQLITE_CORRUPT_BKPT; @@ -5083,6 +5238,39 @@ int sqlite3PagerAcquire( if( pPager->errCode!=SQLITE_OK ){ rc = pPager->errCode; }else{ + + if( bMmapOk && pagerUseWal(pPager) ){ + rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame); + if( rc!=SQLITE_OK ) goto pager_acquire_err; + } + + if( iFrame==0 && bMmapOk ){ + void *pData = 0; + + rc = sqlite3OsFetch(pPager->fd, + (i64)(pgno-1) * pPager->pageSize, pPager->pageSize, &pData + ); + + if( rc==SQLITE_OK && pData ){ + if( pPager->eState>PAGER_READER ){ + (void)sqlite3PcacheFetch(pPager->pPCache, pgno, 0, &pPg); + } + if( pPg==0 ){ + rc = pagerAcquireMapPage(pPager, pgno, pData, &pPg); + }else{ + sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1)*pPager->pageSize, pData); + } + if( pPg ){ + assert( rc==SQLITE_OK ); + *ppPage = pPg; + return SQLITE_OK; + } + } + if( rc!=SQLITE_OK ){ + goto pager_acquire_err; + } + } + rc = sqlite3PcacheFetch(pPager->pPCache, pgno, 1, ppPage); } @@ -5141,9 +5329,13 @@ int sqlite3PagerAcquire( memset(pPg->pData, 0, pPager->pageSize); IOTRACE(("ZERO %p %d\n", pPager, pgno)); }else{ + if( pagerUseWal(pPager) && bMmapOk==0 ){ + rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame); + if( rc!=SQLITE_OK ) goto pager_acquire_err; + } assert( pPg->pPager==pPager ); pPager->aStat[PAGER_STAT_MISS]++; - rc = readDbPage(pPg); + rc = readDbPage(pPg, iFrame); if( rc!=SQLITE_OK ){ goto pager_acquire_err; } @@ -5196,7 +5388,11 @@ DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){ void sqlite3PagerUnref(DbPage *pPg){ if( pPg ){ Pager *pPager = pPg->pPager; - sqlite3PcacheRelease(pPg); + if( pPg->flags & PGHDR_MMAP ){ + pagerReleaseMapPage(pPg); + }else{ + sqlite3PcacheRelease(pPg); + } pagerUnlockIfUnused(pPager); } } @@ -5531,6 +5727,7 @@ int sqlite3PagerWrite(DbPage *pDbPage){ Pager *pPager = pPg->pPager; Pgno nPagePerSector = (pPager->sectorSize/pPager->pageSize); + assert( (pPg->flags & PGHDR_MMAP)==0 ); assert( pPager->eState>=PAGER_WRITER_LOCKED ); assert( pPager->eState!=PAGER_ERROR ); assert( assert_pager_state(pPager) ); @@ -6087,7 +6284,7 @@ int sqlite3PagerRollback(Pager *pPager){ } assert( pPager->eState==PAGER_READER || rc!=SQLITE_OK ); - assert( rc==SQLITE_OK || rc==SQLITE_FULL + assert( rc==SQLITE_OK || rc==SQLITE_FULL || rc==SQLITE_CORRUPT || rc==SQLITE_NOMEM || (rc&0xFF)==SQLITE_IOERR ); /* If an error occurs during a ROLLBACK, we can no longer trust the pager @@ -6821,11 +7018,12 @@ static int pagerOpenWal(Pager *pPager){ ** (e.g. due to malloc() failure), return an error code. */ if( rc==SQLITE_OK ){ - rc = sqlite3WalOpen(pPager->pVfs, + rc = sqlite3WalOpen(pPager->pVfs, pPager->fd, pPager->zWal, pPager->exclusiveMode, pPager->journalSizeLimit, &pPager->pWal ); } + pagerFixMaplimit(pPager); return rc; } @@ -6916,6 +7114,7 @@ int sqlite3PagerCloseWal(Pager *pPager){ rc = sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, pPager->pageSize, (u8*)pPager->pTmpSpace); pPager->pWal = 0; + pagerFixMaplimit(pPager); } } return rc; diff --git a/src/pager.h b/src/pager.h index 90f8e6af78..6f659136e2 100644 --- a/src/pager.h +++ b/src/pager.h @@ -78,6 +78,12 @@ typedef struct PgHdr DbPage; #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(). +*/ +#define PAGER_ACQUIRE_NOCONTENT 0x01 /* Do not load data from disk */ +#define PAGER_ACQUIRE_READONLY 0x02 /* Read-only page is acceptable */ + /* ** The remainder of this file contains the declarations of the functions ** that make up the Pager sub-system API. See source code comments for @@ -102,6 +108,7 @@ void sqlite3PagerSetBusyhandler(Pager*, int(*)(void *), void *); int sqlite3PagerSetPagesize(Pager*, u32*, int); int sqlite3PagerMaxPageCount(Pager*, int); void sqlite3PagerSetCachesize(Pager*, int); +void sqlite3PagerSetMmapLimit(Pager *, sqlite3_int64); void sqlite3PagerShrink(Pager*); void sqlite3PagerSetSafetyLevel(Pager*,int,int,int); int sqlite3PagerLockingMode(Pager *, int); diff --git a/src/pcache.h b/src/pcache.h index b9135fd859..f4d4ad71c1 100644 --- a/src/pcache.h +++ b/src/pcache.h @@ -53,6 +53,8 @@ struct PgHdr { #define PGHDR_REUSE_UNLIKELY 0x010 /* A hint that reuse is unlikely */ #define PGHDR_DONT_WRITE 0x020 /* Do not write content to disk */ +#define PGHDR_MMAP 0x040 /* This is an mmap page object */ + /* Initialize and shutdown the page cache subsystem */ int sqlite3PcacheInitialize(void); void sqlite3PcacheShutdown(void); diff --git a/src/pragma.c b/src/pragma.c index f6dadbd4a8..65efbd8b21 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -319,7 +319,7 @@ void sqlite3Pragma( int rc; /* return value form SQLITE_FCNTL_PRAGMA */ sqlite3 *db = pParse->db; /* The database connection */ Db *pDb; /* The specific database being pragmaed */ - Vdbe *v = pParse->pVdbe = sqlite3VdbeCreate(db); /* Prepared statement */ + Vdbe *v = sqlite3GetVdbe(pParse); /* Prepared statement */ if( v==0 ) return; sqlite3VdbeRunOnlyOnce(v); @@ -402,11 +402,12 @@ void sqlite3Pragma( static const VdbeOpList getCacheSize[] = { { OP_Transaction, 0, 0, 0}, /* 0 */ { OP_ReadCookie, 0, 1, BTREE_DEFAULT_CACHE_SIZE}, /* 1 */ - { OP_IfPos, 1, 7, 0}, + { OP_IfPos, 1, 8, 0}, { OP_Integer, 0, 2, 0}, { OP_Subtract, 1, 2, 1}, - { OP_IfPos, 1, 7, 0}, + { OP_IfPos, 1, 8, 0}, { OP_Integer, 0, 1, 0}, /* 6 */ + { OP_Noop, 0, 0, 0}, { OP_ResultRow, 1, 1, 0}, }; int addr; @@ -744,6 +745,43 @@ void sqlite3Pragma( } }else + /* + ** PRAGMA [database.]mmap_size(N) + ** + ** Used to set mapping size limit. The mapping size limit is + ** used to limit the aggregate size of all memory mapped regions of the + ** database file. If this parameter is set to zero, then memory mapping + ** is not used at all. If N is negative, then the default memory map + ** limit determined by sqlite3_config(SQLITE_CONFIG_MMAP_SIZE) is set. + ** The parameter N is measured in bytes. + ** + ** This value is advisory. The underlying VFS is free to memory map + ** as little or as much as it wants. Except, if N is set to 0 then the + ** upper layers will never invoke the xFetch interfaces to the VFS. + */ + if( sqlite3StrICmp(zLeft,"mmap_size")==0 ){ + sqlite3_int64 sz; + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + if( zRight ){ + int ii; + sqlite3Atoi64(zRight, &sz, 1000, SQLITE_UTF8); + if( sz<0 ) sz = sqlite3GlobalConfig.szMmap; + if( pId2->n==0 ) db->szMmap = sz; + for(ii=db->nDb-1; ii>=0; ii--){ + if( db->aDb[ii].pBt && (ii==iDb || pId2->n==0) ){ + sqlite3BtreeSetMmapLimit(db->aDb[ii].pBt, sz); + } + } + } + sz = -1; + if( sqlite3_file_control(db,zDb,SQLITE_FCNTL_MMAP_SIZE,&sz)==SQLITE_OK ){ +#if SQLITE_MAX_MMAP_SIZE==0 + sz = 0; +#endif + returnSingleInt(pParse, "mmap_size", sz); + } + }else + /* ** PRAGMA temp_store ** PRAGMA temp_store = "default"|"memory"|"file" diff --git a/src/prepare.c b/src/prepare.c index 26d6c2614f..d78d83cbd8 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -654,7 +654,6 @@ static int sqlite3Prepare( } #endif - assert( db->init.busy==0 || saveSqlFlag==0 ); if( db->init.busy==0 ){ Vdbe *pVdbe = pParse->pVdbe; sqlite3VdbeSetSql(pVdbe, zSql, (int)(pParse->zTail-zSql), saveSqlFlag); diff --git a/src/resolve.c b/src/resolve.c index 0380138678..9b350caf80 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -388,7 +388,10 @@ static int lookupName( ** Note that the expression in the result set should have already been ** resolved by the time the WHERE clause is resolved. */ - if( cnt==0 && (pEList = pNC->pEList)!=0 && zTab==0 ){ + if( (pEList = pNC->pEList)!=0 + && zTab==0 + && ((pNC->ncFlags & NC_AsMaybe)==0 || cnt==0) + ){ for(j=0; jnExpr; j++){ char *zAs = pEList->a[j].zName; if( zAs!=0 && sqlite3StrICmp(zAs, zCol)==0 ){ @@ -479,7 +482,9 @@ static int lookupName( lookupname_end: if( cnt==1 ){ assert( pNC!=0 ); - sqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList); + if( pExpr->op!=TK_AS ){ + sqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList); + } /* Increment the nRef value on all name contexts from TopNC up to ** the point where the name matched. */ for(;;){ @@ -1154,11 +1159,10 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** re-evaluated for each reference to it. */ sNC.pEList = p->pEList; - if( sqlite3ResolveExprNames(&sNC, p->pWhere) || - sqlite3ResolveExprNames(&sNC, p->pHaving) - ){ - return WRC_Abort; - } + if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; + sNC.ncFlags |= NC_AsMaybe; + if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort; + sNC.ncFlags &= ~NC_AsMaybe; /* The ORDER BY and GROUP BY clauses may not refer to terms in ** outer queries diff --git a/src/shell.c b/src/shell.c index c6d7fa3d53..13a2f70cbc 100644 --- a/src/shell.c +++ b/src/shell.c @@ -1552,6 +1552,43 @@ static int booleanValue(char *zArg){ return 0; } +/* +** Interpret zArg as an integer value, possibly with suffixes. +*/ +static sqlite3_int64 integerValue(const char *zArg){ + sqlite3_int64 v = 0; + static const struct { char *zSuffix; int iMult; } aMult[] = { + { "KiB", 1024 }, + { "MiB", 1024*1024 }, + { "GiB", 1024*1024*1024 }, + { "KB", 1000 }, + { "MB", 1000000 }, + { "GB", 1000000000 }, + { "K", 1000 }, + { "M", 1000000 }, + { "G", 1000000000 }, + }; + int i; + int isNeg = 0; + if( zArg[0]=='-' ){ + isNeg = 1; + zArg++; + }else if( zArg[0]=='+' ){ + zArg++; + } + while( isdigit(zArg[0]) ){ + v = v*10 + zArg[0] - '0'; + zArg++; + } + for(i=0; i0x7fff0000 ) szHeap = 0x7fff0000; sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); #endif @@ -3026,6 +3063,9 @@ int main(int argc, char **argv){ extern int sqlite3_multiple_initialize(const char*,int); sqlite3_multiplex_initialize(0, 1); #endif + }else if( strcmp(z,"-mmap")==0 ){ + sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i)); + sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz); }else if( strcmp(z,"-vfs")==0 ){ sqlite3_vfs *pVfs = sqlite3_vfs_find(cmdline_option_value(argc,argv,++i)); if( pVfs ){ @@ -3111,6 +3151,8 @@ int main(int argc, char **argv){ stdin_is_interactive = 0; }else if( strcmp(z,"-heap")==0 ){ i++; + }else if( strcmp(z,"-mmap")==0 ){ + i++; }else if( strcmp(z,"-vfs")==0 ){ i++; #ifdef SQLITE_ENABLE_VFSTRACE @@ -3128,7 +3170,7 @@ int main(int argc, char **argv){ z = cmdline_option_value(argc,argv,++i); if( z[0]=='.' ){ rc = do_meta_command(z, &data); - if( rc && bail_on_error ) return rc; + if( rc && bail_on_error ) return rc==2 ? 0 : rc; }else{ open_db(&data); rc = shell_exec(data.db, z, shell_callback, &data, &zErrMsg); @@ -3152,6 +3194,7 @@ int main(int argc, char **argv){ */ if( zFirstCmd[0]=='.' ){ rc = do_meta_command(zFirstCmd, &data); + if( rc==2 ) rc = 0; }else{ open_db(&data); rc = shell_exec(data.db, zFirstCmd, shell_callback, &data, &zErrMsg); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 016b4d2f22..548a8ffd42 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -420,6 +420,8 @@ int sqlite3_exec( #define SQLITE_FORMAT 24 /* Auxiliary database format error */ #define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ #define SQLITE_NOTADB 26 /* File opened that is not a database file */ +#define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */ +#define SQLITE_WARNING 28 /* Warnings from sqlite3_log() */ #define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ #define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ /* end-of-error-codes */ @@ -470,6 +472,7 @@ int sqlite3_exec( #define SQLITE_IOERR_SHMMAP (SQLITE_IOERR | (21<<8)) #define SQLITE_IOERR_SEEK (SQLITE_IOERR | (22<<8)) #define SQLITE_IOERR_DELETE_NOENT (SQLITE_IOERR | (23<<8)) +#define SQLITE_IOERR_MMAP (SQLITE_IOERR | (24<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) #define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8)) @@ -489,6 +492,8 @@ int sqlite3_exec( #define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7<<8)) #define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) #define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) +#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) +#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) /* ** CAPI3REF: Flags For File Open Operations @@ -728,6 +733,9 @@ struct sqlite3_io_methods { void (*xShmBarrier)(sqlite3_file*); int (*xShmUnmap)(sqlite3_file*, int deleteFlag); /* Methods above are valid for version 2 */ + int (*xFetch)(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp); + int (*xUnfetch)(sqlite3_file*, sqlite3_int64 iOfst, void *p); + /* Methods above are valid for version 3 */ /* Additional methods may be added in future releases */ }; @@ -864,7 +872,8 @@ struct sqlite3_io_methods { ** it is able to override built-in [PRAGMA] statements. ** **
  • [[SQLITE_FCNTL_BUSYHANDLER]] -** ^This file-control may be invoked by SQLite on the database file handle +** ^The [SQLITE_FCNTL_BUSYHANDLER] +** file-control may be invoked by SQLite on the database file handle ** shortly after it is opened in order to provide a custom VFS with access ** to the connections busy-handler callback. The argument is of type (void **) ** - an array of two (void *) values. The first (void *) actually points @@ -875,13 +884,24 @@ struct sqlite3_io_methods { ** current operation. ** **
  • [[SQLITE_FCNTL_TEMPFILENAME]] -** ^Application can invoke this file-control to have SQLite generate a +** ^Application can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control +** to have SQLite generate a ** temporary filename using the same algorithm that is followed to generate ** temporary filenames for TEMP tables and other internal uses. The ** argument should be a char** which will be filled with the filename ** written into memory obtained from [sqlite3_malloc()]. The caller should ** invoke [sqlite3_free()] on the result to avoid a memory leak. ** +**
  • [[SQLITE_FCNTL_MMAP_SIZE]] +** The [SQLITE_FCNTL_MMAP_SIZE] file control is used to query or set the +** maximum number of bytes that will be used for memory-mapped I/O. +** The argument is a pointer to a value of type sqlite3_int64 that +** is an advisory maximum number of bytes in the file to memory map. The +** pointer is overwritten with the old value. The limit is not changed if +** the value originally pointed to is negative, and so the current limit +** can be queried by passing in a pointer to a negative number. This +** file-control is used internally to implement [PRAGMA mmap_size]. +** ** */ #define SQLITE_FCNTL_LOCKSTATE 1 @@ -900,6 +920,7 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_PRAGMA 14 #define SQLITE_FCNTL_BUSYHANDLER 15 #define SQLITE_FCNTL_TEMPFILENAME 16 +#define SQLITE_FCNTL_MMAP_SIZE 18 /* ** CAPI3REF: Mutex Handle @@ -1612,12 +1633,12 @@ struct sqlite3_mem_methods { **
    SQLITE_CONFIG_PCACHE and SQLITE_CONFIG_GETPCACHE **
    These options are obsolete and should not be used by new code. ** They are retained for backwards compatibility but are now no-ops. -** +**
    ** ** [[SQLITE_CONFIG_SQLLOG]] **
    SQLITE_CONFIG_SQLLOG **
    This option is only available if sqlite is compiled with the -** SQLITE_ENABLE_SQLLOG pre-processor macro defined. The first argument should +** [SQLITE_ENABLE_SQLLOG] pre-processor macro defined. The first argument should ** be a pointer to a function of type void(*)(void*,sqlite3*,const char*, int). ** The second should be of type (void*). The callback is invoked by the library ** in three separate circumstances, identified by the value passed as the @@ -1627,7 +1648,23 @@ struct sqlite3_mem_methods { ** fourth parameter is 1, then the SQL statement that the third parameter ** points to has just been executed. Or, if the fourth parameter is 2, then ** the connection being passed as the second parameter is being closed. The -** third parameter is passed NULL In this case. +** third parameter is passed NULL In this case. An example of using this +** configuration option can be seen in the "test_sqllog.c" source file in +** the canonical SQLite source tree.
    +** +** [[SQLITE_CONFIG_MMAP_SIZE]] +**
    SQLITE_CONFIG_MMAP_SIZE +**
    SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (sqlite3_int64) values +** that are the default mmap size limit (the default setting for +** [PRAGMA mmap_size]) and the maximum allowed mmap size limit. +** The default setting can be overridden by each database connection using +** either the [PRAGMA mmap_size] command, or by using the +** [SQLITE_FCNTL_MMAP_SIZE] file control. The maximum allowed mmap size +** cannot be changed at run-time. Nor may the maximum allowed mmap size +** exceed the compile-time maximum mmap size set by the +** [SQLITE_MAX_MMAP_SIZE] compile-time option. +** If either argument to this option is negative, then that argument is +** changed to its compile-time default. ** */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ @@ -1651,6 +1688,7 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ #define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */ #define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ +#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ /* ** CAPI3REF: Database Connection Configuration Options @@ -4182,7 +4220,7 @@ void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); ** the content before returning. ** ** The typedef is necessary to work around problems in certain -** C++ compilers. See ticket #2191. +** C++ compilers. */ typedef void (*sqlite3_destructor_type)(void*); #define SQLITE_STATIC ((sqlite3_destructor_type)0) @@ -4981,11 +5019,20 @@ int sqlite3_table_column_metadata( ** ^This interface loads an SQLite extension library from the named file. ** ** ^The sqlite3_load_extension() interface attempts to load an -** SQLite extension library contained in the file zFile. +** [SQLite extension] library contained in the file zFile. If +** the file cannot be loaded directly, attempts are made to load +** with various operating-system specific extensions added. +** So for example, if "samplelib" cannot be loaded, then names like +** "samplelib.so" or "samplelib.dylib" or "samplelib.dll" might +** be tried also. ** ** ^The entry point is zProc. -** ^zProc may be 0, in which case the name of the entry point -** defaults to "sqlite3_extension_init". +** ^(zProc may be 0, in which case SQLite will try to come up with an +** entry point name on its own. It first tries "sqlite3_extension_init". +** If that does not work, it constructs a name "sqlite3_X_init" where the +** X is consists of the lower-case equivalent of all ASCII alphabetic +** characters in the filename from the last "/" to the first following +** "." and omitting any initial "lib".)^ ** ^The sqlite3_load_extension() interface returns ** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. ** ^If an error occurs and pzErrMsg is not 0, then the @@ -5011,11 +5058,11 @@ int sqlite3_load_extension( ** CAPI3REF: Enable Or Disable Extension Loading ** ** ^So as not to open security holes in older applications that are -** unprepared to deal with extension loading, and as a means of disabling -** extension loading while evaluating user-entered SQL, the following API +** unprepared to deal with [extension loading], and as a means of disabling +** [extension loading] while evaluating user-entered SQL, the following API ** is provided to turn the [sqlite3_load_extension()] mechanism on and off. ** -** ^Extension loading is off by default. See ticket #1863. +** ^Extension loading is off by default. ** ^Call the sqlite3_enable_load_extension() routine with onoff==1 ** to turn extension loading on and call it with onoff==0 to turn ** it back off again. @@ -5027,7 +5074,7 @@ int sqlite3_enable_load_extension(sqlite3 *db, int onoff); ** ** ^This interface causes the xEntryPoint() function to be invoked for ** each new [database connection] that is created. The idea here is that -** xEntryPoint() is the entry point for a statically linked SQLite extension +** xEntryPoint() is the entry point for a statically linked [SQLite extension] ** that is to be automatically loaded into all new database connections. ** ** ^(Even though the function prototype shows that xEntryPoint() takes @@ -6807,6 +6854,21 @@ int sqlite3_unlock_notify( int sqlite3_stricmp(const char *, const char *); int sqlite3_strnicmp(const char *, const char *, int); +/* +** CAPI3REF: String Globbing +* +** ^The [sqlite3_strglob(P,X)] interface returns zero if string X matches +** the glob pattern P, and it returns non-zero if string X does not match +** the glob pattern P. ^The definition of glob pattern matching used in +** [sqlite3_strglob(P,X)] is the same as for the "X GLOB P" operator in the +** SQL dialect used by SQLite. ^The sqlite3_strglob(P,X) function is case +** sensitive. +** +** Note that this routine returns zero on a match and non-zero if the strings +** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()]. +*/ +int sqlite3_strglob(const char *zGlob, const char *zStr); + /* ** CAPI3REF: Error Logging Interface ** diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index a465b8eccc..928bb3bad9 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -469,7 +469,16 @@ struct sqlite3_api_routines { #define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2 #endif /* SQLITE_CORE */ -#define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api = 0; -#define SQLITE_EXTENSION_INIT2(v) sqlite3_api = v; +#ifndef SQLITE_CORE + /* This case when the file really is being compiled as a loadable + ** extension */ +# define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0; +# define SQLITE_EXTENSION_INIT2(v) sqlite3_api=v; +#else + /* This case when the file is being statically linked into the + ** application */ +# define SQLITE_EXTENSION_INIT1 /*no-op*/ +# define SQLITE_EXTENSION_INIT2(v) (void)v; /* unused parameter */ +#endif #endif /* _SQLITE3EXT_H_ */ diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 895da1991c..7cde2bcaf1 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -122,11 +122,11 @@ ** We support that for legacy. */ #if !defined(SQLITE_THREADSAFE) -#if defined(THREADSAFE) -# define SQLITE_THREADSAFE THREADSAFE -#else -# define SQLITE_THREADSAFE 1 /* IMP: R-07272-22309 */ -#endif +# if defined(THREADSAFE) +# define SQLITE_THREADSAFE THREADSAFE +# else +# define SQLITE_THREADSAFE 1 /* IMP: R-07272-22309 */ +# endif #endif /* @@ -392,6 +392,7 @@ */ #ifndef SQLITE_TEMP_STORE # define SQLITE_TEMP_STORE 1 +# define SQLITE_TEMP_STORE_xc 1 /* Exclude from ctime.c */ #endif /* @@ -539,6 +540,49 @@ extern const int sqlite3one; # define EIGHT_BYTE_ALIGNMENT(X) ((((char*)(X) - (char*)0)&7)==0) #endif +/* +** Disable MMAP on platforms where it is known to not work +*/ +#if defined(__OpenBSD__) || defined(__QNXNTO__) +# undef SQLITE_MAX_MMAP_SIZE +# define SQLITE_MAX_MMAP_SIZE 0 +#endif + +/* +** Default maximum size of memory used by memory-mapped I/O in the VFS +*/ +#ifdef __APPLE__ +# include +# if TARGET_OS_IPHONE +# undef SQLITE_MAX_MMAP_SIZE +# define SQLITE_MAX_MMAP_SIZE 0 +# endif +#endif +#ifndef SQLITE_MAX_MMAP_SIZE +# if defined(__linux__) \ + || defined(_WIN32) \ + || (defined(__APPLE__) && defined(__MACH__)) \ + || defined(__sun) +# define SQLITE_MAX_MMAP_SIZE 2147483648 +# else +# define SQLITE_MAX_MMAP_SIZE 0 +# endif +# define SQLITE_MAX_MMAP_SIZE_xc 1 /* exclude from ctime.c */ +#endif + +/* +** The default MMAP_SIZE is zero on all platforms. Or, even if a larger +** default MMAP_SIZE is specified at compile-time, make sure that it does +** not exceed the maximum mmap size. +*/ +#ifndef SQLITE_DEFAULT_MMAP_SIZE +# define SQLITE_DEFAULT_MMAP_SIZE 0 +# define SQLITE_DEFAULT_MMAP_SIZE_xc 1 /* Exclude from ctime.c */ +#endif +#if SQLITE_DEFAULT_MMAP_SIZE>SQLITE_MAX_MMAP_SIZE +# undef SQLITE_DEFAULT_MMAP_SIZE +# define SQLITE_DEFAULT_MMAP_SIZE SQLITE_MAX_MMAP_SIZE +#endif /* ** An instance of the following structure is used to store the busy-handler @@ -835,6 +879,7 @@ struct sqlite3 { int nDb; /* Number of backends currently in use */ int flags; /* Miscellaneous flags. See below */ i64 lastRowid; /* ROWID of most recent insert (see above) */ + i64 szMmap; /* Default mmap_size setting */ unsigned int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */ int errCode; /* Most recent error code (SQLITE_*) */ int errMask; /* & result codes with this before returning */ @@ -2078,6 +2123,8 @@ struct NameContext { #define NC_HasAgg 0x02 /* One or more aggregate functions seen */ #define NC_IsCheck 0x04 /* True if resolving names in a CHECK constraint */ #define NC_InAggFunc 0x08 /* True if analyzing arguments to an agg func */ +#define NC_AsMaybe 0x10 /* Resolve to AS terms of the result set only + ** if no other resolution is available */ /* ** An instance of the following structure contains all information @@ -2517,6 +2564,8 @@ struct Sqlite3Config { void *pHeap; /* Heap storage space */ int nHeap; /* Size of pHeap[] */ int mnReq, mxReq; /* Min and max heap requests sizes */ + sqlite3_int64 szMmap; /* mmap() space per open file */ + sqlite3_int64 mxMmap; /* Maximum value for szMmap */ void *pScratch; /* Scratch memory */ int szScratch; /* Size of each scratch buffer */ int nScratch; /* Number of scratch buffers */ diff --git a/src/test1.c b/src/test1.c index 0bace84420..63d014cc17 100644 --- a/src/test1.c +++ b/src/test1.c @@ -116,71 +116,76 @@ int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb){ const char *sqlite3TestErrorName(int rc){ const char *zName = 0; - switch( rc ){ - case SQLITE_OK: zName = "SQLITE_OK"; break; - case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; - case SQLITE_INTERNAL: zName = "SQLITE_INTERNAL"; break; - case SQLITE_PERM: zName = "SQLITE_PERM"; break; - case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; - case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; - case SQLITE_LOCKED: zName = "SQLITE_LOCKED"; break; - case SQLITE_LOCKED_SHAREDCACHE: zName = "SQLITE_LOCKED_SHAREDCACHE";break; - case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; - case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; - case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break; - case SQLITE_IOERR: zName = "SQLITE_IOERR"; break; - case SQLITE_CORRUPT: zName = "SQLITE_CORRUPT"; break; - case SQLITE_NOTFOUND: zName = "SQLITE_NOTFOUND"; break; - case SQLITE_FULL: zName = "SQLITE_FULL"; break; - case SQLITE_CANTOPEN: zName = "SQLITE_CANTOPEN"; break; - case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break; - case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break; - case SQLITE_SCHEMA: zName = "SQLITE_SCHEMA"; break; - case SQLITE_TOOBIG: zName = "SQLITE_TOOBIG"; break; - case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT"; break; - case SQLITE_CONSTRAINT_UNIQUE: zName = "SQLITE_CONSTRAINT_UNIQUE"; break; - case SQLITE_CONSTRAINT_TRIGGER: zName = "SQLITE_CONSTRAINT_TRIGGER";break; - case SQLITE_CONSTRAINT_FOREIGNKEY: - zName = "SQLITE_CONSTRAINT_FOREIGNKEY"; break; - case SQLITE_CONSTRAINT_CHECK: zName = "SQLITE_CONSTRAINT_CHECK"; break; - case SQLITE_CONSTRAINT_PRIMARYKEY: - zName = "SQLITE_CONSTRAINT_PRIMARYKEY"; break; - case SQLITE_CONSTRAINT_NOTNULL: zName = "SQLITE_CONSTRAINT_NOTNULL";break; - case SQLITE_CONSTRAINT_COMMITHOOK: - zName = "SQLITE_CONSTRAINT_COMMITHOOK"; break; - case SQLITE_CONSTRAINT_VTAB: zName = "SQLITE_CONSTRAINT_VTAB"; break; - case SQLITE_CONSTRAINT_FUNCTION: zName = "SQLITE_CONSTRAINT_FUNCTION";break; - case SQLITE_MISMATCH: zName = "SQLITE_MISMATCH"; break; - case SQLITE_MISUSE: zName = "SQLITE_MISUSE"; break; - case SQLITE_NOLFS: zName = "SQLITE_NOLFS"; break; - case SQLITE_AUTH: zName = "SQLITE_AUTH"; break; - case SQLITE_FORMAT: zName = "SQLITE_FORMAT"; break; - case SQLITE_RANGE: zName = "SQLITE_RANGE"; break; - case SQLITE_NOTADB: zName = "SQLITE_NOTADB"; break; - case SQLITE_ROW: zName = "SQLITE_ROW"; break; - case SQLITE_DONE: zName = "SQLITE_DONE"; break; - case SQLITE_IOERR_READ: zName = "SQLITE_IOERR_READ"; break; - case SQLITE_IOERR_SHORT_READ: zName = "SQLITE_IOERR_SHORT_READ"; break; - case SQLITE_IOERR_WRITE: zName = "SQLITE_IOERR_WRITE"; break; - case SQLITE_IOERR_FSYNC: zName = "SQLITE_IOERR_FSYNC"; break; - case SQLITE_IOERR_DIR_FSYNC: zName = "SQLITE_IOERR_DIR_FSYNC"; break; - case SQLITE_IOERR_TRUNCATE: zName = "SQLITE_IOERR_TRUNCATE"; break; - case SQLITE_IOERR_FSTAT: zName = "SQLITE_IOERR_FSTAT"; break; - case SQLITE_IOERR_UNLOCK: zName = "SQLITE_IOERR_UNLOCK"; break; - case SQLITE_IOERR_RDLOCK: zName = "SQLITE_IOERR_RDLOCK"; break; - case SQLITE_IOERR_DELETE: zName = "SQLITE_IOERR_DELETE"; break; - case SQLITE_IOERR_BLOCKED: zName = "SQLITE_IOERR_BLOCKED"; break; - case SQLITE_IOERR_NOMEM: zName = "SQLITE_IOERR_NOMEM"; break; - case SQLITE_IOERR_ACCESS: zName = "SQLITE_IOERR_ACCESS"; break; - case SQLITE_IOERR_CHECKRESERVEDLOCK: - zName = "SQLITE_IOERR_CHECKRESERVEDLOCK"; break; - case SQLITE_IOERR_LOCK: zName = "SQLITE_IOERR_LOCK"; break; - case SQLITE_CORRUPT_VTAB: zName = "SQLITE_CORRUPT_VTAB"; break; - case SQLITE_READONLY_RECOVERY: zName = "SQLITE_READONLY_RECOVERY"; break; - case SQLITE_READONLY_CANTLOCK: zName = "SQLITE_READONLY_CANTLOCK"; break; - case SQLITE_READONLY_ROLLBACK: zName = "SQLITE_READONLY_ROLLBACK"; break; - default: zName = "SQLITE_Unknown"; break; + int i; + for(i=0; i<2 && zName==0; i++, rc &= 0xff){ + switch( rc ){ + case SQLITE_OK: zName = "SQLITE_OK"; break; + case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; + case SQLITE_INTERNAL: zName = "SQLITE_INTERNAL"; break; + case SQLITE_PERM: zName = "SQLITE_PERM"; break; + case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; + case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; + case SQLITE_LOCKED: zName = "SQLITE_LOCKED"; break; + case SQLITE_LOCKED_SHAREDCACHE: zName = "SQLITE_LOCKED_SHAREDCACHE";break; + case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; + case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; + case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break; + case SQLITE_IOERR: zName = "SQLITE_IOERR"; break; + case SQLITE_CORRUPT: zName = "SQLITE_CORRUPT"; break; + case SQLITE_NOTFOUND: zName = "SQLITE_NOTFOUND"; break; + case SQLITE_FULL: zName = "SQLITE_FULL"; break; + case SQLITE_CANTOPEN: zName = "SQLITE_CANTOPEN"; break; + case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break; + case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break; + case SQLITE_SCHEMA: zName = "SQLITE_SCHEMA"; break; + case SQLITE_TOOBIG: zName = "SQLITE_TOOBIG"; break; + case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT"; break; + case SQLITE_CONSTRAINT_UNIQUE: zName = "SQLITE_CONSTRAINT_UNIQUE"; break; + case SQLITE_CONSTRAINT_TRIGGER: zName = "SQLITE_CONSTRAINT_TRIGGER";break; + case SQLITE_CONSTRAINT_FOREIGNKEY: + zName = "SQLITE_CONSTRAINT_FOREIGNKEY"; break; + case SQLITE_CONSTRAINT_CHECK: zName = "SQLITE_CONSTRAINT_CHECK"; break; + case SQLITE_CONSTRAINT_PRIMARYKEY: + zName = "SQLITE_CONSTRAINT_PRIMARYKEY"; break; + case SQLITE_CONSTRAINT_NOTNULL: zName = "SQLITE_CONSTRAINT_NOTNULL";break; + case SQLITE_CONSTRAINT_COMMITHOOK: + zName = "SQLITE_CONSTRAINT_COMMITHOOK"; break; + case SQLITE_CONSTRAINT_VTAB: zName = "SQLITE_CONSTRAINT_VTAB"; break; + case SQLITE_CONSTRAINT_FUNCTION: zName = "SQLITE_CONSTRAINT_FUNCTION";break; + case SQLITE_MISMATCH: zName = "SQLITE_MISMATCH"; break; + case SQLITE_MISUSE: zName = "SQLITE_MISUSE"; break; + case SQLITE_NOLFS: zName = "SQLITE_NOLFS"; break; + case SQLITE_AUTH: zName = "SQLITE_AUTH"; break; + case SQLITE_FORMAT: zName = "SQLITE_FORMAT"; break; + case SQLITE_RANGE: zName = "SQLITE_RANGE"; break; + case SQLITE_NOTADB: zName = "SQLITE_NOTADB"; break; + case SQLITE_ROW: zName = "SQLITE_ROW"; break; + case SQLITE_NOTICE: zName = "SQLITE_NOTICE"; break; + case SQLITE_WARNING: zName = "SQLITE_WARNING"; break; + case SQLITE_DONE: zName = "SQLITE_DONE"; break; + case SQLITE_IOERR_READ: zName = "SQLITE_IOERR_READ"; break; + case SQLITE_IOERR_SHORT_READ: zName = "SQLITE_IOERR_SHORT_READ"; break; + case SQLITE_IOERR_WRITE: zName = "SQLITE_IOERR_WRITE"; break; + case SQLITE_IOERR_FSYNC: zName = "SQLITE_IOERR_FSYNC"; break; + case SQLITE_IOERR_DIR_FSYNC: zName = "SQLITE_IOERR_DIR_FSYNC"; break; + case SQLITE_IOERR_TRUNCATE: zName = "SQLITE_IOERR_TRUNCATE"; break; + case SQLITE_IOERR_FSTAT: zName = "SQLITE_IOERR_FSTAT"; break; + case SQLITE_IOERR_UNLOCK: zName = "SQLITE_IOERR_UNLOCK"; break; + case SQLITE_IOERR_RDLOCK: zName = "SQLITE_IOERR_RDLOCK"; break; + case SQLITE_IOERR_DELETE: zName = "SQLITE_IOERR_DELETE"; break; + case SQLITE_IOERR_BLOCKED: zName = "SQLITE_IOERR_BLOCKED"; break; + case SQLITE_IOERR_NOMEM: zName = "SQLITE_IOERR_NOMEM"; break; + case SQLITE_IOERR_ACCESS: zName = "SQLITE_IOERR_ACCESS"; break; + case SQLITE_IOERR_CHECKRESERVEDLOCK: + zName = "SQLITE_IOERR_CHECKRESERVEDLOCK"; break; + case SQLITE_IOERR_LOCK: zName = "SQLITE_IOERR_LOCK"; break; + case SQLITE_CORRUPT_VTAB: zName = "SQLITE_CORRUPT_VTAB"; break; + case SQLITE_READONLY_RECOVERY: zName = "SQLITE_READONLY_RECOVERY"; break; + case SQLITE_READONLY_CANTLOCK: zName = "SQLITE_READONLY_CANTLOCK"; break; + case SQLITE_READONLY_ROLLBACK: zName = "SQLITE_READONLY_ROLLBACK"; break; + } } + if( zName==0 ) zName = "SQLITE_Unknown"; return zName; } #define t1ErrorName sqlite3TestErrorName @@ -5844,6 +5849,31 @@ static int test_test_control( return TCL_OK; } +#if SQLITE_OS_UNIX +#include +#include + +static int test_getrusage( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + char buf[1024]; + struct rusage r; + memset(&r, 0, sizeof(r)); + getrusage(RUSAGE_SELF, &r); + + sprintf(buf, "ru_utime=%d.%06d ru_stime=%d.%06d ru_minflt=%d ru_majflt=%d", + (int)r.ru_utime.tv_sec, (int)r.ru_utime.tv_usec, + (int)r.ru_stime.tv_sec, (int)r.ru_stime.tv_usec, + (int)r.ru_minflt, (int)r.ru_majflt + ); + Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, -1)); + return TCL_OK; +} +#endif + #if SQLITE_OS_WIN /* ** Information passed from the main thread into the windows file locker @@ -6233,6 +6263,9 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "print_explain_query_plan", test_print_eqp, 0 }, #endif { "sqlite3_test_control", test_test_control }, +#if SQLITE_OS_UNIX + { "getrusage", test_getrusage }, +#endif }; static int bitmask_size = sizeof(Bitmask)*8; int i; diff --git a/src/test2.c b/src/test2.c index 8acdf6f4ef..3faec12619 100644 --- a/src/test2.c +++ b/src/test2.c @@ -19,35 +19,7 @@ #include #include -/* -** Interpret an SQLite error number -*/ -static char *errorName(int rc){ - char *zName; - switch( rc ){ - case SQLITE_OK: zName = "SQLITE_OK"; break; - case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; - case SQLITE_PERM: zName = "SQLITE_PERM"; break; - case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; - case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; - case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; - case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; - case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break; - case SQLITE_IOERR: zName = "SQLITE_IOERR"; break; - case SQLITE_CORRUPT: zName = "SQLITE_CORRUPT"; break; - case SQLITE_FULL: zName = "SQLITE_FULL"; break; - case SQLITE_CANTOPEN: zName = "SQLITE_CANTOPEN"; break; - case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break; - case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break; - case SQLITE_SCHEMA: zName = "SQLITE_SCHEMA"; break; - case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT"; break; - case SQLITE_MISMATCH: zName = "SQLITE_MISMATCH"; break; - case SQLITE_MISUSE: zName = "SQLITE_MISUSE"; break; - case SQLITE_NOLFS: zName = "SQLITE_NOLFS"; break; - default: zName = "SQLITE_Unknown"; break; - } - return zName; -} +extern const char *sqlite3TestErrorName(int rc); /* ** Page size and reserved size used for testing. @@ -87,7 +59,7 @@ static int pager_open( SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB, pager_test_reiniter); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } sqlite3PagerSetCachesize(pPager, nPage); @@ -119,7 +91,7 @@ static int pager_close( pPager = sqlite3TestTextToPtr(argv[1]); rc = sqlite3PagerClose(pPager); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } return TCL_OK; @@ -146,7 +118,7 @@ static int pager_rollback( pPager = sqlite3TestTextToPtr(argv[1]); rc = sqlite3PagerRollback(pPager); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } return TCL_OK; @@ -173,12 +145,12 @@ static int pager_commit( pPager = sqlite3TestTextToPtr(argv[1]); rc = sqlite3PagerCommitPhaseOne(pPager, 0, 0); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } rc = sqlite3PagerCommitPhaseTwo(pPager); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } return TCL_OK; @@ -205,7 +177,7 @@ static int pager_stmt_begin( pPager = sqlite3TestTextToPtr(argv[1]); rc = sqlite3PagerOpenSavepoint(pPager, 1); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } return TCL_OK; @@ -233,7 +205,7 @@ static int pager_stmt_rollback( rc = sqlite3PagerSavepoint(pPager, SAVEPOINT_ROLLBACK, 0); sqlite3PagerSavepoint(pPager, SAVEPOINT_RELEASE, 0); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } return TCL_OK; @@ -260,7 +232,7 @@ static int pager_stmt_commit( pPager = sqlite3TestTextToPtr(argv[1]); rc = sqlite3PagerSavepoint(pPager, SAVEPOINT_RELEASE, 0); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } return TCL_OK; @@ -353,7 +325,7 @@ static int page_get( rc = sqlite3PagerGet(pPager, pgno, &pPage); } if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } sqlite3_snprintf(sizeof(zBuf),zBuf,"%p",pPage); @@ -507,7 +479,7 @@ static int page_write( pPage = (DbPage *)sqlite3TestTextToPtr(argv[1]); rc = sqlite3PagerWrite(pPage); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } pData = sqlite3PagerGetData(pPage); @@ -556,7 +528,7 @@ static int fake_big_file( (SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_DB), 0 ); if( rc ){ - Tcl_AppendResult(interp, "open failed: ", errorName(rc), 0); + Tcl_AppendResult(interp, "open failed: ", sqlite3TestErrorName(rc), 0); sqlite3_free(zFile); return TCL_ERROR; } @@ -566,7 +538,7 @@ static int fake_big_file( sqlite3OsCloseFree(fd); sqlite3_free(zFile); if( rc ){ - Tcl_AppendResult(interp, "write failed: ", errorName(rc), 0); + Tcl_AppendResult(interp, "write failed: ", sqlite3TestErrorName(rc), 0); return TCL_ERROR; } return TCL_OK; diff --git a/src/test3.c b/src/test3.c index e460c42e46..ecc3a543be 100644 --- a/src/test3.c +++ b/src/test3.c @@ -19,31 +19,7 @@ #include #include -/* -** Interpret an SQLite error number -*/ -static char *errorName(int rc){ - char *zName; - switch( rc ){ - case SQLITE_OK: zName = "SQLITE_OK"; break; - case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; - case SQLITE_PERM: zName = "SQLITE_PERM"; break; - case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; - case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; - case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; - case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; - case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break; - case SQLITE_IOERR: zName = "SQLITE_IOERR"; break; - case SQLITE_CORRUPT: zName = "SQLITE_CORRUPT"; break; - case SQLITE_FULL: zName = "SQLITE_FULL"; break; - case SQLITE_CANTOPEN: zName = "SQLITE_CANTOPEN"; break; - case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break; - case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break; - case SQLITE_LOCKED: zName = "SQLITE_LOCKED"; break; - default: zName = "SQLITE_Unknown"; break; - } - return zName; -} +extern const char *sqlite3TestErrorName(int rc); /* ** A bogus sqlite3 connection structure for use in the btree @@ -89,7 +65,7 @@ static int btree_open( SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB); sqlite3_free(zFilename); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } sqlite3BtreeSetCacheSize(pBt, nCache); @@ -119,7 +95,7 @@ static int btree_close( pBt = sqlite3TestTextToPtr(argv[1]); rc = sqlite3BtreeClose(pBt); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } nRefSqlite3--; @@ -156,7 +132,7 @@ static int btree_begin_transaction( rc = sqlite3BtreeBeginTrans(pBt, 1); sqlite3BtreeLeave(pBt); if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } return TCL_OK; @@ -250,7 +226,7 @@ static int btree_cursor( sqlite3BtreeLeave(pBt); if( rc ){ ckfree((char *)pCur); - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } sqlite3_snprintf(sizeof(zBuf), zBuf,"%p", pCur); @@ -285,7 +261,7 @@ static int btree_close_cursor( sqlite3BtreeLeave(pBt); ckfree((char *)pCur); if( rc ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } return SQLITE_OK; @@ -319,7 +295,7 @@ static int btree_next( rc = sqlite3BtreeNext(pCur, &res); sqlite3BtreeLeave(pCur->pBtree); if( rc ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",res); @@ -354,7 +330,7 @@ static int btree_first( rc = sqlite3BtreeFirst(pCur, &res); sqlite3BtreeLeave(pCur->pBtree); if( rc ){ - Tcl_AppendResult(interp, errorName(rc), 0); + Tcl_AppendResult(interp, sqlite3TestErrorName(rc), 0); return TCL_ERROR; } sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",res); diff --git a/src/test4.c b/src/test4.c index 5b4c5a1c17..83c56d3d7e 100644 --- a/src/test4.c +++ b/src/test4.c @@ -20,6 +20,8 @@ #include #include +extern const char *sqlite3TestErrorName(int rc); + /* ** Each thread is controlled by an instance of the following ** structure. @@ -372,34 +374,7 @@ static int tcl_thread_result( return TCL_ERROR; } thread_wait(&threadset[i]); - switch( threadset[i].rc ){ - case SQLITE_OK: zName = "SQLITE_OK"; break; - case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; - case SQLITE_PERM: zName = "SQLITE_PERM"; break; - case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; - case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; - case SQLITE_LOCKED: zName = "SQLITE_LOCKED"; break; - case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; - case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; - case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break; - case SQLITE_IOERR: zName = "SQLITE_IOERR"; break; - case SQLITE_CORRUPT: zName = "SQLITE_CORRUPT"; break; - case SQLITE_FULL: zName = "SQLITE_FULL"; break; - case SQLITE_CANTOPEN: zName = "SQLITE_CANTOPEN"; break; - case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break; - case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break; - case SQLITE_SCHEMA: zName = "SQLITE_SCHEMA"; break; - case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT"; break; - case SQLITE_MISMATCH: zName = "SQLITE_MISMATCH"; break; - case SQLITE_MISUSE: zName = "SQLITE_MISUSE"; break; - case SQLITE_NOLFS: zName = "SQLITE_NOLFS"; break; - case SQLITE_AUTH: zName = "SQLITE_AUTH"; break; - case SQLITE_FORMAT: zName = "SQLITE_FORMAT"; break; - case SQLITE_RANGE: zName = "SQLITE_RANGE"; break; - case SQLITE_ROW: zName = "SQLITE_ROW"; break; - case SQLITE_DONE: zName = "SQLITE_DONE"; break; - default: zName = "SQLITE_Unknown"; break; - } + zName = sqlite3TestErrorName(threadset[i].rc); Tcl_AppendResult(interp, zName, 0); return TCL_OK; } diff --git a/src/test7.c b/src/test7.c index 852cd1db5e..707d2c89af 100644 --- a/src/test7.c +++ b/src/test7.c @@ -376,6 +376,8 @@ static int tcl_client_colname( return TCL_OK; } +extern const char *sqlite3TestErrorName(int rc); + /* ** Usage: client_result ID ** @@ -403,34 +405,7 @@ static int tcl_client_result( return TCL_ERROR; } client_wait(&threadset[i]); - switch( threadset[i].rc ){ - case SQLITE_OK: zName = "SQLITE_OK"; break; - case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; - case SQLITE_PERM: zName = "SQLITE_PERM"; break; - case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; - case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; - case SQLITE_LOCKED: zName = "SQLITE_LOCKED"; break; - case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; - case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; - case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break; - case SQLITE_IOERR: zName = "SQLITE_IOERR"; break; - case SQLITE_CORRUPT: zName = "SQLITE_CORRUPT"; break; - case SQLITE_FULL: zName = "SQLITE_FULL"; break; - case SQLITE_CANTOPEN: zName = "SQLITE_CANTOPEN"; break; - case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break; - case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break; - case SQLITE_SCHEMA: zName = "SQLITE_SCHEMA"; break; - case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT"; break; - case SQLITE_MISMATCH: zName = "SQLITE_MISMATCH"; break; - case SQLITE_MISUSE: zName = "SQLITE_MISUSE"; break; - case SQLITE_NOLFS: zName = "SQLITE_NOLFS"; break; - case SQLITE_AUTH: zName = "SQLITE_AUTH"; break; - case SQLITE_FORMAT: zName = "SQLITE_FORMAT"; break; - case SQLITE_RANGE: zName = "SQLITE_RANGE"; break; - case SQLITE_ROW: zName = "SQLITE_ROW"; break; - case SQLITE_DONE: zName = "SQLITE_DONE"; break; - default: zName = "SQLITE_Unknown"; break; - } + zName = sqlite3TestErrorName(threadset[i].rc); Tcl_AppendResult(interp, zName, 0); return TCL_OK; } diff --git a/src/test_autoext.c b/src/test_autoext.c index 6b1e297ab7..b5013f3173 100644 --- a/src/test_autoext.c +++ b/src/test_autoext.c @@ -15,7 +15,7 @@ #include "sqlite3ext.h" #ifndef SQLITE_OMIT_LOAD_EXTENSION -static SQLITE_EXTENSION_INIT1 +SQLITE_EXTENSION_INIT1 /* ** The sqr() SQL function returns the square of its input value. diff --git a/src/test_config.c b/src/test_config.c index a166728c7d..a1aa03aa14 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -57,7 +57,7 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options","casesensitivelike","0",TCL_GLOBAL_ONLY); #endif -#ifdef SQLITE_CURDIR +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT Tcl_SetVar2(interp, "sqlite_options", "curdir", "1", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "curdir", "0", TCL_GLOBAL_ONLY); @@ -87,6 +87,12 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "lfs", "1", TCL_GLOBAL_ONLY); #endif +#if SQLITE_MAX_MMAP_SIZE>0 + Tcl_SetVar2(interp, "sqlite_options", "mmap", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "mmap", "0", TCL_GLOBAL_ONLY); +#endif + #if 1 /* def SQLITE_MEMDEBUG */ Tcl_SetVar2(interp, "sqlite_options", "memdebug", "1", TCL_GLOBAL_ONLY); #else diff --git a/src/test_sqllog.c b/src/test_sqllog.c index 24c675b5e9..4aa68b7c42 100644 --- a/src/test_sqllog.c +++ b/src/test_sqllog.c @@ -13,22 +13,32 @@ ** OVERVIEW ** ** This file contains experimental code used to record data from live -** SQLite applications that may be useful for offline analysis. Specifically: +** SQLite applications that may be useful for offline analysis. +** Specifically, this module can be used to capture the following +** information: ** ** 1) The initial contents of all database files opened by the ** application, and ** ** 2) All SQL statements executed by the application. ** +** The captured information can then be used to run (for example) +** performance analysis looking for slow queries or to look for +** optimization opportunities in either the application or in SQLite +** itself. +** ** USAGE ** ** To use this module, SQLite must be compiled with the SQLITE_ENABLE_SQLLOG -** pre-processor symbol defined and this file linked into the application -** somehow. +** pre-processor symbol defined and this file linked into the application. +** One way to link this file into the application is to append the content +** of this file onto the end of the "sqlite3.c" amalgamation and then +** recompile the application as normal except with the addition of the +** -DSQLITE_ENABLE_SQLLOG option. ** ** At runtime, logging is enabled by setting environment variable ** SQLITE_SQLLOG_DIR to the name of a directory in which to store logged -** data. The directory must already exist. +** data. The logging directory must already exist. ** ** Usually, if the application opens the same database file more than once ** (either by attaching it or by using more than one database handle), only @@ -57,14 +67,16 @@ ** logging proceeds as expected. Errors are logged by calling sqlite3_log(). */ +#ifndef _SQLITE3_H_ #include "sqlite3.h" -#include "stdio.h" -#include "stdlib.h" -#include "string.h" -#include "assert.h" +#endif +#include +#include +#include +#include -#include "sys/types.h" -#include "unistd.h" +#include +#include static int getProcessId(void){ #if SQLITE_OS_WIN return (int)_getpid(); @@ -73,7 +85,7 @@ static int getProcessId(void){ #endif } - +/* Names of environment variables to be used */ #define ENVIRONMENT_VARIABLE1_NAME "SQLITE_SQLLOG_DIR" #define ENVIRONMENT_VARIABLE2_NAME "SQLITE_SQLLOG_REUSE_FILES" @@ -86,6 +98,9 @@ static int getProcessId(void){ */ #define MAX_CONNECTIONS 256 +/* There is one instance of this object for each SQLite database connection +** that is being logged. +*/ struct SLConn { int isErr; /* True if an error has occurred */ sqlite3 *db; /* Connection handle */ @@ -93,7 +108,9 @@ struct SLConn { FILE *fd; /* File descriptor for log file */ }; -struct SLGlobal { +/* This object is a singleton that keeps track of all data loggers. +*/ +static struct SLGlobal { /* Protected by MUTEX_STATIC_MASTER */ sqlite3_mutex *mutex; /* Recursive mutex */ int nConn; /* Size of aConn[] array */ @@ -388,6 +405,24 @@ static void testSqllogStmt(struct SLConn *p, const char *zSql){ /* ** The SQLITE_CONFIG_SQLLOG callback registered by sqlite3_init_sqllog(). +** +** The eType parameter has the following values: +** +** 0: Opening a new database connection. zSql is the name of the +** file being opened. db is a pointer to the newly created database +** connection. +** +** 1: An SQL statement has run to completion. zSql is the text of the +** SQL statement with all parameters expanded to their actual values. +** +** 2: Closing a database connection. zSql is NULL. The db pointer to +** the database connection being closed has already been shut down +** and cannot be used for any further SQL. +** +** The pCtx parameter is a copy of the pointer that was originally passed +** into the sqlite3_config(SQLITE_CONFIG_SQLLOG) statement. In this +** particular implementation, pCtx is always a pointer to the +** sqllogglobal global variable define above. */ static void testSqllog(void *pCtx, sqlite3 *db, const char *zSql, int eType){ struct SLConn *p = 0; diff --git a/src/test_syscall.c b/src/test_syscall.c index d484f22db4..3d117fb2ab 100644 --- a/src/test_syscall.c +++ b/src/test_syscall.c @@ -23,7 +23,7 @@ ** ** open close access getcwd stat fstat ** ftruncate fcntl read pread pread64 write -** pwrite pwrite64 fchmod fallocate +** pwrite pwrite64 fchmod fallocate mmap ** ** test_syscall uninstall ** Uninstall all wrapper functions. @@ -81,6 +81,7 @@ /* From test1.c */ extern const char *sqlite3TestErrorName(int); +#include #include #include @@ -106,7 +107,8 @@ static int ts_pwrite(int fd, const void *aBuf, size_t nBuf, off_t off); static int ts_pwrite64(int fd, const void *aBuf, size_t nBuf, off_t off); static int ts_fchmod(int fd, mode_t mode); static int ts_fallocate(int fd, off_t off, off_t len); - +static void *ts_mmap(void *, size_t, int, int, int, off_t); +static void *ts_mremap(void*, size_t, size_t, int, ...); struct TestSyscallArray { const char *zName; @@ -131,6 +133,8 @@ struct TestSyscallArray { /* 13 */ { "pwrite64", (sqlite3_syscall_ptr)ts_pwrite64, 0, 0, 0 }, /* 14 */ { "fchmod", (sqlite3_syscall_ptr)ts_fchmod, 0, 0, 0 }, /* 15 */ { "fallocate", (sqlite3_syscall_ptr)ts_fallocate, 0, 0, 0 }, + /* 16 */ { "mmap", (sqlite3_syscall_ptr)ts_mmap, 0, 0, 0 }, + /* 17 */ { "mremap", (sqlite3_syscall_ptr)ts_mremap, 0, 0, 0 }, { 0, 0, 0, 0, 0 } }; @@ -152,6 +156,8 @@ struct TestSyscallArray { aSyscall[13].xOrig) #define orig_fchmod ((int(*)(int,mode_t))aSyscall[14].xOrig) #define orig_fallocate ((int(*)(int,off_t,off_t))aSyscall[15].xOrig) +#define orig_mmap ((void*(*)(void*,size_t,int,int,int,off_t))aSyscall[16].xOrig) +#define orig_mremap ((void*(*)(void*,size_t,size_t,int,...))aSyscall[17].xOrig) /* ** This function is called exactly once from within each invocation of a @@ -377,6 +383,31 @@ static int ts_fallocate(int fd, off_t off, off_t len){ return orig_fallocate(fd, off, len); } +static void *ts_mmap( + void *pAddr, + size_t nByte, + int prot, + int flags, + int fd, + off_t iOff +){ + if( tsIsFailErrno("mmap") ){ + return MAP_FAILED; + } + return orig_mmap(pAddr, nByte, prot, flags, fd, iOff); +} + +static void *ts_mremap(void *a, size_t b, size_t c, int d, ...){ + va_list ap; + void *pArg; + if( tsIsFailErrno("mremap") ){ + return MAP_FAILED; + } + va_start(ap, d); + pArg = va_arg(ap, void *); + return orig_mremap(a, b, c, d, pArg); +} + static int test_syscall_install( void * clientData, Tcl_Interp *interp, diff --git a/src/test_vfs.c b/src/test_vfs.c index 933de99515..fcd577439c 100644 --- a/src/test_vfs.c +++ b/src/test_vfs.c @@ -125,8 +125,9 @@ struct Testvfs { #define TESTVFS_ACCESS_MASK 0x00004000 #define TESTVFS_FULLPATHNAME_MASK 0x00008000 #define TESTVFS_READ_MASK 0x00010000 +#define TESTVFS_UNLOCK_MASK 0x00020000 -#define TESTVFS_ALL_MASK 0x0001FFFF +#define TESTVFS_ALL_MASK 0x0003FFFF #define TESTVFS_MAX_PAGES 1024 @@ -467,8 +468,12 @@ static int tvfsLock(sqlite3_file *pFile, int eLock){ ** Unlock an tvfs-file. */ static int tvfsUnlock(sqlite3_file *pFile, int eLock){ - TestvfsFd *p = tvfsGetFd(pFile); - return sqlite3OsUnlock(p->pReal, eLock); + TestvfsFd *pFd = tvfsGetFd(pFile); + Testvfs *p = (Testvfs *)pFd->pVfs->pAppData; + if( p->mask&TESTVFS_WRITE_MASK && tvfsInjectIoerr(p) ){ + return SQLITE_IOERR_UNLOCK; + } + return sqlite3OsUnlock(pFd->pReal, eLock); } /* @@ -1101,6 +1106,7 @@ static int testvfs_obj_cmd( { "xClose", TESTVFS_CLOSE_MASK }, { "xAccess", TESTVFS_ACCESS_MASK }, { "xFullPathname", TESTVFS_FULLPATHNAME_MASK }, + { "xUnlock", TESTVFS_UNLOCK_MASK }, }; Tcl_Obj **apElem = 0; int nElem = 0; diff --git a/src/vdbe.c b/src/vdbe.c index 37435c8edd..aa82a683e3 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -3525,7 +3525,7 @@ case OP_SeekGt: { /* jump, in3 */ ** r.flags = 0; ** } */ - r.flags = (u16)(UNPACKED_INCRKEY * (1 & (oc - OP_SeekLt))); + r.flags = (u8)(UNPACKED_INCRKEY * (1 & (oc - OP_SeekLt))); assert( oc!=OP_SeekGt || r.flags==UNPACKED_INCRKEY ); assert( oc!=OP_SeekLe || r.flags==UNPACKED_INCRKEY ); assert( oc!=OP_SeekGe || r.flags==0 ); diff --git a/src/vdbeInt.h b/src/vdbeInt.h index b871b25dae..b1e637b32d 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -18,6 +18,14 @@ #ifndef _VDBEINT_H_ #define _VDBEINT_H_ +/* +** The maximum number of times that a statement will try to reparse +** itself before giving up and returning SQLITE_SCHEMA. +*/ +#ifndef SQLITE_MAX_SCHEMA_RETRY +# define SQLITE_MAX_SCHEMA_RETRY 50 +#endif + /* ** SQL is translated into a sequence of instructions to be ** executed by a virtual machine. Each instruction is an instance diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 79827b7f9d..61041237df 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -453,14 +453,6 @@ end_of_step: return (rc&db->errMask); } -/* -** The maximum number of times that a statement will try to reparse -** itself before giving up and returning SQLITE_SCHEMA. -*/ -#ifndef SQLITE_MAX_SCHEMA_RETRY -# define SQLITE_MAX_SCHEMA_RETRY 5 -#endif - /* ** This is the top-level implementation of sqlite3_step(). Call ** sqlite3Step() to do most of the work. If a schema error occurs, diff --git a/src/vdbeblob.c b/src/vdbeblob.c index 995c08d166..044e7a8977 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -317,7 +317,7 @@ int sqlite3_blob_open( } sqlite3_bind_int64(pBlob->pStmt, 1, iRow); rc = blobSeekToRow(pBlob, iRow, &zErr); - } while( (++nAttempt)<5 && rc==SQLITE_SCHEMA ); + } while( (++nAttempt)mallocFailed==0 ){ diff --git a/src/vdbetrace.c b/src/vdbetrace.c index 35825c8736..88935beeac 100644 --- a/src/vdbetrace.c +++ b/src/vdbetrace.c @@ -53,6 +53,11 @@ static int findNextHostParameter(const char *zSql, int *pnToken){ ** then the returned string holds a copy of zRawSql with "-- " prepended ** to each line of text. ** +** If the SQLITE_TRACE_SIZE_LIMIT macro is defined to an integer, then +** then long strings and blobs are truncated to that many bytes. This +** can be used to prevent unreasonably large trace strings when dealing +** with large (multi-megabyte) strings and blobs. +** ** The calling function is responsible for making sure the memory returned ** is eventually freed. ** @@ -123,30 +128,49 @@ char *sqlite3VdbeExpandSql( }else if( pVar->flags & MEM_Real ){ sqlite3XPrintf(&out, "%!.15g", pVar->r); }else if( pVar->flags & MEM_Str ){ + int nOut; /* Number of bytes of the string text to include in output */ #ifndef SQLITE_OMIT_UTF16 u8 enc = ENC(db); + Mem utf8; if( enc!=SQLITE_UTF8 ){ - Mem utf8; memset(&utf8, 0, sizeof(utf8)); utf8.db = db; sqlite3VdbeMemSetStr(&utf8, pVar->z, pVar->n, enc, SQLITE_STATIC); sqlite3VdbeChangeEncoding(&utf8, SQLITE_UTF8); - sqlite3XPrintf(&out, "'%.*q'", utf8.n, utf8.z); - sqlite3VdbeMemRelease(&utf8); - }else -#endif - { - sqlite3XPrintf(&out, "'%.*q'", pVar->n, pVar->z); + pVar = &utf8; } +#endif + nOut = pVar->n; +#ifdef SQLITE_TRACE_SIZE_LIMIT + if( n>SQLITE_TRACE_SIZE_LIMIT ){ + nOut = SQLITE_TRACE_SIZE_LIMIT; + while( nOutn && (pVar->z[n]&0xc0)==0x80 ){ n++; } + } +#endif + sqlite3XPrintf(&out, "'%.*q'", nOut, pVar->z); +#ifdef SQLITE_TRACE_SIZE_LIMIT + if( nOutn ) sqlite3XPrintf(&out, "/*+%d bytes*/", pVar->n-n); +#endif +#ifndef SQLITE_OMIT_UTF16 + if( enc!=SQLITE_UTF8 ) sqlite3VdbeMemRelease(&utf8); +#endif }else if( pVar->flags & MEM_Zero ){ sqlite3XPrintf(&out, "zeroblob(%d)", pVar->u.nZero); }else{ + int nOut; /* Number of bytes of the blob to include in output */ assert( pVar->flags & MEM_Blob ); sqlite3StrAccumAppend(&out, "x'", 2); - for(i=0; in; i++){ + nOut = pVar->n; +#ifdef SQLITE_TRACE_SIZE_LIMIT + if( nOut>SQLITE_TRACE_SIZE_LIMIT ) nOut = SQLITE_TRACE_SIZE_LIMIT; +#endif + for(i=0; iz[i]&0xff); } sqlite3StrAccumAppend(&out, "'", 1); +#ifdef SQLITE_TRACE_SIZE_LIMIT + if( nOutn ) sqlite3XPrintf(&out, "/*+%d bytes*/", pVar->n-n); +#endif } } } diff --git a/src/wal.c b/src/wal.c index 0d7271bf04..e642ea2105 100644 --- a/src/wal.c +++ b/src/wal.c @@ -1207,8 +1207,9 @@ finished: ** checkpointing the log file. */ if( pWal->hdr.nPage ){ - sqlite3_log(SQLITE_OK, "Recovered %d frames from WAL file %s", - pWal->hdr.nPage, pWal->zWalName + sqlite3_log(SQLITE_NOTICE_RECOVER_WAL, + "recovered %d frames from WAL file %s", + pWal->hdr.mxFrame, pWal->zWalName ); } } @@ -1722,8 +1723,8 @@ static int walCheckpoint( rc = sqlite3OsSync(pWal->pWalFd, sync_flags); } - /* If the database file may grow as a result of this checkpoint, hint - ** about the eventual size of the db file to the VFS layer. + /* If the database may grow as a result of this checkpoint, hint + ** about the eventual size of the db file to the VFS layer. */ if( rc==SQLITE_OK ){ i64 nReq = ((i64)mxPage * szPage); @@ -1733,6 +1734,7 @@ static int walCheckpoint( } } + /* Iterate through the contents of the WAL, copying data to the db file. */ while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ i64 iOffset; @@ -2287,19 +2289,17 @@ void sqlite3WalEndReadTransaction(Wal *pWal){ } /* -** Read a page from the WAL, if it is present in the WAL and if the -** current read transaction is configured to use the WAL. +** Search the wal file for page pgno. If found, set *piRead to the frame that +** contains the page. Otherwise, if pgno is not in the wal file, set *piRead +** to zero. ** -** The *pInWal is set to 1 if the requested page is in the WAL and -** has been loaded. Or *pInWal is set to 0 if the page was not in -** the WAL and needs to be read out of the database. +** Return SQLITE_OK if successful, or an error code if an error occurs. If an +** error does occur, the final value of *piRead is undefined. */ -int sqlite3WalRead( +int sqlite3WalFindFrame( Wal *pWal, /* WAL handle */ Pgno pgno, /* Database page number to read data for */ - int *pInWal, /* OUT: True if data is read from WAL */ - int nOut, /* Size of buffer pOut in bytes */ - u8 *pOut /* Buffer to write page data to */ + u32 *piRead /* OUT: Frame number (or zero) */ ){ u32 iRead = 0; /* If !=0, WAL frame to return data from */ u32 iLast = pWal->hdr.mxFrame; /* Last page in WAL for this reader */ @@ -2315,7 +2315,7 @@ int sqlite3WalRead( ** WAL were empty. */ if( iLast==0 || pWal->readLock==0 ){ - *pInWal = 0; + *piRead = 0; return SQLITE_OK; } @@ -2386,26 +2386,31 @@ int sqlite3WalRead( } #endif - /* If iRead is non-zero, then it is the log frame number that contains the - ** required page. Read and return data from the log file. - */ - if( iRead ){ - int sz; - i64 iOffset; - sz = pWal->hdr.szPage; - sz = (sz&0xfe00) + ((sz&0x0001)<<16); - testcase( sz<=32768 ); - testcase( sz>=65536 ); - iOffset = walFrameOffset(iRead, sz) + WAL_FRAME_HDRSIZE; - *pInWal = 1; - /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL */ - return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset); - } - - *pInWal = 0; + *piRead = iRead; return SQLITE_OK; } +/* +** Read the contents of frame iRead from the wal file into buffer pOut +** (which is nOut bytes in size). Return SQLITE_OK if successful, or an +** error code otherwise. +*/ +int sqlite3WalReadFrame( + Wal *pWal, /* WAL handle */ + u32 iRead, /* Frame to read */ + int nOut, /* Size of buffer pOut in bytes */ + u8 *pOut /* Buffer to write page data to */ +){ + int sz; + i64 iOffset; + sz = pWal->hdr.szPage; + sz = (sz&0xfe00) + ((sz&0x0001)<<16); + testcase( sz<=32768 ); + testcase( sz>=65536 ); + iOffset = walFrameOffset(iRead, sz) + WAL_FRAME_HDRSIZE; + /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL */ + return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset); +} /* ** Return the size of the database in pages (or zero, if unknown). @@ -2952,6 +2957,9 @@ int sqlite3WalCheckpoint( /* Read the wal-index header. */ if( rc==SQLITE_OK ){ rc = walIndexReadHdr(pWal, &isChanged); + if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ + sqlite3OsUnfetch(pWal->pDbFd, 0, 0); + } } /* Copy data from the log to the database file. */ diff --git a/src/wal.h b/src/wal.h index a848de1527..ff7624af63 100644 --- a/src/wal.h +++ b/src/wal.h @@ -31,7 +31,6 @@ # define sqlite3WalClose(w,x,y,z) 0 # define sqlite3WalBeginReadTransaction(y,z) 0 # define sqlite3WalEndReadTransaction(z) -# define sqlite3WalRead(v,w,x,y,z) 0 # define sqlite3WalDbsize(y) 0 # define sqlite3WalBeginWriteTransaction(y) 0 # define sqlite3WalEndWriteTransaction(x) 0 @@ -71,7 +70,8 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *); void sqlite3WalEndReadTransaction(Wal *pWal); /* Read a page from the write-ahead log, if it is present. */ -int sqlite3WalRead(Wal *pWal, Pgno pgno, int *pInWal, int nOut, u8 *pOut); +int sqlite3WalFindFrame(Wal *, Pgno, u32 *); +int sqlite3WalReadFrame(Wal *, u32, int, u8 *); /* If the WAL is not empty, return the size of the database. */ Pgno sqlite3WalDbsize(Wal *pWal); diff --git a/src/where.c b/src/where.c index bd8a7541fa..d70205205c 100644 --- a/src/where.c +++ b/src/where.c @@ -2275,9 +2275,8 @@ static void bestVirtualIndex(WhereBestIdx *p){ struct sqlite3_index_constraint *pIdxCons; struct sqlite3_index_constraint_usage *pUsage; WhereTerm *pTerm; - int i, j, k; + int i, j; int nOrderBy; - int sortOrder; /* Sort order for IN clauses */ int bAllowIN; /* Allow IN optimizations */ double rCost; @@ -2376,7 +2375,6 @@ static void bestVirtualIndex(WhereBestIdx *p){ return; } - sortOrder = SQLITE_SO_ASC; pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; for(i=0; inConstraint; i++, pIdxCons++){ if( pUsage[i].argvIndex>0 ){ @@ -2391,17 +2389,28 @@ static void bestVirtualIndex(WhereBestIdx *p){ ** repeated in the output. */ break; } - for(k=0; knOrderBy; k++){ - if( pIdxInfo->aOrderBy[k].iColumn==pIdxCons->iColumn ){ - sortOrder = pIdxInfo->aOrderBy[k].desc; - break; - } - } + /* A virtual table that is constrained by an IN clause may not + ** consume the ORDER BY clause because (1) the order of IN terms + ** is not necessarily related to the order of output terms and + ** (2) Multiple outputs from a single IN value will not merge + ** together. */ + pIdxInfo->orderByConsumed = 0; } } } if( i>=pIdxInfo->nConstraint ) break; } + + /* The orderByConsumed signal is only valid if all outer loops collectively + ** generate just a single row of output. + */ + if( pIdxInfo->orderByConsumed ){ + for(i=0; ii; i++){ + if( (p->aLevel[i].plan.wsFlags & WHERE_UNIQUE)==0 ){ + pIdxInfo->orderByConsumed = 0; + } + } + } /* If there is an ORDER BY clause, and the selected virtual table index ** does not satisfy it, increase the cost of the scan accordingly. This @@ -2426,8 +2435,7 @@ static void bestVirtualIndex(WhereBestIdx *p){ } p->cost.plan.u.pVtabIdx = pIdxInfo; if( pIdxInfo->orderByConsumed ){ - assert( sortOrder==0 || sortOrder==1 ); - p->cost.plan.wsFlags |= WHERE_ORDERED + sortOrder*WHERE_REVERSE; + p->cost.plan.wsFlags |= WHERE_ORDERED; p->cost.plan.nOBSat = nOrderBy; }else{ p->cost.plan.nOBSat = p->i ? p->aLevel[p->i-1].plan.nOBSat : 0; @@ -4164,6 +4172,7 @@ static Bitmask codeOneLoopStart( int addrCont; /* Jump here to continue with next cycle */ int iRowidReg = 0; /* Rowid is stored in this register, if not zero */ int iReleaseReg = 0; /* Temp register to free before returning */ + Bitmask newNotReady; /* Return value */ pParse = pWInfo->pParse; v = pParse->pVdbe; @@ -4174,6 +4183,7 @@ static Bitmask codeOneLoopStart( bRev = (pLevel->plan.wsFlags & WHERE_REVERSE)!=0; omitTable = (pLevel->plan.wsFlags & WHERE_IDX_ONLY)!=0 && (wctrlFlags & WHERE_FORCE_TABLE)==0; + VdbeNoopComment((v, "Begin Join Loop %d", iLevel)); /* Create labels for the "break" and "continue" instructions ** for the current loop. Jump to addrBrk to break out of a loop. @@ -4824,7 +4834,7 @@ static Bitmask codeOneLoopStart( pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrBrk); pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP; } - notReady &= ~getMask(pWC->pMaskSet, iCur); + newNotReady = notReady & ~getMask(pWC->pMaskSet, iCur); /* Insert code to test every subexpression that can be completely ** computed using the current set of tables. @@ -4838,7 +4848,7 @@ static Bitmask codeOneLoopStart( testcase( pTerm->wtFlags & TERM_VIRTUAL ); /* IMP: R-30575-11662 */ testcase( pTerm->wtFlags & TERM_CODED ); if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; - if( (pTerm->prereqAll & notReady)!=0 ){ + if( (pTerm->prereqAll & newNotReady)!=0 ){ testcase( pWInfo->untestedTerms==0 && (pWInfo->wctrlFlags & WHERE_ONETABLE_ONLY)!=0 ); pWInfo->untestedTerms = 1; @@ -4853,6 +4863,32 @@ static Bitmask codeOneLoopStart( pTerm->wtFlags |= TERM_CODED; } + /* Insert code to test for implied constraints based on transitivity + ** of the "==" operator. + ** + ** Example: If the WHERE clause contains "t1.a=t2.b" and "t2.b=123" + ** and we are coding the t1 loop and the t2 loop has not yet coded, + ** then we cannot use the "t1.a=t2.b" constraint, but we can code + ** the implied "t1.a=123" constraint. + */ + for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){ + Expr *pE; + WhereTerm *pAlt; + Expr sEq; + if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; + if( pTerm->eOperator!=(WO_EQUIV|WO_EQ) ) continue; + if( pTerm->leftCursor!=iCur ) continue; + pE = pTerm->pExpr; + assert( !ExprHasProperty(pE, EP_FromJoin) ); + assert( (pTerm->prereqRight & newNotReady)!=0 ); + pAlt = findTerm(pWC, iCur, pTerm->u.leftColumn, notReady, WO_EQ|WO_IN, 0); + if( pAlt==0 ) continue; + VdbeNoopComment((v, "begin transitive constraint")); + sEq = *pAlt->pExpr; + sEq.pLeft = pE->pLeft; + sqlite3ExprIfFalse(pParse, &sEq, addrCont, SQLITE_JUMPIFNULL); + } + /* For a LEFT OUTER JOIN, generate code that will record the fact that ** at least one row of the right table has matched the left table. */ @@ -4865,7 +4901,7 @@ static Bitmask codeOneLoopStart( testcase( pTerm->wtFlags & TERM_VIRTUAL ); /* IMP: R-30575-11662 */ testcase( pTerm->wtFlags & TERM_CODED ); if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; - if( (pTerm->prereqAll & notReady)!=0 ){ + if( (pTerm->prereqAll & newNotReady)!=0 ){ assert( pWInfo->untestedTerms ); continue; } @@ -4876,7 +4912,7 @@ static Bitmask codeOneLoopStart( } sqlite3ReleaseTempReg(pParse, iReleaseReg); - return notReady; + return newNotReady; } #if defined(SQLITE_TEST) diff --git a/test/btreefault.test b/test/btreefault.test new file mode 100644 index 0000000000..45d326da7f --- /dev/null +++ b/test/btreefault.test @@ -0,0 +1,50 @@ +# 2013 April 02 +# +# 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 contains fault injection tests designed to test the btree.c +# module. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set testprefix btreefault + +do_test 1-pre1 { + execsql { + PRAGMA auto_vacuum = incremental; + PRAGMA journal_mode = DELETE; + CREATE TABLE t1(a PRIMARY KEY, b); + INSERT INTO t1 VALUES(randomblob(1000), randomblob(100)); + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + DELETE FROM t1 WHERE rowid%2; + } + faultsim_save_and_close +} {} + +do_faultsim_test 1 -prep { + faultsim_restore_and_reopen + set ::STMT [sqlite3_prepare db "SELECT * FROM t1 ORDER BY a" -1 DUMMY] + sqlite3_step $::STMT + sqlite3_step $::STMT +} -body { + execsql { PRAGMA incremental_vacuum = 10 } +} -test { + sqlite3_finalize $::STMT + faultsim_test_result {0 {}} + faultsim_integrity_check +} + +finish_test + diff --git a/test/dbstatus2.test b/test/dbstatus2.test index 18bb0870bd..2541a1a823 100644 --- a/test/dbstatus2.test +++ b/test/dbstatus2.test @@ -40,6 +40,7 @@ proc db_write {db {reset 0}} { do_test 1.1 { db close sqlite3 db test.db + execsql { PRAGMA mmap_size = 0 } expr {[file size test.db] / 1024} } 6 diff --git a/test/exclusive2.test b/test/exclusive2.test index 2208da5101..54203e3d8f 100644 --- a/test/exclusive2.test +++ b/test/exclusive2.test @@ -25,6 +25,14 @@ ifcapable {!pager_pragmas} { return } +# Tests in this file verify that locking_mode=exclusive causes SQLite to +# use cached pages even if the database is changed on disk. This doesn't +# work with mmap. +if {[permutation]=="mmap"} { + finish_test + return +} + # This module does not work right if the cache spills at unexpected # moments. So disable the soft-heap-limit. # diff --git a/test/fts3aux1.test b/test/fts3aux1.test index adda586353..ef17949fd9 100644 --- a/test/fts3aux1.test +++ b/test/fts3aux1.test @@ -354,10 +354,10 @@ do_execsql_test 3.1.1 { do_catchsql_test 3.1.2 { CREATE VIRTUAL TABLE terms2 USING fts4aux; -} {1 {wrong number of arguments to fts4aux constructor}} +} {1 {invalid arguments to fts4aux constructor}} do_catchsql_test 3.1.3 { CREATE VIRTUAL TABLE terms2 USING fts4aux(t2, t2); -} {1 {wrong number of arguments to fts4aux constructor}} +} {1 {invalid arguments to fts4aux constructor}} do_execsql_test 3.2.1 { CREATE VIRTUAL TABLE terms3 USING fts4aux(does_not_exist) @@ -444,7 +444,6 @@ do_plansql_test 4.5 { # odd name (one that requires quoting for use in SQL statements). And that # the argument to the fts4aux constructor is properly dequoted before use. # -# do_execsql_test 5.1 { CREATE VIRTUAL TABLE "abc '!' def" USING fts4(x, y); INSERT INTO "abc '!' def" VALUES('XX', 'YY'); @@ -458,5 +457,66 @@ do_execsql_test 5.2 { SELECT * FROM "%%^^%%"; } {xx * 1 1 xx 0 1 1 yy * 1 1 yy 1 1 1} +#------------------------------------------------------------------------- +# Test that we can create an fts4aux table in the temp database. +# +forcedelete test.db2 +do_execsql_test 6.1 { + CREATE VIRTUAL TABLE ft1 USING fts4(x, y); + INSERT INTO ft1 VALUES('a b', 'c d'); + INSERT INTO ft1 VALUES('e e', 'c d'); + INSERT INTO ft1 VALUES('a a', 'b b'); + CREATE VIRTUAL TABLE temp.aux1 USING fts4aux(main, ft1); + SELECT * FROM aux1; +} { + a * 2 3 a 0 2 3 + b * 2 3 b 0 1 1 b 1 1 2 + c * 2 2 c 1 2 2 + d * 2 2 d 1 2 2 + e * 1 2 e 0 1 2 +} + +do_execsql_test 6.2 { + ATTACH 'test.db2' AS att; + CREATE VIRTUAL TABLE att.ft1 USING fts4(x, y); + INSERT INTO att.ft1 VALUES('v w', 'x y'); + INSERT INTO att.ft1 VALUES('z z', 'x y'); + INSERT INTO att.ft1 VALUES('v v', 'w w'); + CREATE VIRTUAL TABLE temp.aux2 USING fts4aux(att, ft1); + SELECT * FROM aux2; +} { + v * 2 3 v 0 2 3 + w * 2 3 w 0 1 1 w 1 1 2 + x * 2 2 x 1 2 2 + y * 2 2 y 1 2 2 + z * 1 2 z 0 1 2 +} + +foreach {tn q res1 res2} { + 1 { SELECT * FROM %%% WHERE term = 'a' } {a * 2 3 a 0 2 3} {} + 2 { SELECT * FROM %%% WHERE term = 'x' } {} {x * 2 2 x 1 2 2} + + 3 { SELECT * FROM %%% WHERE term >= 'y' } + {} {y * 2 2 y 1 2 2 z * 1 2 z 0 1 2} + + 4 { SELECT * FROM %%% WHERE term <= 'c' } + {a * 2 3 a 0 2 3 b * 2 3 b 0 1 1 b 1 1 2 c * 2 2 c 1 2 2} {} +} { + set sql1 [string map {%%% aux1} $q] + set sql2 [string map {%%% aux2} $q] + + do_execsql_test 7.$tn.1 $sql1 $res1 + do_execsql_test 7.$tn.2 $sql2 $res2 +} + +do_test 8.1 { + catchsql { CREATE VIRTUAL TABLE att.aux3 USING fts4aux(main, ft1) } +} {1 {invalid arguments to fts4aux constructor}} + +do_test 8.2 { + execsql {DETACH att} + catchsql { SELECT * FROM aux2 } +} {1 {SQL logic error or missing database}} finish_test + diff --git a/test/fts3tok1.test b/test/fts3tok1.test new file mode 100644 index 0000000000..89f799606b --- /dev/null +++ b/test/fts3tok1.test @@ -0,0 +1,125 @@ +# 2013 April 22 +# +# 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. The +# focus of this script is testing the "fts3tokenize" virtual table +# that is part of the FTS3 module. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +ifcapable !fts3 { finish_test ; return } +set ::testprefix fts3tok1 + +#------------------------------------------------------------------------- +# Simple test cases. Using the default (simple) tokenizer. +# +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING fts3tokenize(simple); + CREATE VIRTUAL TABLE t2 USING fts3tokenize(); + CREATE VIRTUAL TABLE t3 USING fts3tokenize(simple, '', 'xyz '); +} + +foreach {tn tbl} {1 t1 2 t2 3 t3} { + do_execsql_test 1.$tn.1 "SELECT * FROM $tbl WHERE input = 'one two three'" { + {one two three} one 0 3 0 + {one two three} two 4 7 1 + {one two three} three 8 13 2 + } + + do_execsql_test 1.$tn.2 " + SELECT token FROM $tbl WHERE input = 'OnE tWo tHrEe' + " { + one two three + } +} + +do_execsql_test 1.4 { + SELECT token FROM t3 WHERE input = '1x2x3x' +} {1 2 3} + +do_execsql_test 1.5 { + SELECT token FROM t1 WHERE input = '1x2x3x' +} {1x2x3x} + +do_execsql_test 1.6 { + SELECT token FROM t3 WHERE input = '1''2x3x' +} {1'2 3} + +do_execsql_test 1.7 { + SELECT token FROM t3 WHERE input = '' +} {} + +do_execsql_test 1.8 { + SELECT token FROM t3 WHERE input = NULL +} {} + +do_execsql_test 1.9 { + SELECT * FROM t3 WHERE input = 123 +} {123 123 0 3 0} + +do_execsql_test 1.10 { + SELECT * FROM t1 WHERE input = 'a b c' AND token = 'b'; +} { + {a b c} b 2 3 1 +} + +do_execsql_test 1.11 { + SELECT * FROM t1 WHERE token = 'b' AND input = 'a b c'; +} { + {a b c} b 2 3 1 +} + +do_execsql_test 1.12 { + SELECT * FROM t1 WHERE input < 'b' AND input = 'a b c'; +} { + {a b c} a 0 1 0 + {a b c} b 2 3 1 + {a b c} c 4 5 2 +} + +do_execsql_test 1.13.1 { + CREATE TABLE c1(x); + INSERT INTO c1(x) VALUES('a b c'); + INSERT INTO c1(x) VALUES('d e f'); +} +breakpoint +do_execsql_test 1.13.2 { + SELECT * FROM c1, t1 WHERE input = x AND c1.rowid=t1.rowid; +} { + {a b c} {a b c} a 0 1 0 + {d e f} {d e f} e 2 3 1 +} + + +#------------------------------------------------------------------------- +# Error cases. +# +do_catchsql_test 2.0 { + CREATE VIRTUAL TABLE tX USING fts3tokenize(nosuchtokenizer); +} {1 {unknown tokenizer: nosuchtokenizer}} + +proc fts3_tokenizer {args} { return 1 } +db function fts3_tokenizer -argcount 1 fts3_tokenizer +do_catchsql_test 2.1 { + CREATE VIRTUAL TABLE tX USING fts3tokenize(simple); +} {1 {vtable constructor failed: tX}} + +db close +sqlite3 db test.db +do_catchsql_test 2.2 { + CREATE VIRTUAL TABLE t4 USING fts3tokenize; + SELECT * FROM t4; +} {1 {SQL logic error or missing database}} + + +finish_test + + diff --git a/test/fts3tok_err.test b/test/fts3tok_err.test new file mode 100644 index 0000000000..aaa7272b7b --- /dev/null +++ b/test/fts3tok_err.test @@ -0,0 +1,49 @@ +# 2013 April 22 +# +# 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. The +# focus of this script is testing the "fts3tokenize" virtual table +# that is part of the FTS3 module. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +ifcapable !fts3 { finish_test ; return } +set ::testprefix fts3tok_err + + +faultsim_save_and_close +do_faultsim_test fts3tok_err-1 -faults oom* -prep { + faultsim_restore_and_reopen +} -body { + execsql { CREATE VIRTUAL TABLE t1 USING fts3tokenize("simple"); } +} -test { + faultsim_test_result {0 {}} +} + +do_test fts3tok_err-2.prep { + faultsim_delete_and_reopen + execsql { CREATE VIRTUAL TABLE t1 USING fts3tokenize("simple"); } + faultsim_save_and_close +} {} + +do_faultsim_test fts3tok_err-2 -faults oom* -prep { + faultsim_restore_and_reopen +} -body { + execsql { SELECT token FROM t1 WHERE input = 'A galaxy far, far away' } +} -test { + faultsim_test_result {0 {a galaxy far far away}} +} + + +finish_test + + diff --git a/test/func.test b/test/func.test index f09ff49805..4ab7688461 100644 --- a/test/func.test +++ b/test/func.test @@ -1273,11 +1273,13 @@ do_test func-29.3 { sqlite3_db_status db CACHE_MISS 1 db eval {SELECT typeof(+x) FROM t29 ORDER BY id} } {integer null real blob text} -do_test func-29.4 { - set x [lindex [sqlite3_db_status db CACHE_MISS 1] 1] - if {$x>100} {set x many} - set x -} {many} +if {[permutation] != "mmap"} { + do_test func-29.4 { + set x [lindex [sqlite3_db_status db CACHE_MISS 1] 1] + if {$x>100} {set x many} + set x + } {many} +} do_test func-29.5 { db close sqlite3 db test.db diff --git a/test/incrblob.test b/test/incrblob.test index 6634f90f9c..4277e5c4c1 100644 --- a/test/incrblob.test +++ b/test/incrblob.test @@ -123,6 +123,7 @@ foreach AutoVacuumMode [list 0 1] { forcedelete test.db test.db-journal sqlite3 db test.db + execsql "PRAGMA mmap_size = 0" execsql "PRAGMA auto_vacuum = $AutoVacuumMode" do_test incrblob-2.$AutoVacuumMode.1 { @@ -149,6 +150,7 @@ foreach AutoVacuumMode [list 0 1] { # Open and close the db to make sure the page cache is empty. db close sqlite3 db test.db + execsql "PRAGMA mmap_size = 0" # Read the last 20 bytes of the blob via a blob handle. set ::blob [db incrblob blobs v 1] @@ -171,6 +173,7 @@ foreach AutoVacuumMode [list 0 1] { # Open and close the db to make sure the page cache is empty. db close sqlite3 db test.db + execsql "PRAGMA mmap_size = 0" # Write the second-to-last 20 bytes of the blob via a blob handle. # @@ -200,6 +203,7 @@ foreach AutoVacuumMode [list 0 1] { # Open and close the db to make sure the page cache is empty. db close sqlite3 db test.db + execsql { PRAGMA mmap_size = 0 } execsql { SELECT i FROM blobs } } {45} diff --git a/test/loadext.test b/test/loadext.test index 72eff12f7a..0d5b841980 100644 --- a/test/loadext.test +++ b/test/loadext.test @@ -139,20 +139,22 @@ do_test loadext-2.1 { sqlite3_load_extension db "${testextension}xx" } msg] list $rc $msg -} [list 1 [format $dlerror_nosuchfile ${testextension}xx]] +} /[list 1 [format $dlerror_nosuchfile ${testextension}xx.*]]/ # Try to load an extension for which the file is not a shared object # do_test loadext-2.2 { - set fd [open "${testextension}xx" w] + set fd [open "./notasharedlib.so" w] + puts $fd blah + close $fd + set fd [open "./notasharedlib.dll" w] puts $fd blah close $fd set rc [catch { - sqlite3_load_extension db "${testextension}xx" + sqlite3_load_extension db "./notasharedlib" } msg] - set expected_error_pattern [format $dlerror_notadll ${testextension}xx] - list $rc [string match $expected_error_pattern $msg] -} [list 1 1] + list $rc $msg +} /[list 1 [format $dlerror_notadll ./notasharedlib.*]]/ # Try to load an extension for which the file is present but the # entry point is not. @@ -196,7 +198,7 @@ do_test loadext-3.2 { regsub {0x[1234567890abcdefABCDEF]*} $res XXX res } set res -} [list 1 [format $dlerror_nosymbol $testextension sqlite3_extension_init]] +} /[list 1 [format $dlerror_nosymbol $testextension sqlite3_.*_init]]/ do_test loadext-3.3 { catchsql { SELECT load_extension($::testextension,'testloadext_init') diff --git a/test/malloc_common.tcl b/test/malloc_common.tcl index 5937b958f8..2ac619b1df 100644 --- a/test/malloc_common.tcl +++ b/test/malloc_common.tcl @@ -264,7 +264,7 @@ proc faultsim_test_result_int {args} { set t [list $testrc $testresult] set r $args if { ($testnfail==0 && $t != [lindex $r 0]) || [lsearch $r $t]<0 } { - error "nfail=$testnfail rc=$testrc result=$testresult" + error "nfail=$testnfail rc=$testrc result=$testresult list=$r" } } diff --git a/test/mmap1.test b/test/mmap1.test new file mode 100644 index 0000000000..0d0e3fd7a4 --- /dev/null +++ b/test/mmap1.test @@ -0,0 +1,317 @@ +# 2013 March 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +ifcapable !mmap { + finish_test + return +} +source $testdir/lock_common.tcl +set testprefix mmap1 + +proc nRead {db} { + set bt [btree_from_db $db] + db_enter $db + array set stats [btree_pager_stats $bt] + db_leave $db + # puts [array get stats] + return $stats(read) +} + +proc register_rblob_code {dbname seed} { + return [subst -nocommands { + set ::rcnt $seed + proc rblob {n} { + set ::rcnt [expr (([set ::rcnt] << 3) + [set ::rcnt] + 456) & 0xFFFFFFFF] + set str [format %.8x [expr [set ::rcnt] ^ 0xbdf20da3]] + string range [string repeat [set str] [expr [set n]/4]] 1 [set n] + } + $dbname func rblob rblob + }] +} + +foreach {t mmap_size nRead c2init} { + 1.1 { PRAGMA mmap_size = 67108864 } 4 {PRAGMA mmap_size = 0} + 1.2 { PRAGMA mmap_size = 53248 } 150 {PRAGMA mmap_size = 0} + 1.3 { PRAGMA mmap_size = 0 } 344 {PRAGMA mmap_size = 0} + 1.4 { PRAGMA mmap_size = 67108864 } 4 {PRAGMA mmap_size = 67108864 } + 1.5 { PRAGMA mmap_size = 53248 } 150 {PRAGMA mmap_size = 67108864 } + 1.6 { PRAGMA mmap_size = 0 } 344 {PRAGMA mmap_size = 67108864 } +} { + do_multiclient_test tn { + sql1 {PRAGMA page_size=1024} + sql1 $mmap_size + sql2 $c2init + + code2 [register_rblob_code db2 0] + + sql2 { + PRAGMA page_size=1024; + PRAGMA auto_vacuum = 1; + CREATE TABLE t1(a, b, UNIQUE(a, b)); + INSERT INTO t1 VALUES(rblob(500), rblob(500)); + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 2 + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 4 + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 8 + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 16 + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 32 + } + do_test $t.$tn.1 { + sql1 "SELECT count(*) FROM t1; PRAGMA integrity_check ; PRAGMA page_count" + } {32 ok 77} + + # Have connection 2 shrink the file. Check connection 1 can still read it. + sql2 { DELETE FROM t1 WHERE rowid%2; } + do_test $t.$tn.2 { + sql1 "SELECT count(*) FROM t1; PRAGMA integrity_check ; PRAGMA page_count" + } {16 ok 42} + + # Have connection 2 grow the file. Check connection 1 can still read it. + sql2 { INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1 } + do_test $t.$tn.3 { + sql1 "SELECT count(*) FROM t1; PRAGMA integrity_check ; PRAGMA page_count" + } {32 ok 79} + + # Have connection 2 grow the file again. Check connection 1 is still ok. + sql2 { INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1 } + do_test $t.$tn.4 { + sql1 "SELECT count(*) FROM t1; PRAGMA integrity_check ; PRAGMA page_count" + } {64 ok 149} + + # Check that the number of pages read by connection 1 indicates that the + # "PRAGMA mmap_size" command worked. + do_test $t.$tn.5 { nRead db } $nRead + } +} + +set ::rcnt 0 +proc rblob {n} { + set ::rcnt [expr (($::rcnt << 3) + $::rcnt + 456) & 0xFFFFFFFF] + set str [format %.8x [expr $::rcnt ^ 0xbdf20da3]] + string range [string repeat $str [expr $n/4]] 1 $n +} + +reset_db +db func rblob rblob + +do_execsql_test 2.1 { + PRAGMA auto_vacuum = 1; + PRAGMA mmap_size = 67108864; + PRAGMA journal_mode = wal; + CREATE TABLE t1(a, b, UNIQUE(a, b)); + INSERT INTO t1 VALUES(rblob(500), rblob(500)); + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 2 + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 4 + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 8 + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 16 + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 32 + PRAGMA wal_checkpoint; +} {67108864 wal 0 103 103} + +do_execsql_test 2.2 { + PRAGMA auto_vacuum; + SELECT count(*) FROM t1; +} {1 32} + +do_test 2.3 { + sqlite3 db2 test.db + db2 func rblob rblob + db2 eval { + DELETE FROM t1 WHERE (rowid%4); + PRAGMA wal_checkpoint; + } + db2 eval { + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 16 + SELECT count(*) FROM t1; + } +} {16} + +do_execsql_test 2.4 { + PRAGMA wal_checkpoint; +} {0 24 24} + +db2 close +reset_db +db func rblob rblob +do_execsql_test 3.1 { + PRAGMA auto_vacuum = 1; + + CREATE TABLE t1(a, b, UNIQUE(a, b)); + INSERT INTO t1 VALUES(rblob(500), rblob(500)); + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 2 + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 4 + INSERT INTO t1 SELECT rblob(500), rblob(500) FROM t1; -- 8 + + CREATE TABLE t2(a, b, UNIQUE(a, b)); + INSERT INTO t2 SELECT * FROM t1; +} {} + +do_test 3.2 { + set nRow 0 + db eval {SELECT * FROM t2 ORDER BY a, b} { + if {$nRow==4} { db eval { DELETE FROM t1 } } + incr nRow + } + set nRow +} {8} + +#------------------------------------------------------------------------- +# Ensure that existing cursors using xFetch() pages see changes made +# to rows using the incrblob API. +# +reset_db +set aaa [string repeat a 400] +set bbb [string repeat b 400] +set ccc [string repeat c 400] +set ddd [string repeat d 400] +set eee [string repeat e 400] + +do_execsql_test 4.1 { + PRAGMA page_size = 1024; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES($aaa); + INSERT INTO t1 VALUES($bbb); + INSERT INTO t1 VALUES($ccc); + INSERT INTO t1 VALUES($ddd); + SELECT * FROM t1; + BEGIN; +} [list $aaa $bbb $ccc $ddd] + +do_test 4.2 { + set ::STMT [sqlite3_prepare db "SELECT * FROM t1 ORDER BY rowid" -1 dummy] + sqlite3_step $::STMT + sqlite3_column_text $::STMT 0 +} $aaa + +do_test 4.3 { + foreach r {2 3 4} { + set fd [db incrblob t1 x $r] + puts -nonewline $fd $eee + close $fd + } + + set res [list] + while {"SQLITE_ROW" == [sqlite3_step $::STMT]} { + lappend res [sqlite3_column_text $::STMT 0] + } + set res +} [list $eee $eee $eee] + +do_test 4.4 { + sqlite3_finalize $::STMT +} SQLITE_OK + +do_execsql_test 4.5 { COMMIT } + +#------------------------------------------------------------------------- +# Ensure that existing cursors holding xFetch() references are not +# confused if those pages are moved to make way for the root page of a +# new table or index. +# +reset_db +do_execsql_test 5.1 { + PRAGMA auto_vacuum = 2; + PRAGMA page_size = 1024; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES($aaa); + INSERT INTO t1 VALUES($bbb); + INSERT INTO t1 VALUES($ccc); + INSERT INTO t1 VALUES($ddd); + + PRAGMA auto_vacuum; + SELECT * FROM t1; +} [list 2 $aaa $bbb $ccc $ddd] + +do_test 5.2 { + set ::STMT [sqlite3_prepare db "SELECT * FROM t1 ORDER BY rowid" -1 dummy] + sqlite3_step $::STMT + sqlite3_column_text $::STMT 0 +} $aaa + +do_execsql_test 5.3 { + CREATE TABLE t2(x); + INSERT INTO t2 VALUES('tricked you!'); + INSERT INTO t2 VALUES('tricked you!'); +} + +do_test 5.4 { + sqlite3_step $::STMT + sqlite3_column_text $::STMT 0 +} $bbb + +do_test 5.5 { + sqlite3_finalize $::STMT +} SQLITE_OK + +#------------------------------------------------------------------------- +# Test various mmap_size settings. +# +foreach {tn1 mmap1 mmap2} { + 1 6144 167773 + 2 18432 140399 + 3 43008 401302 + 4 92160 253899 + 5 190464 2 + 6 387072 752431 + 7 780288 291143 + 8 1566720 594306 + 9 3139584 829137 + 10 6285312 793963 + 11 12576768 1015590 +} { + do_multiclient_test tn { + sql1 { + CREATE TABLE t1(a PRIMARY KEY); + CREATE TABLE t2(x); + INSERT INTO t2 VALUES(''); + } + + code1 [register_rblob_code db 0] + code2 [register_rblob_code db2 444] + + sql1 "PRAGMA mmap_size = $mmap1" + sql2 "PRAGMA mmap_size = $mmap2" + + do_test $tn1.$tn { + for {set i 1} {$i <= 100} {incr i} { + if {$i % 2} { + set c1 sql1 + set c2 sql2 + } else { + set c1 sql2 + set c2 sql1 + } + + $c1 { + INSERT INTO t1 VALUES( rblob(5000) ); + UPDATE t2 SET x = (SELECT md5sum(a) FROM t1); + } + + set res [$c2 { + SELECT count(*) FROM t1; + SELECT x == (SELECT md5sum(a) FROM t1) FROM t2; + PRAGMA integrity_check; + }] + if {$res != [list $i 1 ok]} { + do_test $tn1.$tn.$i { + set ::res + } [list $i 1 ok] + } + } + set res 1 + } {1} + } +} + + +finish_test diff --git a/test/mmap2.test b/test/mmap2.test new file mode 100644 index 0000000000..2c80a0eb7f --- /dev/null +++ b/test/mmap2.test @@ -0,0 +1,86 @@ +# 2013 March 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 tests the effect of the mmap() or mremap() system calls +# returning an error on the library. +# +# If either mmap() or mremap() fails, SQLite should log an error +# message, then continue accessing the database using read() and +# write() exclusively. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix mmap2 + +if {$::tcl_platform(platform)!="unix" || [test_syscall defaultvfs] != "unix"} { + finish_test + return +} +ifcapable !mmap { + finish_test + return +} + +db close +sqlite3_shutdown +test_sqlite3_log xLog +proc xLog {error_code msg} { + if {[string match os_unix.c* $msg]} { + lappend ::log $msg + } +} + +foreach syscall {mmap mremap} { + test_syscall uninstall + if {[catch {test_syscall install $syscall}]} continue + + for {set i 1} {$i < 20} {incr i} { + reset_db + + test_syscall fault $i 1 + test_syscall errno $syscall ENOMEM + set ::log "" + + do_execsql_test 1.$syscall.$i.1 { + CREATE TABLE t1(a, b, UNIQUE(a, b)); + INSERT INTO t1 VALUES(randomblob(1000), randomblob(1000)); + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + } + + set nFail [test_syscall fault 0 0] + + do_execsql_test 1.$syscall.$i.2 { + SELECT count(*) FROM t1; + PRAGMA integrity_check; + } {64 ok} + + do_test 1.$syscall.$i.3 { + expr {$nFail==0 || $nFail==1} + } {1} + + do_test 1.$syscall.$i.4.nFail=$nFail { + regexp ".*${syscall}.*" $::log + } [expr $nFail>0] + } +} + +db close +test_syscall uninstall +sqlite3_shutdown +test_sqlite3_log +sqlite3_initialize +finish_test diff --git a/test/pager1.test b/test/pager1.test index ae47b3d932..72e47805a4 100644 --- a/test/pager1.test +++ b/test/pager1.test @@ -15,6 +15,7 @@ source $testdir/tester.tcl source $testdir/lock_common.tcl source $testdir/malloc_common.tcl source $testdir/wal_common.tcl +set testprefix pager1 # Do not use a codec for tests in this file, as the database file is # manipulated directly using tcl scripts (using the [hexio_write] command). @@ -1382,6 +1383,7 @@ db2 close # testvfs tv -default 1 foreach sectorsize { + 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 } { @@ -1404,7 +1406,7 @@ foreach sectorsize { COMMIT; } file size test.db-journal - } [expr $sectorsize > 65536 ? 65536 : $sectorsize] + } [expr $sectorsize > 65536 ? 65536 : ($sectorsize<32 ? 512 : $sectorsize)] do_test pager1-10.$sectorsize.2 { execsql { @@ -2243,7 +2245,6 @@ do_test pager1-25-1 { } db close } {} -breakpoint do_test pager1-25-2 { faultsim_delete_and_reopen execsql { @@ -2523,4 +2524,295 @@ if {$::tcl_platform(platform)=="unix"} { } {one two} } +#------------------------------------------------------------------------- +# Test that appending pages to the database file then moving those pages +# to the free-list before the transaction is committed does not cause +# an error. +# +foreach {tn pragma strsize} { + 1 { PRAGMA mmap_size = 0 } 2400 + 2 { } 2400 + 3 { PRAGMA mmap_size = 0 } 4400 + 4 { } 4400 +} { + reset_db + db func a_string a_string + db eval $pragma + do_execsql_test 34.$tn.1 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + } + do_execsql_test 34.$tn.2 { + BEGIN; + INSERT INTO t1 VALUES(2, a_string($strsize)); + DELETE FROM t1 WHERE oid=2; + COMMIT; + PRAGMA integrity_check; + } {ok} +} + +#------------------------------------------------------------------------- +# +reset_db +do_test 35 { + sqlite3 db test.db + + execsql { + CREATE TABLE t1(x, y); + PRAGMA journal_mode = WAL; + INSERT INTO t1 VALUES(1, 2); + } + + execsql { + BEGIN; + CREATE TABLE t2(a, b); + } + + hexio_write test.db-shm [expr 16*1024] [string repeat 0055 8192] + catchsql ROLLBACK +} {0 {}} + +do_multiclient_test tn { + sql1 { + PRAGMA auto_vacuum = 0; + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); + } + + do_test 36.$tn.1 { + sql2 { PRAGMA max_page_count = 2 } + list [catch { sql2 { CREATE TABLE t2(x) } } msg] $msg + } {1 {database or disk is full}} + + sql1 { PRAGMA checkpoint_fullfsync = 1 } + sql1 { CREATE TABLE t2(x) } + + do_test 36.$tn.2 { + sql2 { INSERT INTO t2 VALUES('xyz') } + list [catch { sql2 { CREATE TABLE t3(x) } } msg] $msg + } {1 {database or disk is full}} +} + +forcedelete test1 test2 +foreach {tn uri} { + 1 {file:?mode=memory&cache=shared} + 2 {file:one?mode=memory&cache=shared} + 3 {file:test1?cache=shared} + 4 {file:test2?another=parameter&yet=anotherone} +} { + do_test 37.$tn { + catch { db close } + sqlite3_shutdown + sqlite3_config_uri 1 + sqlite3 db $uri + + db eval { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(1); + SELECT * FROM t1; + } + } {1} + + do_execsql_test 37.$tn.2 { + VACUUM; + SELECT * FROM t1; + } {1} + + db close + sqlite3_shutdown + sqlite3_config_uri 0 +} + +do_test 38.1 { + catch { db close } + forcedelete test.db + set fd [open test.db w] + puts $fd "hello world" + close $fd + sqlite3 db test.db + catchsql { CREATE TABLE t1(x) } +} {1 {file is encrypted or is not a database}} +do_test 38.2 { + catch { db close } + forcedelete test.db +} {} + +do_test 39.1 { + sqlite3 db test.db + execsql { + PRAGMA auto_vacuum = 1; + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('xxx'); + INSERT INTO t1 VALUES('two'); + INSERT INTO t1 VALUES(randomblob(400)); + INSERT INTO t1 VALUES(randomblob(400)); + INSERT INTO t1 VALUES(randomblob(400)); + INSERT INTO t1 VALUES(randomblob(400)); + BEGIN; + UPDATE t1 SET x = 'one' WHERE rowid=1; + } + set ::stmt [sqlite3_prepare db "SELECT * FROM t1 ORDER BY rowid" -1 dummy] + sqlite3_step $::stmt + sqlite3_column_text $::stmt 0 +} {one} +do_test 39.2 { + execsql { CREATE TABLE t2(x) } + sqlite3_step $::stmt + sqlite3_column_text $::stmt 0 +} {two} +do_test 39.3 { + sqlite3_finalize $::stmt + execsql COMMIT +} {} + +do_execsql_test 39.4 { + PRAGMA auto_vacuum = 2; + CREATE TABLE t3(x); + CREATE TABLE t4(x); + + DROP TABLE t2; + DROP TABLE t3; + DROP TABLE t4; +} +do_test 39.5 { + db close + sqlite3 db test.db + execsql { + PRAGMA cache_size = 1; + PRAGMA incremental_vacuum; + PRAGMA integrity_check; + } +} {ok} + +do_test 40.1 { + reset_db + execsql { + PRAGMA auto_vacuum = 1; + CREATE TABLE t1(x PRIMARY KEY); + INSERT INTO t1 VALUES(randomblob(1200)); + PRAGMA page_count; + } +} {6} +do_test 40.2 { + execsql { + INSERT INTO t1 VALUES(randomblob(1200)); + INSERT INTO t1 VALUES(randomblob(1200)); + INSERT INTO t1 VALUES(randomblob(1200)); + } +} {} +do_test 40.3 { + db close + sqlite3 db test.db + execsql { + PRAGMA cache_size = 1; + CREATE TABLE t2(x); + PRAGMA integrity_check; + } +} {ok} + +do_test 41.1 { + reset_db + execsql { + CREATE TABLE t1(x PRIMARY KEY); + INSERT INTO t1 VALUES(randomblob(200)); + INSERT INTO t1 SELECT randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200) FROM t1; + } +} {} +do_test 41.2 { + testvfs tv -default 1 + tv sectorsize 16384; + tv devchar [list] + db close + sqlite3 db test.db + execsql { + PRAGMA cache_size = 1; + DELETE FROM t1 WHERE rowid%4; + PRAGMA integrity_check; + } +} {ok} +db close +tv delete + +set pending_prev [sqlite3_test_control_pending_byte 0x1000000] +do_test 42.1 { + reset_db + execsql { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(randomblob(200), randomblob(200)); + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + } + db close + sqlite3_test_control_pending_byte 0x0010000 + sqlite3 db test.db + db eval { PRAGMA mmap_size = 0 } + catchsql { SELECT sum(length(y)) FROM t1 } +} {1 {database disk image is malformed}} +do_test 42.2 { + reset_db + execsql { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(randomblob(200), randomblob(200)); + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + } + db close + + testvfs tv -default 1 + tv sectorsize 16384; + tv devchar [list] + sqlite3 db test.db -vfs tv + execsql { UPDATE t1 SET x = randomblob(200) } +} {} +db close +tv delete +sqlite3_test_control_pending_byte $pending_prev + +do_test 43.1 { + reset_db + execsql { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); + CREATE TABLE t2(x, y); + INSERT INTO t2 VALUES(1, 2); + CREATE TABLE t3(x, y); + INSERT INTO t3 VALUES(1, 2); + } + db close + sqlite3 db test.db + + db eval { PRAGMA mmap_size = 0 } + db eval { SELECT * FROM t1 } + sqlite3_db_status db CACHE_MISS 0 +} {0 2 0} + +do_test 43.2 { + db eval { SELECT * FROM t2 } + sqlite3_db_status db CACHE_MISS 1 +} {0 3 0} + +do_test 43.3 { + db eval { SELECT * FROM t3 } + sqlite3_db_status db CACHE_MISS 0 +} {0 1 0} + finish_test + diff --git a/test/pager2.test b/test/pager2.test index fa5f7b9611..0e2b33b833 100644 --- a/test/pager2.test +++ b/test/pager2.test @@ -118,7 +118,6 @@ tv delete #------------------------------------------------------------------------- -# # pager2-2.1: Test a ROLLBACK with journal_mode=off. # pager2-2.2: Test shrinking the database (auto-vacuum) with # journal_mode=off @@ -148,4 +147,22 @@ do_test pager2-2.2 { file size test.db } {3072} +#------------------------------------------------------------------------- +# Test that shared in-memory databases seem to work. +# +db close +do_test pager2-3.1 { + forcedelete test.db + sqlite3_shutdown + sqlite3_config_uri 1 + + sqlite3 db1 {file:test.db?mode=memory&cache=shared} + sqlite3 db2 {file:test.db?mode=memory&cache=shared} + sqlite3 db3 test.db + + db1 eval { CREATE TABLE t1(a, b) } + db2 eval { INSERT INTO t1 VALUES(1, 2) } + list [catch { db3 eval { INSERT INTO t1 VALUES(3, 4) } } msg] $msg +} {1 {no such table: t1}} + finish_test diff --git a/test/pagerfault.test b/test/pagerfault.test index e04e97e4fc..23754fa9de 100644 --- a/test/pagerfault.test +++ b/test/pagerfault.test @@ -1246,5 +1246,304 @@ do_faultsim_test pagerfault-27 -faults ioerr-persistent -prep { faultsim_integrity_check } + +#------------------------------------------------------------------------- +# +do_test pagerfault-28-pre { + faultsim_delete_and_reopen + db func a_string a_string + execsql { + PRAGMA page_size = 512; + + PRAGMA journal_mode = wal; + PRAGMA wal_autocheckpoint = 0; + PRAGMA cache_size = 100000; + + BEGIN; + CREATE TABLE t2(a UNIQUE, b UNIQUE); + INSERT INTO t2 VALUES( a_string(800), a_string(800) ); + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + COMMIT; + CREATE TABLE t1(a PRIMARY KEY, b); + } + expr {[file size test.db-shm] >= 96*1024} +} {1} +faultsim_save_and_close + +do_faultsim_test pagerfault-28a -faults oom* -prep { + faultsim_restore_and_reopen + execsql { PRAGMA mmap_size=0 } + + sqlite3 db2 test.db + db2 eval { SELECT count(*) FROM t2 } + + db func a_string a_string + execsql { + BEGIN; + INSERT INTO t1 VALUES(a_string(2000), a_string(2000)); + INSERT INTO t1 VALUES(a_string(2000), a_string(2000)); + } + set ::STMT [sqlite3_prepare db "SELECT * FROM t1 ORDER BY a" -1 DUMMY] + sqlite3_step $::STMT +} -body { + execsql { ROLLBACK } +} -test { + db2 close + sqlite3_finalize $::STMT + catchsql { ROLLBACK } + faultsim_integrity_check +} + +faultsim_restore_and_reopen +sqlite3 db2 test.db +db2 eval {SELECT count(*) FROM t2} +db close + +do_faultsim_test pagerfault-28b -faults oom* -prep { + sqlite3 db test.db +} -body { + execsql { SELECT count(*) FROM t2 } +} -test { + faultsim_test_result {0 2048} + db close +} + +db2 close + +#------------------------------------------------------------------------- +# Try this: +# +# 1) Put the pager in ERROR state (error during rollback) +# +# 2) Next time the connection is used inject errors into all xWrite() and +# xUnlock() calls. This causes the hot-journal rollback to fail and +# the pager to declare its locking state UNKNOWN. +# +# 3) Same again. +# +# 4a) Stop injecting errors. Allow the rollback to succeed. Check that +# the database is Ok. Or, +# +# 4b) Close and reopen the db. Check that the db is Ok. +# +proc custom_injectinstall {} { + testvfs custom -default true + custom filter {xWrite xUnlock} +} +proc custom_injectuninstall {} { + catch {db close} + catch {db2 close} + custom delete +} +proc custom_injectstart {iFail} { + custom ioerr $iFail 1 +} +proc custom_injectstop {} { + custom ioerr +} +set ::FAULTSIM(custom) [list \ + -injectinstall custom_injectinstall \ + -injectstart custom_injectstart \ + -injectstop custom_injectstop \ + -injecterrlist {{1 {disk I/O error}}} \ + -injectuninstall custom_injectuninstall \ +] + +do_test pagerfault-29-pre { + faultsim_delete_and_reopen + db func a_string a_string + execsql { + PRAGMA page_size = 1024; + PRAGMA cache_size = 5; + + BEGIN; + CREATE TABLE t2(a UNIQUE, b UNIQUE); + INSERT INTO t2 VALUES( a_string(800), a_string(800) ); + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + INSERT INTO t2 SELECT a_string(800), a_string(800) FROM t2; + COMMIT; + } + expr {[file size test.db] >= 50*1024} +} {1} +faultsim_save_and_close +foreach {tn tt} { + 29 { catchsql ROLLBACK } + 30 { db close ; sqlite3 db test.db } +} { + do_faultsim_test pagerfault-$tn -faults custom -prep { + faultsim_restore_and_reopen + db func a_string a_string + execsql { + PRAGMA cache_size = 5; + BEGIN; + UPDATE t2 SET a = a_string(799); + } + } -body { + catchsql ROLLBACK + catchsql ROLLBACK + catchsql ROLLBACK + } -test { + eval $::tt + if {"ok" != [db one {PRAGMA integrity_check}]} { + error "integrity check failed" + } + } +} + +do_test pagerfault-31-pre { + sqlite3_shutdown + sqlite3_config_uri 1 +} {SQLITE_OK} +do_faultsim_test pagerfault-31 -faults oom* -body { + sqlite3 db {file:one?mode=memory&cache=shared} + db eval { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(1); + SELECT * FROM t1; + } +} -test { + faultsim_test_result {0 1} {1 {}} + catch { db close } +} +sqlite3_shutdown +sqlite3_config_uri 0 + +do_test pagerfault-32-pre { + reset_db + execsql { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('one'); + } +} {} +faultsim_save_and_close + +do_faultsim_test pagerfault-32 -prep { + faultsim_restore_and_reopen + db eval { SELECT * FROM t1; } +} -body { + execsql { SELECT * FROM t1; } +} -test { + faultsim_test_result {0 one} +} +sqlite3_shutdown +sqlite3_config_uri 0 + +do_faultsim_test pagerfault-33a -prep { + sqlite3 db :memory: + execsql { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + } +} -body { + execsql { VACUUM } +} -test { + faultsim_test_result {0 {}} +} +do_faultsim_test pagerfault-33b -prep { + sqlite3 db "" + execsql { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + } +} -body { + execsql { VACUUM } +} -test { + faultsim_test_result {0 {}} +} + +do_test pagerfault-34-pre { + reset_db + execsql { + CREATE TABLE t1(x PRIMARY KEY); + } +} {} +faultsim_save_and_close +do_faultsim_test pagerfault-34 -prep { + faultsim_restore_and_reopen + execsql { + BEGIN; + INSERT INTO t1 VALUES( randomblob(4000) ); + DELETE FROM t1; + } +} -body { + execsql COMMIT +} -test { + faultsim_test_result {0 {}} +} + +do_test pagerfault-35-pre { + faultsim_delete_and_reopen + execsql { + CREATE TABLE t1(x PRIMARY KEY, y); + INSERT INTO t1 VALUES(randomblob(200), randomblob(200)); + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + } + faultsim_save_and_close +} {} +testvfs tv -default 1 +tv sectorsize 8192; +tv devchar [list] +do_faultsim_test pagerfault-35 -prep { + faultsim_restore_and_reopen +} -body { + execsql { UPDATE t1 SET x=randomblob(200) } +} -test { + faultsim_test_result {0 {}} +} +catch {db close} +tv delete + +sqlite3_shutdown +sqlite3_config_uri 1 +do_test pagerfault-36-pre { + faultsim_delete_and_reopen + execsql { + CREATE TABLE t1(x PRIMARY KEY, y); + INSERT INTO t1 VALUES(randomblob(200), randomblob(200)); + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1; + } + faultsim_save_and_close +} {} +do_faultsim_test pagerfault-36 -prep { + faultsim_restore + sqlite3 db file:test.db?cache=shared + sqlite3 db2 file:test.db?cache=shared + db2 eval { + BEGIN; + SELECT count(*) FROM sqlite_master; + } + db eval { + PRAGMA cache_size = 1; + BEGIN; + UPDATE t1 SET x = randomblob(200); + } +} -body { + execsql ROLLBACK db +} -test { + catch { db eval {UPDATE t1 SET x = randomblob(200)} } + faultsim_test_result {0 {}} + catch { db close } + catch { db2 close } +} + +sqlite3_shutdown +sqlite3_config_uri 0 + finish_test diff --git a/test/pageropt.test b/test/pageropt.test index 82311965a5..7191661ba5 100644 --- a/test/pageropt.test +++ b/test/pageropt.test @@ -87,12 +87,17 @@ do_test pageropt-1.4 { # But if the other thread modifies the database, then the cache # must refill. # +ifcapable mmap { + set x [expr {[permutation]=="mmap" ? 1 : 6}] +} else { + set x 6 +} do_test pageropt-1.5 { db2 eval {CREATE TABLE t2(y)} pagercount_sql { SELECT hex(x) FROM t1 } -} [list 6 0 0 $blobcontent] +} [list $x 0 0 $blobcontent] do_test pageropt-1.6 { pagercount_sql { SELECT hex(x) FROM t1 diff --git a/test/permutations.test b/test/permutations.test index 18377e8aab..2e5a9ad8e5 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -141,6 +141,14 @@ test_suite "veryquick" -prefix "" -description { test_set $allquicktests -exclude *malloc* *ioerr* *fault* ] +test_suite "mmap" -prefix "mm-" -description { + Similar to veryquick. Except with memory mapping disabled. +} -presql { + pragma mmap_size = 268435456; +} -files [ + test_set $allquicktests -exclude *malloc* *ioerr* *fault* -include malloc.test +] + test_suite "valgrind" -prefix "" -description { Run the "veryquick" test suite with a couple of multi-process tests (that fail under valgrind) omitted. diff --git a/test/resolver01.test b/test/resolver01.test new file mode 100644 index 0000000000..3ca6acec47 --- /dev/null +++ b/test/resolver01.test @@ -0,0 +1,39 @@ +# 2013-04-13 +# +# 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 tests features of the name resolver (the component that +# figures out what identifiers in the SQL statement refer to) that +# were fixed by ticket [2500cdb9be] +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_test resolver01-1.1 { + catchsql { + CREATE TABLE t1(x, y); INSERT INTO t1 VALUES(11,22); + CREATE TABLE t2(y, z); INSERT INTO t2 VALUES(33,44); + SELECT 1 AS y FROM t1, t2 ORDER BY y; + } +} {0 1} +do_test resolver01-1.2 { + catchsql { + SELECT 2 AS y FROM t1, t2 ORDER BY y COLLATE nocase; + } +} {0 2} +do_test resolver01-1.3 { + catchsql { + SELECT 3 AS y FROM t1, t2 ORDER BY +y; + } +} {0 3} + + +finish_test diff --git a/test/speed1p.test b/test/speed1p.test index 915f165354..6bf7b10e1f 100644 --- a/test/speed1p.test +++ b/test/speed1p.test @@ -24,6 +24,8 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl speed_trial_init speed1 +sqlite3_memdebug_vfs_oom_test 0 + # Set a uniform random seed expr srand(0) @@ -78,7 +80,6 @@ do_test speed1p-1.0 { } } {i2a i2b t1 t2} - # 50000 INSERTs on an unindexed table # set list {} diff --git a/test/syscall.test b/test/syscall.test index d841a9a0b2..5bf1225867 100644 --- a/test/syscall.test +++ b/test/syscall.test @@ -60,7 +60,7 @@ foreach s { open close access getcwd stat fstat ftruncate fcntl read pread write pwrite fchmod fallocate pread64 pwrite64 unlink openDirectory mkdir rmdir - statvfs fchown umask + statvfs fchown umask mmap munmap mremap } { if {[test_syscall exists $s]} {lappend syscall_list $s} } diff --git a/test/sysfault.test b/test/sysfault.test index 07d525ca9e..92fb534dd0 100644 --- a/test/sysfault.test +++ b/test/sysfault.test @@ -243,5 +243,35 @@ do_faultsim_test 3 -faults vfsfault-* -prep { faultsim_test_result {0 20000} } -finish_test +#------------------------------------------------------------------------- +# Test errors in mmap(). +# +proc vfsfault_install {} { + test_syscall reset + test_syscall install {mmap} +} +faultsim_delete_and_reopen +execsql { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); +} +faultsim_save_and_close + +do_faultsim_test 4 -faults vfsfault-* -prep { + faultsim_restore_and_reopen + file_control_chunksize_test db main 8192 + execsql { + PRAGMA mmap_size = 1000000; + } +} -body { + test_syscall errno mmap EACCES + + execsql { + SELECT * FROM t1; + } +} -test { + faultsim_test_result {0 {1 2}} {1 {disk I/O error}} +} + +finish_test diff --git a/test/tester.tcl b/test/tester.tcl index d5ec3ec60b..e99cdc278a 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -462,6 +462,7 @@ if {0==[info exists ::SLAVE]} { set TC(count) 0 set TC(fail_list) [list] set TC(omit_list) [list] + set TC(warn_list) [list] proc set_test_counter {counter args} { if {[llength $args]} { @@ -496,6 +497,18 @@ proc fail_test {name} { } } +# Remember a warning message to be displayed at the conclusion of all testing +# +proc warning {msg {append 1}} { + puts "Warning: $msg" + set warnList [set_test_counter warn_list] + if {$append} { + lappend warnList $msg + } + set_test_counter warn_list $warnList +} + + # Increment the number of tests run # proc incr_ntest {} { @@ -790,6 +803,9 @@ proc finalize_testing {} { if {$nErr>0} { puts "Failures on these tests: [set_test_counter fail_list]" } + foreach warning [set_test_counter warn_list] { + puts "Warning: $warning" + } run_thread_tests 1 if {[llength $omitList]>0} { puts "Omitted test cases:" diff --git a/test/tkt2822.test b/test/tkt2822.test index d0b16338c6..d3512d3038 100644 --- a/test/tkt2822.test +++ b/test/tkt2822.test @@ -208,12 +208,15 @@ do_test tkt2822-5.4 { # In "ORDER BY +b" the term is now an expression rather than # a label. It therefore matches by rule (3) instead of rule (2). +# +# 2013-04-13: This is busted. Changed to conform to PostgreSQL and +# MySQL and Oracle behavior. # do_test tkt2822-5.5 { execsql { SELECT a AS b FROM t3 ORDER BY +b; } -} {9 1} +} {1 9} # Tests for rule 2 in compound queries # diff --git a/test/wal.test b/test/wal.test index 24ce5f86a3..d9f2145b2e 100644 --- a/test/wal.test +++ b/test/wal.test @@ -727,6 +727,9 @@ do_test wal-11.9 { list [expr [file size test.db]/1024] [log_deleted test.db-wal] } {37 1} sqlite3_wal db test.db +set nWal 39 +if {[permutation]!="mmap"} {set nWal 37} +ifcapable !mmap {set nWal 37} do_test wal-11.10 { execsql { PRAGMA cache_size = 10; @@ -735,7 +738,7 @@ do_test wal-11.10 { SELECT count(*) FROM t1; } list [expr [file size test.db]/1024] [file size test.db-wal] -} [list 37 [wal_file_size 37 1024]] +} [list 37 [wal_file_size $nWal 1024]] do_test wal-11.11 { execsql { SELECT count(*) FROM t1; @@ -745,7 +748,7 @@ do_test wal-11.11 { } {32 16} do_test wal-11.12 { list [expr [file size test.db]/1024] [file size test.db-wal] -} [list 37 [wal_file_size 37 1024]] +} [list 37 [wal_file_size $nWal 1024]] do_test wal-11.13 { execsql { INSERT INTO t1 VALUES( blob(900) ); @@ -755,7 +758,7 @@ do_test wal-11.13 { } {17 ok} do_test wal-11.14 { list [expr [file size test.db]/1024] [file size test.db-wal] -} [list 37 [wal_file_size 37 1024]] +} [list 37 [wal_file_size $nWal 1024]] #------------------------------------------------------------------------- @@ -1512,7 +1515,7 @@ do_test wal-23.3 { set nPage [expr 2+$AUTOVACUUM] do_test wal-23.4 { set ::log -} [list SQLITE_OK "Recovered $nPage frames from WAL file $walfile"] +} [list SQLITE_NOTICE "recovered $nPage frames from WAL file $walfile"] ifcapable autovacuum { diff --git a/test/wal5.test b/test/wal5.test index 6eceed5e59..68750f1479 100644 --- a/test/wal5.test +++ b/test/wal5.test @@ -235,7 +235,16 @@ foreach {testprefix do_wal_checkpoint} { do_test 2.3.$tn.5 { sql1 { INSERT INTO t2 VALUES(3, 4) } } {} do_test 2.3.$tn.6 { file_page_counts } {1 4 1 4} do_test 2.3.$tn.7 { code1 { do_wal_checkpoint db -mode full } } {1 4 3} - do_test 2.3.$tn.8 { file_page_counts } {1 4 2 4} + + # The checkpoint above only writes page 1 of the db file. The other + # page (page 2) is locked by the read-transaction opened by the + # [sql2] commmand above. So normally, the db is 1 page in size here. + # However, in mmap() mode, the db is pre-allocated to 2 pages at the + # start of the checkpoint, even though page 2 cannot be written. + set nDb 2 + if {[permutation]!="mmap"} {set nDb 1} + ifcapable !mmap {set nDb 1} + do_test 2.3.$tn.8 { file_page_counts } [list $nDb 4 2 4] } # Check that checkpoints block on the correct locks. And respond correctly @@ -343,4 +352,3 @@ foreach {testprefix do_wal_checkpoint} { finish_test - diff --git a/test/walfault.test b/test/walfault.test index 6f9aeddea2..4a9d98afda 100644 --- a/test/walfault.test +++ b/test/walfault.test @@ -548,6 +548,44 @@ do_faultsim_test walfault-14 -prep { set nRow [db eval {SELECT count(*) FROM abc}] if {!(($nRow==2 && $testrc) || $nRow==3)} { error "Bad db content" } } -finish_test + +#------------------------------------------------------------------------- +# Test fault-handling when switching out of exclusive-locking mode. +# +do_test walfault-14-pre { + faultsim_delete_and_reopen + execsql { + PRAGMA auto_vacuum = 0; + PRAGMA journal_mode = WAL; + BEGIN; + CREATE TABLE abc(a PRIMARY KEY); + INSERT INTO abc VALUES(randomblob(1500)); + INSERT INTO abc VALUES(randomblob(1500)); + COMMIT; + } + faultsim_save_and_close +} {} +do_faultsim_test walfault-14 -prep { + faultsim_restore_and_reopen + breakpoint + execsql { + SELECT count(*) FROM abc; + PRAGMA locking_mode = exclusive; + BEGIN; + INSERT INTO abc VALUES(randomblob(1500)); + COMMIT; + } +} -body { + db eval { + PRAGMA locking_mode = normal; + BEGIN; + INSERT INTO abc VALUES(randomblob(1500)); + COMMIT; + } +} -test { + faultsim_integrity_check + set nRow [db eval {SELECT count(*) FROM abc}] + if {$nRow!=3 && $nRow!=4} { error "Bad db content" } +} finish_test diff --git a/test/win32lock.test b/test/win32lock.test index d014be4d2c..724172065f 100644 --- a/test/win32lock.test +++ b/test/win32lock.test @@ -27,6 +27,7 @@ proc xLog {error_code msg} { lappend ::log $msg } sqlite3 db test.db +db eval {PRAGMA mmap_size=0} do_test win32lock-1.1 { db eval { diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl index 4058631bbf..9032d1ead7 100644 --- a/tool/mksqlite3c.tcl +++ b/tool/mksqlite3c.tcl @@ -313,6 +313,7 @@ foreach file { fts3_porter.c fts3_tokenizer.c fts3_tokenizer1.c + fts3_tokenize_vtab.c fts3_write.c fts3_snippet.c fts3_unicode.c