Index: ext/fts3/fts3.c ================================================================== --- ext/fts3/fts3.c +++ ext/fts3/fts3.c @@ -2598,11 +2598,13 @@ if( rc!=SQLITE_OK ) goto finished; if( isPrefix==0 && isScan==0 ) iLeavesEndBlock = iStartBlock; } rc = sqlite3Fts3SegReaderNew(pCsr->nSegment+1, - iStartBlock, iLeavesEndBlock, iEndBlock, zRoot, nRoot, &pSeg + (isPrefix==0 && isScan==0), + iStartBlock, iLeavesEndBlock, + iEndBlock, zRoot, nRoot, &pSeg ); if( rc!=SQLITE_OK ) goto finished; rc = fts3SegReaderCursorAppend(pCsr, pSeg); } } Index: ext/fts3/fts3Int.h ================================================================== --- ext/fts3/fts3Int.h +++ ext/fts3/fts3Int.h @@ -399,11 +399,11 @@ /* fts3_write.c */ int sqlite3Fts3UpdateMethod(sqlite3_vtab*,int,sqlite3_value**,sqlite3_int64*); int sqlite3Fts3PendingTermsFlush(Fts3Table *); void sqlite3Fts3PendingTermsClear(Fts3Table *); int sqlite3Fts3Optimize(Fts3Table *); -int sqlite3Fts3SegReaderNew(int, sqlite3_int64, +int sqlite3Fts3SegReaderNew(int, int, sqlite3_int64, sqlite3_int64, sqlite3_int64, const char *, int, Fts3SegReader**); int sqlite3Fts3SegReaderPending( Fts3Table*,int,const char*,int,int,Fts3SegReader**); void sqlite3Fts3SegReaderFree(Fts3SegReader *); int sqlite3Fts3AllSegdirs(Fts3Table*, int, int, sqlite3_stmt **); Index: ext/fts3/fts3_write.c ================================================================== --- ext/fts3/fts3_write.c +++ ext/fts3/fts3_write.c @@ -108,10 +108,11 @@ ** fts3SegReaderFirstDocid() ** fts3SegReaderNextDocid() */ struct Fts3SegReader { int iIdx; /* Index within level, or 0x7FFFFFFF for PT */ + int bLookup; /* True for a lookup only */ sqlite3_int64 iStartBlock; /* Rowid of first leaf block to traverse */ sqlite3_int64 iLeafEndBlock; /* Rowid of final leaf block to traverse */ sqlite3_int64 iEndBlock; /* Rowid of final block in segment (or 0) */ sqlite3_int64 iCurrentBlock; /* Current leaf block (or 0) */ @@ -1085,10 +1086,22 @@ ){ rc = fts3SegReaderIncrRead(pReader); } return rc; } + +/* +** Set an Fts3SegReader cursor to point at EOF. +*/ +static void fts3SegReaderSetEof(Fts3SegReader *pSeg){ + if( !fts3SegReaderIsRootOnly(pSeg) ){ + sqlite3_free(pSeg->aNode); + sqlite3_blob_close(pSeg->pBlob); + pSeg->pBlob = 0; + } + pSeg->aNode = 0; +} /* ** Move the iterator passed as the first argument to the next term in the ** segment. If successful, SQLITE_OK is returned. If there is no next term, ** SQLITE_DONE. Otherwise, an SQLite error code. @@ -1125,16 +1138,11 @@ assert( pReader->aNode ); } return SQLITE_OK; } - if( !fts3SegReaderIsRootOnly(pReader) ){ - sqlite3_free(pReader->aNode); - sqlite3_blob_close(pReader->pBlob); - pReader->pBlob = 0; - } - pReader->aNode = 0; + fts3SegReaderSetEof(pReader); /* If iCurrentBlock>=iLeafEndBlock, this is an EOF condition. All leaf ** blocks have already been traversed. */ assert( pReader->iCurrentBlock<=pReader->iLeafEndBlock ); if( pReader->iCurrentBlock>=pReader->iLeafEndBlock ){ @@ -1377,10 +1385,11 @@ /* ** Allocate a new SegReader object. */ int sqlite3Fts3SegReaderNew( int iAge, /* Segment "age". */ + int bLookup, /* True for a lookup only */ sqlite3_int64 iStartLeaf, /* First leaf to traverse */ sqlite3_int64 iEndLeaf, /* Final leaf to traverse */ sqlite3_int64 iEndBlock, /* Final block of segment */ const char *zRoot, /* Buffer containing root node */ int nRoot, /* Size of buffer containing root node */ @@ -1399,10 +1408,11 @@ if( !pReader ){ return SQLITE_NOMEM; } memset(pReader, 0, sizeof(Fts3SegReader)); pReader->iIdx = iAge; + pReader->bLookup = bLookup; pReader->iStartBlock = iStartLeaf; pReader->iLeafEndBlock = iEndLeaf; pReader->iEndBlock = iEndBlock; if( nExtra ){ @@ -2401,15 +2411,20 @@ ** equal or greater value than the specified term. This prevents many ** unnecessary merge/sort operations for the case where single segment ** b-tree leaf nodes contain more than one term. */ for(i=0; pCsr->bRestart==0 && inSegment; i++){ + int res; Fts3SegReader *pSeg = pCsr->apSegment[i]; do { int rc = fts3SegReaderNext(p, pSeg, 0); if( rc!=SQLITE_OK ) return rc; - }while( zTerm && fts3SegReaderTermCmp(pSeg, zTerm, nTerm)<0 ); + }while( zTerm && (res = fts3SegReaderTermCmp(pSeg, zTerm, nTerm))<0 ); + + if( pSeg->bLookup && res!=0 ){ + fts3SegReaderSetEof(pSeg); + } } fts3SegReaderSort(pCsr->apSegment, nSeg, nSeg, fts3SegReaderCmp); return SQLITE_OK; } @@ -2526,11 +2541,16 @@ /* Advance the first pCsr->nAdvance entries in the apSegment[] array ** forward. Then sort the list in order of current term again. */ for(i=0; inAdvance; i++){ - rc = fts3SegReaderNext(p, apSegment[i], 0); + Fts3SegReader *pSeg = apSegment[i]; + if( pSeg->bLookup ){ + fts3SegReaderSetEof(pSeg); + }else{ + rc = fts3SegReaderNext(p, pSeg, 0); + } if( rc!=SQLITE_OK ) return rc; } fts3SegReaderSort(apSegment, nSegment, pCsr->nAdvance, fts3SegReaderCmp); pCsr->nAdvance = 0; Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -3977,11 +3977,11 @@ && pBt->pPage1->aData[19]==0x01 /* (5) */ ){ u8 aSave[4]; u8 *aWrite = &pBuf[-4]; memcpy(aSave, aWrite, 4); - rc = sqlite3OsRead(fd, aWrite, a+4, pBt->pageSize * (nextPage-1)); + rc = sqlite3OsRead(fd, aWrite, a+4, (i64)pBt->pageSize*(nextPage-1)); nextPage = get4byte(aWrite); memcpy(aWrite, aSave, 4); }else #endif Index: src/loadext.c ================================================================== --- src/loadext.c +++ src/loadext.c @@ -623,10 +623,11 @@ ** If anything goes wrong, set an error in the database connection. */ void sqlite3AutoLoadExtensions(sqlite3 *db){ int i; int go = 1; + int rc; int (*xInit)(sqlite3*,char**,const sqlite3_api_routines*); wsdAutoextInit; if( wsdAutoext.nExt==0 ){ /* Common case: early out without every having to acquire a mutex */ @@ -645,13 +646,13 @@ xInit = (int(*)(sqlite3*,char**,const sqlite3_api_routines*)) wsdAutoext.aExt[i]; } sqlite3_mutex_leave(mutex); zErrmsg = 0; - if( xInit && xInit(db, &zErrmsg, &sqlite3Apis) ){ - sqlite3Error(db, SQLITE_ERROR, + if( xInit && (rc = xInit(db, &zErrmsg, &sqlite3Apis))!=0 ){ + sqlite3Error(db, rc, "automatic extension loading failed: %s", zErrmsg); go = 0; } sqlite3_free(zErrmsg); } } Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -237,12 +237,12 @@ /* Do extra initialization steps requested by the SQLITE_EXTRA_INIT ** compile-time option. */ #ifdef SQLITE_EXTRA_INIT if( rc==SQLITE_OK && sqlite3GlobalConfig.isInit ){ - int SQLITE_EXTRA_INIT(void); - rc = SQLITE_EXTRA_INIT(); + int SQLITE_EXTRA_INIT(const char*); + rc = SQLITE_EXTRA_INIT(0); } #endif return rc; } @@ -255,10 +255,14 @@ ** on when SQLite is already shut down. If SQLite is already shut down ** when this routine is invoked, then this routine is a harmless no-op. */ int sqlite3_shutdown(void){ if( sqlite3GlobalConfig.isInit ){ +#ifdef SQLITE_EXTRA_SHUTDOWN + void SQLITE_EXTRA_SHUTDOWN(void); + SQLITE_EXTRA_SHUTDOWN(); +#endif sqlite3_os_end(); sqlite3_reset_auto_extension(); sqlite3GlobalConfig.isInit = 0; } if( sqlite3GlobalConfig.isPCacheInit ){ @@ -2244,14 +2248,17 @@ sqlite3RegisterBuiltinFunctions(db); /* Load automatic extensions - extensions that have been registered ** using the sqlite3_automatic_extension() API. */ - sqlite3AutoLoadExtensions(db); rc = sqlite3_errcode(db); - if( rc!=SQLITE_OK ){ - goto opendb_out; + if( rc==SQLITE_OK ){ + sqlite3AutoLoadExtensions(db); + rc = sqlite3_errcode(db); + if( rc!=SQLITE_OK ){ + goto opendb_out; + } } #ifdef SQLITE_ENABLE_FTS1 if( !db->mallocFailed ){ extern int sqlite3Fts1Init(sqlite3*); Index: src/os.h ================================================================== --- src/os.h +++ src/os.h @@ -98,10 +98,41 @@ # define SQLITE_TEMPNAME_SIZE (CCHMAXPATHCOMP) #else # define SQLITE_TEMPNAME_SIZE 200 #endif +/* +** Determine if we are dealing with Windows NT. +** +** We ought to be able to determine if we are compiling for win98 or winNT +** using the _WIN32_WINNT macro as follows: +** +** #if defined(_WIN32_WINNT) +** # define SQLITE_OS_WINNT 1 +** #else +** # define SQLITE_OS_WINNT 0 +** #endif +** +** However, vs2005 does not set _WIN32_WINNT by default, as it ought to, +** so the above test does not work. We'll just assume that everything is +** winNT unless the programmer explicitly says otherwise by setting +** SQLITE_OS_WINNT to 0. +*/ +#if SQLITE_OS_WIN && !defined(SQLITE_OS_WINNT) +# define SQLITE_OS_WINNT 1 +#endif + +/* +** Determine if we are dealing with WindowsCE - which has a much +** reduced API. +*/ +#if defined(_WIN32_WCE) +# define SQLITE_OS_WINCE 1 +#else +# define SQLITE_OS_WINCE 0 +#endif + /* If the SET_FULLSYNC macro is not defined above, then make it ** a no-op */ #ifndef SET_FULLSYNC # define SET_FULLSYNC(x,y) Index: src/os_unix.c ================================================================== --- src/os_unix.c +++ src/os_unix.c @@ -204,10 +204,11 @@ ** VFS implementations. */ typedef struct unixFile unixFile; struct unixFile { sqlite3_io_methods const *pMethod; /* Always the first entry */ + sqlite3_vfs *pVfs; /* The VFS that created this unixFile */ unixInodeInfo *pInode; /* Info about locks on this inode */ int h; /* The file descriptor */ unsigned char eFileLock; /* The type of lock held on this fd */ unsigned char ctrlFlags; /* Behavioral bits. UNIXFILE_* flags */ int lastErrno; /* The unix errno from last I/O error */ @@ -3531,10 +3532,14 @@ }else{ pFile->ctrlFlags |= UNIXFILE_PERSIST_WAL; } return SQLITE_OK; } + case SQLITE_FCNTL_VFSNAME: { + *(char**)pArg = sqlite3_mprintf("%s", pFile->pVfs->zName); + return SQLITE_OK; + } #ifndef NDEBUG /* The pager calls this method to signal that it has done ** a rollback and that the database is therefore unchanged and ** it hence it is OK for the transaction change counter to be ** unchanged. @@ -4558,10 +4563,11 @@ /* No locking occurs in temporary files */ assert( zFilename!=0 || noLock ); OSTRACE(("OPEN %-3d %s\n", h, zFilename)); pNew->h = h; + pNew->pVfs = pVfs; pNew->zPath = zFilename; if( memcmp(pVfs->zName,"unix-excl",10)==0 ){ pNew->ctrlFlags = UNIXFILE_EXCL; }else{ pNew->ctrlFlags = 0; @@ -4897,11 +4903,11 @@ ** where NN is a decimal number. The NN naming schemes are ** used by the test_multiplex.c module. */ nDb = sqlite3Strlen30(zPath) - 1; #ifdef SQLITE_ENABLE_8_3_NAMES - while( nDb>0 && !sqlite3Isalnum(zPath[nDb]) ) nDb--; + while( nDb>0 && sqlite3Isalnum(zPath[nDb]) ) nDb--; if( nDb==0 || zPath[nDb]!='-' ) return SQLITE_OK; #else while( zPath[nDb]!='-' ){ assert( nDb>0 ); assert( zPath[nDb]!='\n' ); Index: src/os_win.c ================================================================== --- src/os_win.c +++ src/os_win.c @@ -179,11 +179,11 @@ */ #if !SQLITE_OS_WINCE # define SQLITE_WIN32_HAS_ANSI #endif -#if SQLITE_OS_WINCE || defined(_WIN32_WINNT) +#if SQLITE_OS_WINCE || SQLITE_OS_WINNT # define SQLITE_WIN32_HAS_WIDE #endif #ifndef SYSCALL # define SYSCALL sqlite3_syscall_ptr @@ -2142,10 +2142,14 @@ *(int*)pArg = pFile->bPersistWal; }else{ pFile->bPersistWal = bPersist!=0; } return SQLITE_OK; + } + case SQLITE_FCNTL_VFSNAME: { + *(char**)pArg = sqlite3_mprintf("win32"); + return SQLITE_OK; } case SQLITE_FCNTL_SYNC_OMITTED: { return SQLITE_OK; } case SQLITE_FCNTL_WIN32_AV_RETRY: { Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -2483,11 +2483,11 @@ rc = sqlite3OsFileSize(pPager->fd, ¤tSize); newSize = szPage*(i64)nPage; if( rc==SQLITE_OK && currentSize!=newSize ){ if( currentSize>newSize ){ rc = sqlite3OsTruncate(pPager->fd, newSize); - }else{ + }else if( (currentSize+szPage)<=newSize ){ char *pTmp = pPager->pTmpSpace; memset(pTmp, 0, szPage); testcase( (newSize-szPage) < currentSize ); testcase( (newSize-szPage) == currentSize ); testcase( (newSize-szPage) > currentSize ); @@ -3072,11 +3072,11 @@ int rc = sqlite3OsFileSize(pPager->fd, &n); if( rc!=SQLITE_OK ){ return rc; } } - nPage = (Pgno)(n / pPager->pageSize); + nPage = (Pgno)((n+pPager->pageSize-1) / pPager->pageSize); if( nPage==0 && n>0 ){ nPage = 1; } } @@ -3265,17 +3265,17 @@ ** previously rolled back out of the main journal (and are hence in pDone) ** will be skipped. Out-of-range pages are also skipped. */ if( pSavepoint ){ u32 ii; /* Loop counter */ - i64 offset = pSavepoint->iSubRec*(4+pPager->pageSize); + i64 offset = (i64)pSavepoint->iSubRec*(4+pPager->pageSize); if( pagerUseWal(pPager) ){ rc = sqlite3WalSavepointUndo(pPager->pWal, pSavepoint->aWalData); } for(ii=pSavepoint->iSubRec; rc==SQLITE_OK && iinSubRec; ii++){ - assert( offset==ii*(4+pPager->pageSize) ); + assert( offset==(i64)ii*(4+pPager->pageSize) ); rc = pager_playback_one_page(pPager, &offset, pDone, 0, 1); } assert( rc!=SQLITE_DONE ); } @@ -3502,11 +3502,11 @@ if( !pNew ) rc = SQLITE_NOMEM; } if( rc==SQLITE_OK ){ pager_reset(pPager); - pPager->dbSize = (Pgno)(nByte/pageSize); + pPager->dbSize = (Pgno)((nByte+pageSize-1)/pageSize); pPager->pageSize = pageSize; sqlite3PageFree(pPager->pTmpSpace); pPager->pTmpSpace = pNew; sqlite3PcacheSetPageSize(pPager->pPCache, pageSize); } @@ -4119,11 +4119,11 @@ /* If the sub-journal was opened successfully (or was already open), ** write the journal record into the file. */ if( rc==SQLITE_OK ){ void *pData = pPg->pData; - i64 offset = pPager->nSubRec*(4+pPager->pageSize); + i64 offset = (i64)pPager->nSubRec*(4+pPager->pageSize); char *pData2; CODEC2(pPager, pData, pPg->pgno, 7, return SQLITE_NOMEM, pData2); PAGERTRACE(("STMT-JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno)); rc = write32bits(pPager->sjfd, offset, pPg->pgno); Index: src/shell.c ================================================================== --- src/shell.c +++ src/shell.c @@ -1394,10 +1394,11 @@ ".stats ON|OFF Turn stats on or off\n" ".tables ?TABLE? List names of tables\n" " If TABLE specified, only list tables matching\n" " LIKE pattern TABLE.\n" ".timeout MS Try opening locked tables for MS milliseconds\n" + ".vfsname ?AUX? Print the name of the VFS stack\n" ".width NUM1 NUM2 ... Set column widths for \"column\" mode\n" ; static char zTimerHelp[] = ".timer ON|OFF Turn the CPU timer measurement on or off\n" @@ -2333,13 +2334,25 @@ ){ enableTimer = booleanValue(azArg[1]); }else if( c=='v' && strncmp(azArg[0], "version", n)==0 ){ - printf("SQLite %s %s\n", + printf("SQLite %s %s\n" /*extra-version-info*/, sqlite3_libversion(), sqlite3_sourceid()); }else + + if( c=='v' && strncmp(azArg[0], "vfsname", n)==0 ){ + const char *zDbName = nArg==2 ? azArg[1] : "main"; + char *zVfsName = 0; + if( p->db ){ + sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName); + if( zVfsName ){ + printf("%s\n", zVfsName); + sqlite3_free(zVfsName); + } + } + }else if( c=='w' && strncmp(azArg[0], "width", n)==0 && nArg>1 ){ int j; assert( nArg<=ArraySize(azArg) ); for(j=1; jcolWidth); j++){ @@ -2642,33 +2655,34 @@ /* ** Show available command line options */ static const char zOptions[] = - " -help show this message\n" - " -init filename read/process named file\n" - " -echo print commands before execution\n" - " -[no]header turn headers on or off\n" " -bail stop after hitting an error\n" - " -interactive force interactive I/O\n" " -batch force batch I/O\n" " -column set output mode to 'column'\n" + " -cmd command run \"command\" before reading stdin\n" " -csv set output mode to 'csv'\n" + " -echo print commands before execution\n" + " -init filename read/process named file\n" + " -[no]header turn headers on or off\n" + " -help show this message\n" " -html set output mode to HTML\n" + " -interactive force interactive I/O\n" " -line set output mode to 'line'\n" " -list set output mode to 'list'\n" +#ifdef SQLITE_ENABLE_MULTIPLEX + " -multiplex enable the multiplexor VFS\n" +#endif + " -nullvalue 'text' set text string for NULL values\n" " -separator 'x' set output field separator (|)\n" " -stats print memory stats before each finalize\n" - " -nullvalue 'text' set text string for NULL values\n" " -version show SQLite version\n" " -vfs NAME use NAME as the default VFS\n" #ifdef SQLITE_ENABLE_VFSTRACE " -vfstrace enable tracing of all VFS calls\n" #endif -#ifdef SQLITE_ENABLE_MULTIPLEX - " -multiplex enable the multiplexor VFS\n" -#endif ; static void usage(int showDetail){ fprintf(stderr, "Usage: %s [OPTIONS] FILENAME [SQL]\n" "FILENAME is the name of an SQLite database. A new database is created\n" @@ -2727,23 +2741,26 @@ */ for(i=1; i0x7fff0000 ) szHeap = 0x7fff0000; sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); #endif #ifdef SQLITE_ENABLE_VFSTRACE - }else if( strcmp(argv[i],"-vfstrace")==0 ){ + }else if( strcmp(z,"-vfstrace")==0 ){ extern int vfstrace_register( const char *zTraceName, const char *zOldVfsName, int (*xOut)(const char*,void*), void *pOutArg, @@ -2767,15 +2784,15 @@ int makeDefault ); vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1); #endif #ifdef SQLITE_ENABLE_MULTIPLEX - }else if( strcmp(argv[i],"-multiplex")==0 ){ + }else if( strcmp(z,"-multiplex")==0 ){ extern int sqlite3_multiple_initialize(const char*,int); sqlite3_multiplex_initialize(0, 1); #endif - }else if( strcmp(argv[i],"-vfs")==0 ){ + }else if( strcmp(z,"-vfs")==0 ){ sqlite3_vfs *pVfs = sqlite3_vfs_find(argv[++i]); if( pVfs ){ sqlite3_vfs_register(pVfs, 1); }else{ fprintf(stderr, "no such VFS: \"%s\"\n", argv[i]); @@ -2853,20 +2870,22 @@ data.mode = MODE_Csv; memcpy(data.separator,",",2); }else if( strcmp(z,"-separator")==0 ){ i++; if(i>=argc){ - fprintf(stderr,"%s: Error: missing argument for option: %s\n", Argv0, z); + fprintf(stderr,"%s: Error: missing argument for option: %s\n", + Argv0, z); fprintf(stderr,"Use -help for a list of options.\n"); return 1; } sqlite3_snprintf(sizeof(data.separator), data.separator, "%.*s",(int)sizeof(data.separator)-1,argv[i]); }else if( strcmp(z,"-nullvalue")==0 ){ i++; if(i>=argc){ - fprintf(stderr,"%s: Error: missing argument for option: %s\n", Argv0, z); + fprintf(stderr,"%s: Error: missing argument for option: %s\n", + Argv0, z); fprintf(stderr,"Use -help for a list of options.\n"); return 1; } sqlite3_snprintf(sizeof(data.nullvalue), data.nullvalue, "%.*s",(int)sizeof(data.nullvalue)-1,argv[i]); @@ -2897,12 +2916,30 @@ #endif #ifdef SQLITE_ENABLE_MULTIPLEX }else if( strcmp(z,"-multiplex")==0 ){ i++; #endif - }else if( strcmp(z,"-help")==0 || strcmp(z, "--help")==0 ){ + }else if( strcmp(z,"-help")==0 ){ usage(1); + }else if( strcmp(z,"-cmd")==0 ){ + if( i==argc-1 ) break; + i++; + z = argv[i]; + if( z[0]=='.' ){ + rc = do_meta_command(z, &data); + if( rc && bail_on_error ) return rc; + }else{ + open_db(&data); + rc = shell_exec(data.db, z, shell_callback, &data, &zErrMsg); + if( zErrMsg!=0 ){ + fprintf(stderr,"Error: %s\n", zErrMsg); + if( bail_on_error ) return rc!=0 ? rc : 1; + }else if( rc!=0 ){ + fprintf(stderr,"Error: unable to process SQL \"%s\"\n", z); + if( bail_on_error ) return rc; + } + } }else{ fprintf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); fprintf(stderr,"Use -help for a list of options.\n"); return 1; } @@ -2930,11 +2967,11 @@ if( stdin_is_interactive ){ char *zHome; char *zHistory = 0; int nHistory; printf( - "SQLite version %s %.19s\n" + "SQLite version %s %.19s\n" /*extra-version-info*/ "Enter \".help\" for instructions\n" "Enter SQL statements terminated with a \";\"\n", sqlite3_libversion(), sqlite3_sourceid() ); zHome = find_home_dir(); Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -769,10 +769,21 @@ ** ** ^The [SQLITE_FCNTL_OVERWRITE] opcode is invoked by SQLite after opening ** a write transaction to indicate that, unless it is rolled back for some ** reason, the entire database file will be overwritten by the current ** transaction. This is used by VACUUM operations. +** +** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of +** all [VFSes] in the VFS stack. The names are of all VFS shims and the +** final bottom-level VFS are written into memory obtained from +** [sqlite3_malloc()] and the result is stored in the char* variable +** that the fourth parameter of [sqlite3_file_control()] points to. +** The caller is responsible for freeing the memory when done. As with +** all file-control actions, there is no guarantee that this will actually +** do anything. Callers should initialize the char* variable to a NULL +** pointer in case this file-control is not implemented. This file-control +** is intended for diagnostic use only. */ #define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_GET_LOCKPROXYFILE 2 #define SQLITE_SET_LOCKPROXYFILE 3 #define SQLITE_LAST_ERRNO 4 @@ -781,10 +792,11 @@ #define SQLITE_FCNTL_FILE_POINTER 7 #define SQLITE_FCNTL_SYNC_OMITTED 8 #define SQLITE_FCNTL_WIN32_AV_RETRY 9 #define SQLITE_FCNTL_PERSIST_WAL 10 #define SQLITE_FCNTL_OVERWRITE 11 +#define SQLITE_FCNTL_VFSNAME 12 /* ** CAPI3REF: Mutex Handle ** ** The mutex module within SQLite defines [sqlite3_mutex] to be an Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -5206,10 +5206,43 @@ sqlite3_snprintf(sizeof(z), z, "%d %d", rc, bPersist); Tcl_AppendResult(interp, z, (char*)0); return TCL_OK; } + +/* +** tclcmd: file_control_vfsname DB ?AUXDB? +** +** Return a string that describes the stack of VFSes. +*/ +static int file_control_vfsname( + ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3 *db; + const char *zDbName = "main"; + char *zVfsName = 0; + + if( objc!=2 && objc!=3 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", + Tcl_GetStringFromObj(objv[0], 0), " DB ?AUXDB?", 0); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){ + return TCL_ERROR; + } + if( objc==3 ){ + zDbName = Tcl_GetString(objv[2]); + } + sqlite3_file_control(db, zDbName, SQLITE_FCNTL_VFSNAME,(void*)&zVfsName); + Tcl_AppendResult(interp, zVfsName, (char*)0); + sqlite3_free(zVfsName); + return TCL_OK; +} + /* ** tclcmd: sqlite3_vfs_list ** ** Return a tcl list containing the names of all registered vfs's. @@ -6030,10 +6063,11 @@ { "file_control_lockproxy_test", file_control_lockproxy_test, 0 }, { "file_control_chunksize_test", file_control_chunksize_test, 0 }, { "file_control_sizehint_test", file_control_sizehint_test, 0 }, { "file_control_win32_av_retry", file_control_win32_av_retry, 0 }, { "file_control_persist_wal", file_control_persist_wal, 0 }, + { "file_control_vfsname", file_control_vfsname, 0 }, { "sqlite3_vfs_list", vfs_list, 0 }, { "sqlite3_create_function_v2", test_create_function_v2, 0 }, /* Functions from os.h */ #ifndef SQLITE_OMIT_UTF16 Index: src/test_journal.c ================================================================== --- src/test_journal.c +++ src/test_journal.c @@ -389,11 +389,11 @@ } iTrunk = decodeUint32(&aData[32]); while( rc==SQLITE_OK && iTrunk>0 ){ u32 nLeaf; u32 iLeaf; - sqlite3_int64 iOff = (iTrunk-1)*pMain->nPagesize; + sqlite3_int64 iOff = (i64)(iTrunk-1)*pMain->nPagesize; rc = sqlite3OsRead(p, aData, pMain->nPagesize, iOff); nLeaf = decodeUint32(&aData[4]); for(iLeaf=0; rc==SQLITE_OK && iLeafpWritable, pgno); Index: src/test_multiplex.c ================================================================== --- src/test_multiplex.c +++ src/test_multiplex.c @@ -79,10 +79,13 @@ #define sqlite3_mutex_leave(X) #define sqlite3_mutex_held(X) ((void)(X),1) #define sqlite3_mutex_notheld(X) ((void)(X),1) #endif /* SQLITE_THREADSAFE==0 */ +/* First chunk for rollback journal files */ +#define SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET 400 + /************************ Shim Definitions ******************************/ #ifndef SQLITE_MULTIPLEX_VFS_NAME # define SQLITE_MULTIPLEX_VFS_NAME "multiplex" @@ -94,30 +97,20 @@ */ #ifndef SQLITE_MULTIPLEX_CHUNK_SIZE # define SQLITE_MULTIPLEX_CHUNK_SIZE 2147418112 #endif -/* Default limit on number of chunks. Care should be taken -** so that values for chunks numbers fit in the SQLITE_MULTIPLEX_EXT_FMT -** format specifier. It may be changed by calling -** the xFileControl() interface. +/* This used to be the default limit on number of chunks, but +** it is no longer enforced. There is currently no limit to the +** number of chunks. +** +** May be changed by calling the xFileControl() interface. */ #ifndef SQLITE_MULTIPLEX_MAX_CHUNKS -# define SQLITE_MULTIPLEX_MAX_CHUNKS 32 +# define SQLITE_MULTIPLEX_MAX_CHUNKS 12 #endif -/* If SQLITE_MULTIPLEX_EXT_OVWR is defined, the -** last SQLITE_MULTIPLEX_EXT_SZ characters of the -** filename will be overwritten, otherwise, the -** multiplex extension is simply appended to the filename. -** Ex. (undefined) test.db -> test.db01 -** (defined) test.db -> test.01 -** Chunk 0 does not have a modified extension. -*/ -#define SQLITE_MULTIPLEX_EXT_FMT "%02d" -#define SQLITE_MULTIPLEX_EXT_SZ 2 - /************************ Object Definitions ******************************/ /* Forward declaration of all object types */ typedef struct multiplexGroup multiplexGroup; typedef struct multiplexConn multiplexConn; @@ -138,11 +131,12 @@ int nReal; /* Number of chunks */ char *zName; /* Base filename of this group */ int nName; /* Length of base filename */ int flags; /* Flags used for original opening */ unsigned int szChunk; /* Chunk size used for this group */ - int bEnabled; /* TRUE to use Multiplex VFS for this file */ + unsigned char bEnabled; /* TRUE to use Multiplex VFS for this file */ + unsigned char bTruncate; /* TRUE to enable truncation of databases */ multiplexGroup *pNext, *pPrev; /* Doubly linked list of all group objects */ }; /* ** An instance of the following object represents each open connection @@ -222,72 +216,40 @@ while( *z2 ){ z2++; } return 0x3fffffff & (int)(z2 - z); } /* -** Create a temporary file name in zBuf. zBuf must be big enough to -** hold at pOrigVfs->mxPathname characters. This function departs -** from the traditional temporary name generation in the os_win -** and os_unix VFS in several ways, but is necessary so that -** the file name is known for temporary files (like those used -** during vacuum.) -** -** N.B. This routine assumes your underlying VFS is ok with using -** "/" as a directory seperator. This is the default for UNIXs -** and is allowed (even mixed) for most versions of Windows. -*/ -static int multiplexGetTempname(sqlite3_vfs *pOrigVfs, int nBuf, char *zBuf){ - static char zChars[] = - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789"; - int i,j; - int attempts = 0; - int exists = 0; - int rc = SQLITE_ERROR; - - /* Check that the output buffer is large enough for - ** pVfs->mxPathname characters. - */ - if( pOrigVfs->mxPathname <= nBuf ){ - char *zTmp = sqlite3_malloc(pOrigVfs->mxPathname); - if( zTmp==0 ) return SQLITE_NOMEM; - - /* sqlite3_temp_directory should always be less than - ** pVfs->mxPathname characters. - */ - sqlite3_snprintf(pOrigVfs->mxPathname, - zTmp, - "%s/", - sqlite3_temp_directory ? sqlite3_temp_directory : "."); - rc = pOrigVfs->xFullPathname(pOrigVfs, zTmp, nBuf, zBuf); - sqlite3_free(zTmp); - if( rc ) return rc; - - /* Check that the output buffer is large enough for the temporary file - ** name. - */ - j = multiplexStrlen30(zBuf); - if( (j + 8 + 1 + 3 + 1) <= nBuf ){ - /* Make 3 attempts to generate a unique name. */ - do { - attempts++; - sqlite3_randomness(8, &zBuf[j]); - for(i=0; i<8; i++){ - unsigned char uc = (unsigned char)zBuf[j+i]; - zBuf[j+i] = (char)zChars[uc%(sizeof(zChars)-1)]; - } - memcpy(&zBuf[j+i], ".tmp", 5); - rc = pOrigVfs->xAccess(pOrigVfs, zBuf, SQLITE_ACCESS_EXISTS, &exists); - } while ( (rc==SQLITE_OK) && exists && (attempts<3) ); - if( rc==SQLITE_OK && exists ){ - rc = SQLITE_ERROR; - } - } - } - - return rc; +** Generate the file-name for chunk iChunk of the group with base name +** zBase. The file-name is written to buffer zOut before returning. Buffer +** zOut must be allocated by the caller so that it is at least (nBase+4) +** bytes in size, where nBase is the length of zBase, not including the +** nul-terminator. +*/ +static void multiplexFilename( + const char *zBase, /* Filename for chunk 0 */ + int nBase, /* Size of zBase in bytes (without \0) */ + int flags, /* Flags used to open file */ + int iChunk, /* Chunk to generate filename for */ + char *zOut /* Buffer to write generated name to */ +){ + memcpy(zOut, zBase, nBase+1); + if( iChunk!=0 && iChunk!=SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET ){ + int n = nBase; +#ifdef SQLITE_ENABLE_8_3_NAMES + int i; + for(i=n-1; i>0 && i>=n-4 && zOut[i]!='.'; i--){} + if( i>=n-4 ) n = i+1; + if( flags & SQLITE_OPEN_MAIN_JOURNAL ){ + /* The extensions on overflow files for main databases are 001, 002, + ** 003 and so forth. To avoid name collisions, add 400 to the + ** extensions of journal files so that they are 401, 402, 403, .... + */ + iChunk += SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET; + } +#endif + sqlite3_snprintf(4,&zOut[n],"%03d",iChunk); + } } /* Compute the filename for the iChunk-th chunk */ static int multiplexSubFilename(multiplexGroup *pGroup, int iChunk){ @@ -299,62 +261,111 @@ } memset(&p[pGroup->nReal], 0, sizeof(p[0])*(iChunk+1-pGroup->nReal)); pGroup->aReal = p; pGroup->nReal = iChunk+1; } - if( pGroup->aReal[iChunk].z==0 ){ + if( pGroup->zName && pGroup->aReal[iChunk].z==0 ){ char *z; int n = pGroup->nName; - pGroup->aReal[iChunk].z = z = sqlite3_malloc( n+3 ); + pGroup->aReal[iChunk].z = z = sqlite3_malloc( n+4 ); if( z==0 ){ return SQLITE_NOMEM; } - memcpy(z, pGroup->zName, n+1); - if( iChunk>0 ){ -#ifdef SQLITE_ENABLE_8_3_NAMES - if( n>3 && z[n-3]=='.' ){ - n--; - }else if( n>4 && z[n-4]=='.' ){ - n -= 2; - } -#endif - sqlite3_snprintf(3,&z[n],"%02d",iChunk); - } + multiplexFilename(pGroup->zName, pGroup->nName, pGroup->flags, iChunk, z); } return SQLITE_OK; } /* Translate an sqlite3_file* that is really a multiplexGroup* into ** the sqlite3_file* for the underlying original VFS. +** +** For chunk 0, the pGroup->flags determines whether or not a new file +** is created if it does not already exist. For chunks 1 and higher, the +** file is created only if createFlag is 1. */ static sqlite3_file *multiplexSubOpen( - multiplexGroup *pGroup, - int iChunk, - int *rc, - int *pOutFlags + multiplexGroup *pGroup, /* The multiplexor group */ + int iChunk, /* Which chunk to open. 0==original file */ + int *rc, /* Result code in and out */ + int *pOutFlags, /* Output flags */ + int createFlag /* True to create if iChunk>0 */ ){ sqlite3_file *pSubOpen = 0; sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ + +#ifdef SQLITE_ENABLE_8_3_NAMES + /* If JOURNAL_8_3_OFFSET is set to (say) 400, then any overflow files are + ** part of a database journal are named db.401, db.402, and so on. A + ** database may therefore not grow to larger than 400 chunks. Attempting + ** to open chunk 401 indicates the database is full. */ + if( iChunk>=SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET ){ + sqlite3_log(SQLITE_FULL, "multiplexed chunk overflow: %s", pGroup->zName); + *rc = SQLITE_FULL; + return 0; + } +#endif + *rc = multiplexSubFilename(pGroup, iChunk); if( (*rc)==SQLITE_OK && (pSubOpen = pGroup->aReal[iChunk].p)==0 ){ + int flags, bExists; + flags = pGroup->flags; + if( createFlag ){ + flags |= SQLITE_OPEN_CREATE; + }else if( iChunk==0 ){ + /* Fall through */ + }else if( pGroup->aReal[iChunk].z==0 ){ + return 0; + }else{ + *rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[iChunk].z, + SQLITE_ACCESS_EXISTS, &bExists); + if( *rc || !bExists ){ + if( *rc ){ + sqlite3_log(*rc, "multiplexor.xAccess failure on %s", + pGroup->aReal[iChunk].z); + } + return 0; + } + flags &= ~SQLITE_OPEN_CREATE; + } pSubOpen = sqlite3_malloc( pOrigVfs->szOsFile ); if( pSubOpen==0 ){ - *rc = SQLITE_NOMEM; + *rc = SQLITE_IOERR_NOMEM; return 0; } pGroup->aReal[iChunk].p = pSubOpen; *rc = pOrigVfs->xOpen(pOrigVfs, pGroup->aReal[iChunk].z, pSubOpen, - pGroup->flags, pOutFlags); - if( *rc!=SQLITE_OK ){ + flags, pOutFlags); + if( (*rc)!=SQLITE_OK ){ + sqlite3_log(*rc, "multiplexor.xOpen failure on %s", + pGroup->aReal[iChunk].z); sqlite3_free(pSubOpen); pGroup->aReal[iChunk].p = 0; return 0; } } return pSubOpen; } +/* +** Return the size, in bytes, of chunk number iChunk. If that chunk +** does not exist, then return 0. This function does not distingish between +** non-existant files and zero-length files. +*/ +static sqlite3_int64 multiplexSubSize( + multiplexGroup *pGroup, /* The multiplexor group */ + int iChunk, /* Which chunk to open. 0==original file */ + int *rc /* Result code in and out */ +){ + sqlite3_file *pSub; + sqlite3_int64 sz = 0; + + pSub = multiplexSubOpen(pGroup, iChunk, rc, NULL, 0); + if( pSub==0 ) return 0; + *rc = pSub->pMethods->xFileSize(pSub, &sz); + return sz; +} + /* ** This is the implementation of the multiplex_control() SQL function. */ static void multiplexControlFunc( sqlite3_context *context, @@ -418,11 +429,13 @@ sqlite3_vfs *pOrigVfs ){ sqlite3_file *pSubOpen = pGroup->aReal[iChunk].p; if( pSubOpen ){ pSubOpen->pMethods->xClose(pSubOpen); - if( pOrigVfs ) pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0); + if( pOrigVfs && pGroup->aReal[iChunk].z ){ + pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0); + } sqlite3_free(pGroup->aReal[iChunk].p); } sqlite3_free(pGroup->aReal[iChunk].z); memset(&pGroup->aReal[iChunk], 0, sizeof(pGroup->aReal[iChunk])); } @@ -464,34 +477,21 @@ int sz; char *zToFree = 0; UNUSED_PARAMETER(pVfs); memset(pConn, 0, pVfs->szOsFile); + assert( zName || (flags & SQLITE_OPEN_DELETEONCLOSE) ); /* We need to create a group structure and manage ** access to this group of files. */ multiplexEnter(); pMultiplexOpen = (multiplexConn*)pConn; - /* If the second argument to this function is NULL, generate a - ** temporary file name to use. This will be handled by the - ** original xOpen method. We just need to allocate space for - ** it. - */ - if( !zName ){ - zName = zToFree = sqlite3_malloc( pOrigVfs->mxPathname + 10 ); - if( zName==0 ){ - rc = SQLITE_NOMEM; - }else{ - rc = multiplexGetTempname(pOrigVfs, pOrigVfs->mxPathname, zToFree); - } - } - if( rc==SQLITE_OK ){ /* allocate space for group */ - nName = multiplexStrlen30(zName); + nName = zName ? multiplexStrlen30(zName) : 0; sz = sizeof(multiplexGroup) /* multiplexGroup */ + nName + 1; /* zName */ pGroup = sqlite3_malloc( sz ); if( pGroup==0 ){ rc = SQLITE_NOMEM; @@ -498,66 +498,104 @@ } } if( rc==SQLITE_OK ){ /* assign pointers to extra space allocated */ - char *p = (char *)&pGroup[1]; + memset(pGroup, 0, sz); pMultiplexOpen->pGroup = pGroup; - memset(pGroup, 0, sz); pGroup->bEnabled = -1; + pGroup->bTruncate = (flags & SQLITE_OPEN_MAIN_DB)==0; pGroup->szChunk = SQLITE_MULTIPLEX_CHUNK_SIZE; - if( flags & SQLITE_OPEN_URI ){ - const char *zChunkSize; - zChunkSize = sqlite3_uri_parameter(zName, "chunksize"); - if( zChunkSize ){ - unsigned int n = 0; - int i; - for(i=0; zChunkSize[i]>='0' && zChunkSize[i]<='9'; i++){ - n = n*10 + zChunkSize[i] - '0'; - } - if( n>0 ){ - pGroup->szChunk = (n+0xffff)&~0xffff; - }else{ - /* A zero or negative chunksize disabled the multiplexor */ - pGroup->bEnabled = 0; - } - } - } - pGroup->zName = p; - /* save off base filename, name length, and original open flags */ - memcpy(pGroup->zName, zName, nName+1); - pGroup->nName = nName; + + if( zName ){ + char *p = (char *)&pGroup[1]; + if( flags & SQLITE_OPEN_URI ){ + const char *zChunkSize; + zChunkSize = sqlite3_uri_parameter(zName, "chunksize"); + if( zChunkSize ){ + unsigned int n = 0; + int i; + for(i=0; zChunkSize[i]>='0' && zChunkSize[i]<='9'; i++){ + n = n*10 + zChunkSize[i] - '0'; + } + if( n>0 ){ + pGroup->szChunk = (n+0xffff)&~0xffff; + }else{ + /* A zero or negative chunksize disabled the multiplexor */ + pGroup->bEnabled = 0; + } + } + if( sqlite3_uri_parameter(zName, "truncate") ) pGroup->bTruncate = 1; + } + pGroup->zName = p; + memcpy(pGroup->zName, zName, nName+1); + pGroup->nName = nName; + } + if( pGroup->bEnabled ){ + /* Make sure that the chunksize is not such that the pending byte + ** falls at the end of a chunk. A region of up to 64K following + ** the pending byte is never written, so if the pending byte occurs + ** near the end of a chunk, that chunk will be too small. */ + extern int sqlite3PendingByte; + while( (sqlite3PendingByte % pGroup->szChunk)>=(pGroup->szChunk-65536) ){ + pGroup->szChunk += 65536; + } + } pGroup->flags = flags; rc = multiplexSubFilename(pGroup, 1); if( rc==SQLITE_OK ){ - pSubOpen = multiplexSubOpen(pGroup, 0, &rc, pOutFlags); + pSubOpen = multiplexSubOpen(pGroup, 0, &rc, pOutFlags, 0); + if( pSubOpen==0 && rc==SQLITE_OK ) rc = SQLITE_CANTOPEN; } - if( pSubOpen ){ - int exists, rc2, rc3; + if( rc==SQLITE_OK ){ sqlite3_int64 sz; - rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); - if( rc2==SQLITE_OK ){ - /* If the first overflow file exists and if the size of the main file - ** is different from the chunk size, that means the chunk size is set - ** set incorrectly. So fix it. - ** - ** Or, if the first overflow file does not exist and the main file is - ** larger than the chunk size, that means the chunk size is too small. - ** But we have no way of determining the intended chunk size, so - ** just disable the multiplexor all togethre. - */ - rc3 = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[1].z, - SQLITE_ACCESS_EXISTS, &exists); - if( rc3==SQLITE_OK && exists && sz==(sz&0xffff0000) && sz>0 - && sz!=pGroup->szChunk ){ - pGroup->szChunk = sz; - }else if( rc3==SQLITE_OK && !exists && sz>pGroup->szChunk ){ - pGroup->bEnabled = 0; - } - } - + rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); + if( rc==SQLITE_OK && zName ){ + int bExists; + if( sz==0 ){ + if( flags & SQLITE_OPEN_MAIN_JOURNAL ){ + /* If opening a main journal file and the first chunk is zero + ** bytes in size, delete any subsequent chunks from the + ** file-system. */ + int iChunk = 1; + do { + rc = pOrigVfs->xAccess(pOrigVfs, + pGroup->aReal[iChunk].z, SQLITE_ACCESS_EXISTS, &bExists + ); + if( rc==SQLITE_OK && bExists ){ + rc = pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0); + if( rc==SQLITE_OK ){ + rc = multiplexSubFilename(pGroup, ++iChunk); + } + } + }while( rc==SQLITE_OK && bExists ); + } + }else{ + /* If the first overflow file exists and if the size of the main file + ** is different from the chunk size, that means the chunk size is set + ** set incorrectly. So fix it. + ** + ** Or, if the first overflow file does not exist and the main file is + ** larger than the chunk size, that means the chunk size is too small. + ** But we have no way of determining the intended chunk size, so + ** just disable the multiplexor all togethre. + */ + rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[1].z, + SQLITE_ACCESS_EXISTS, &bExists); + bExists = multiplexSubSize(pGroup, 1, &rc)>0; + if( rc==SQLITE_OK && bExists && sz==(sz&0xffff0000) && sz>0 + && sz!=pGroup->szChunk ){ + pGroup->szChunk = sz; + }else if( rc==SQLITE_OK && !bExists && sz>pGroup->szChunk ){ + pGroup->bEnabled = 0; + } + } + } + } + + if( rc==SQLITE_OK ){ if( pSubOpen->pMethods->iVersion==1 ){ pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1; }else{ pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV2; } @@ -582,12 +620,37 @@ static int multiplexDelete( sqlite3_vfs *pVfs, /* The multiplex VFS */ const char *zName, /* Name of file to delete */ int syncDir ){ + int rc; sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ - return pOrigVfs->xDelete(pOrigVfs, zName, syncDir); + rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir); + if( rc==SQLITE_OK ){ + /* If the main chunk was deleted successfully, also delete any subsequent + ** chunks - starting with the last (highest numbered). + */ + int nName = strlen(zName); + char *z; + z = sqlite3_malloc(nName + 4); + if( z==0 ){ + rc = SQLITE_IOERR_NOMEM; + }else{ + int iChunk = 0; + int bExists; + do{ + multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, ++iChunk, z); + rc = pOrigVfs->xAccess(pOrigVfs, z, SQLITE_ACCESS_EXISTS, &bExists); + }while( rc==SQLITE_OK && bExists ); + while( rc==SQLITE_OK && iChunk>1 ){ + multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, --iChunk, z); + rc = pOrigVfs->xDelete(pOrigVfs, z, syncDir); + } + } + sqlite3_free(z); + } + return rc; } static int multiplexAccess(sqlite3_vfs *a, const char *b, int c, int *d){ return gMultiplex.pOrigVfs->xAccess(gMultiplex.pOrigVfs, b, c, d); } @@ -660,20 +723,20 @@ multiplexConn *p = (multiplexConn*)pConn; multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; multiplexEnter(); if( !pGroup->bEnabled ){ - sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0); if( pSubOpen==0 ){ rc = SQLITE_IOERR_READ; }else{ rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst); } }else{ while( iAmt > 0 ){ int i = (int)(iOfst / pGroup->szChunk); - sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1); if( pSubOpen ){ int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) - pGroup->szChunk; if( extra<0 ) extra = 0; iAmt -= extra; rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, @@ -705,34 +768,30 @@ multiplexConn *p = (multiplexConn*)pConn; multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; multiplexEnter(); if( !pGroup->bEnabled ){ - sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0); if( pSubOpen==0 ){ rc = SQLITE_IOERR_WRITE; }else{ rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst); } }else{ - while( iAmt > 0 ){ + while( rc==SQLITE_OK && iAmt>0 ){ int i = (int)(iOfst / pGroup->szChunk); - sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1); if( pSubOpen ){ int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) - pGroup->szChunk; if( extra<0 ) extra = 0; iAmt -= extra; rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst % pGroup->szChunk); - if( rc!=SQLITE_OK ) break; pBuf = (char *)pBuf + iAmt; iOfst += iAmt; iAmt = extra; - }else{ - rc = SQLITE_IOERR_WRITE; - break; } } } multiplexLeave(); return rc; @@ -746,32 +805,39 @@ multiplexConn *p = (multiplexConn*)pConn; multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; multiplexEnter(); if( !pGroup->bEnabled ){ - sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0); if( pSubOpen==0 ){ rc = SQLITE_IOERR_TRUNCATE; }else{ rc = pSubOpen->pMethods->xTruncate(pSubOpen, size); } }else{ - int rc2; int i; + int iBaseGroup = (int)(size / pGroup->szChunk); sqlite3_file *pSubOpen; sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ /* delete the chunks above the truncate limit */ - for(i=(int)(size / pGroup->szChunk)+1; inReal; i++){ - multiplexSubClose(pGroup, i, pOrigVfs); - } - pSubOpen = multiplexSubOpen(pGroup, (int)(size/pGroup->szChunk), &rc2,0); - if( pSubOpen ){ - rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->szChunk); - if( rc2!=SQLITE_OK ) rc = rc2; - }else{ - rc = SQLITE_IOERR_TRUNCATE; - } + for(i = pGroup->nReal-1; i>iBaseGroup && rc==SQLITE_OK; i--){ + if( pGroup->bTruncate ){ + multiplexSubClose(pGroup, i, pOrigVfs); + }else{ + pSubOpen = multiplexSubOpen(pGroup, i, &rc, 0, 0); + if( pSubOpen ){ + rc = pSubOpen->pMethods->xTruncate(pSubOpen, 0); + } + } + } + if( rc==SQLITE_OK ){ + pSubOpen = multiplexSubOpen(pGroup, iBaseGroup, &rc, 0, 0); + if( pSubOpen ){ + rc = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->szChunk); + } + } + if( rc ) rc = SQLITE_IOERR_TRUNCATE; } multiplexLeave(); return rc; } @@ -799,51 +865,25 @@ */ static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ multiplexConn *p = (multiplexConn*)pConn; multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; - int rc2; int i; multiplexEnter(); if( !pGroup->bEnabled ){ - sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0); if( pSubOpen==0 ){ rc = SQLITE_IOERR_FSTAT; }else{ rc = pSubOpen->pMethods->xFileSize(pSubOpen, pSize); } }else{ - sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; *pSize = 0; - for(i=0; 1; i++){ - sqlite3_file *pSubOpen = 0; - int exists = 0; - rc = multiplexSubFilename(pGroup, i); - if( rc ) break; - rc2 = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[i].z, - SQLITE_ACCESS_EXISTS, &exists); - if( rc2==SQLITE_OK && exists){ - /* if it exists, open it */ - pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL); - }else{ - /* stop at first "gap" */ - break; - } - if( pSubOpen ){ - sqlite3_int64 sz; - rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); - if( rc2!=SQLITE_OK ){ - rc = rc2; - }else{ - if( sz>pGroup->szChunk ){ - rc = SQLITE_IOERR_FSTAT; - } - *pSize += sz; - } - }else{ - break; - } + for(i=0; rc==SQLITE_OK; i++){ + sqlite3_int64 sz = multiplexSubSize(pGroup, i, &rc); + if( sz==0 ) break; + *pSize = i*(sqlite3_int64)pGroup->szChunk + sz; } } multiplexLeave(); return rc; } @@ -851,11 +891,11 @@ /* Pass xLock requests through to the original VFS unchanged. */ static int multiplexLock(sqlite3_file *pConn, int lock){ multiplexConn *p = (multiplexConn*)pConn; int rc; - sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0); if( pSubOpen ){ return pSubOpen->pMethods->xLock(pSubOpen, lock); } return SQLITE_BUSY; } @@ -863,11 +903,11 @@ /* Pass xUnlock requests through to the original VFS unchanged. */ static int multiplexUnlock(sqlite3_file *pConn, int lock){ multiplexConn *p = (multiplexConn*)pConn; int rc; - sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0); if( pSubOpen ){ return pSubOpen->pMethods->xUnlock(pSubOpen, lock); } return SQLITE_IOERR_UNLOCK; } @@ -875,11 +915,11 @@ /* Pass xCheckReservedLock requests through to the original VFS unchanged. */ static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){ multiplexConn *p = (multiplexConn*)pConn; int rc; - sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0); if( pSubOpen ){ return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut); } return SQLITE_IOERR_CHECKRESERVEDLOCK; } @@ -923,13 +963,16 @@ case SQLITE_FCNTL_CHUNK_SIZE: /* no-op these */ rc = SQLITE_OK; break; default: - pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL); + pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0); if( pSubOpen ){ rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); + if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){ + *(char**)pArg = sqlite3_mprintf("multiplex/%z", *(char**)pArg); + } } break; } return rc; } @@ -937,12 +980,12 @@ /* Pass xSectorSize requests through to the original VFS unchanged. */ static int multiplexSectorSize(sqlite3_file *pConn){ multiplexConn *p = (multiplexConn*)pConn; int rc; - sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL); - if( pSubOpen ){ + sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0); + if( pSubOpen && pSubOpen->pMethods->xSectorSize ){ return pSubOpen->pMethods->xSectorSize(pSubOpen); } return DEFAULT_SECTOR_SIZE; } @@ -949,11 +992,11 @@ /* Pass xDeviceCharacteristics requests through to the original VFS unchanged. */ static int multiplexDeviceCharacteristics(sqlite3_file *pConn){ multiplexConn *p = (multiplexConn*)pConn; int rc; - sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0); if( pSubOpen ){ return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen); } return 0; } @@ -967,11 +1010,11 @@ int bExtend, /* True to extend file if necessary */ void volatile **pp /* OUT: Mapped memory */ ){ multiplexConn *p = (multiplexConn*)pConn; int rc; - sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0); if( pSubOpen ){ return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend,pp); } return SQLITE_IOERR; } @@ -984,11 +1027,11 @@ int n, /* Number of locks to acquire or release */ int flags /* What to do with the lock */ ){ multiplexConn *p = (multiplexConn*)pConn; int rc; - sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0); if( pSubOpen ){ return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags); } return SQLITE_BUSY; } @@ -996,11 +1039,11 @@ /* Pass xShmBarrier requests through to the original VFS unchanged. */ static void multiplexShmBarrier(sqlite3_file *pConn){ multiplexConn *p = (multiplexConn*)pConn; int rc; - sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0); if( pSubOpen ){ pSubOpen->pMethods->xShmBarrier(pSubOpen); } } @@ -1007,11 +1050,11 @@ /* Pass xShmUnmap requests through to the original VFS unchanged. */ static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){ multiplexConn *p = (multiplexConn*)pConn; int rc; - sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL); + sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0); if( pSubOpen ){ return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag); } return SQLITE_OK; } @@ -1189,13 +1232,17 @@ pResult = Tcl_NewObj(); multiplexEnter(); for(pGroup=gMultiplex.pGroups; pGroup; pGroup=pGroup->pNext){ pGroupTerm = Tcl_NewObj(); - pGroup->zName[pGroup->nName] = '\0'; - Tcl_ListObjAppendElement(interp, pGroupTerm, + if( pGroup->zName ){ + pGroup->zName[pGroup->nName] = '\0'; + Tcl_ListObjAppendElement(interp, pGroupTerm, Tcl_NewStringObj(pGroup->zName, -1)); + }else{ + Tcl_ListObjAppendElement(interp, pGroupTerm, Tcl_NewObj()); + } Tcl_ListObjAppendElement(interp, pGroupTerm, Tcl_NewIntObj(pGroup->nName)); Tcl_ListObjAppendElement(interp, pGroupTerm, Tcl_NewIntObj(pGroup->flags)); Index: src/test_osinst.c ================================================================== --- src/test_osinst.c +++ src/test_osinst.c @@ -387,11 +387,15 @@ /* ** File control method. For custom operations on an vfslog-file. */ static int vfslogFileControl(sqlite3_file *pFile, int op, void *pArg){ VfslogFile *p = (VfslogFile *)pFile; - return p->pReal->pMethods->xFileControl(p->pReal, op, pArg); + int rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg); + if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){ + *(char**)pArg = sqlite3_mprintf("vfslog/%z", *(char**)pArg); + } + return rc; } /* ** Return the sector-size in bytes for an vfslog-file. */ Index: src/test_quota.c ================================================================== --- src/test_quota.c +++ src/test_quota.c @@ -587,11 +587,15 @@ /* Pass xFileControl requests through to the original VFS unchanged. */ static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){ sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); + int rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); + if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){ + *(char**)pArg = sqlite3_mprintf("quota/%z", *(char**)pArg); + } + return rc; } /* Pass xSectorSize requests through to the original VFS unchanged. */ static int quotaSectorSize(sqlite3_file *pConn){ Index: src/test_stat.c ================================================================== --- src/test_stat.c +++ src/test_stat.c @@ -367,11 +367,11 @@ sqlite3_file *fd; sqlite3_int64 x[2]; /* The default page size and offset */ pCsr->szPage = sqlite3BtreeGetPageSize(pBt); - pCsr->iOffset = pCsr->szPage * (pCsr->iPageno - 1); + pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1); /* If connected to a ZIPVFS backend, override the page size and ** offset with actual values obtained from ZIPVFS. */ fd = sqlite3PagerFile(pPager); Index: src/test_vfstrace.c ================================================================== --- src/test_vfstrace.c +++ src/test_vfstrace.c @@ -469,10 +469,14 @@ zOp = zBuf; break; } case SQLITE_FCNTL_FILE_POINTER: zOp = "FILE_POINTER"; break; case SQLITE_FCNTL_SYNC_OMITTED: zOp = "SYNC_OMITTED"; break; + case SQLITE_FCNTL_WIN32_AV_RETRY: zOp = "WIN32_AV_RETRY"; break; + case SQLITE_FCNTL_PERSIST_WAL: zOp = "PERSIST_WAL"; break; + case SQLITE_FCNTL_OVERWRITE: zOp = "OVERWRITE"; break; + case SQLITE_FCNTL_VFSNAME: zOp = "VFSNAME"; break; case 0xca093fa0: zOp = "DB_UNCHANGED"; break; default: { sqlite3_snprintf(sizeof zBuf, zBuf, "%d", op); zOp = zBuf; break; @@ -480,10 +484,14 @@ } vfstrace_printf(pInfo, "%s.xFileControl(%s,%s)", pInfo->zVfsName, p->zFName, zOp); rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg); vfstrace_print_errcode(pInfo, " -> %s\n", rc); + if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){ + *(char**)pArg = sqlite3_mprintf("vfstrace.%s/%z", + pInfo->zVfsName, *(char**)pArg); + } return rc; } /* ** Return the sector-size in bytes for an vfstrace-file. Index: src/util.c ================================================================== --- src/util.c +++ src/util.c @@ -1167,10 +1167,11 @@ ** Examples: ** ** test.db-journal => test.nal ** test.db-wal => test.wal ** test.db-shm => test.shm +** test.db-mj7f3319fa => test.9fa */ void sqlite3FileSuffix3(const char *zBaseFilename, char *z){ #if SQLITE_ENABLE_8_3_NAMES<2 const char *zOk; zOk = sqlite3_uri_parameter(zBaseFilename, "8_3_names"); Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -1822,20 +1822,30 @@ char *zMaster = 0; /* File-name for the master journal */ char const *zMainFile = sqlite3BtreeGetFilename(db->aDb[0].pBt); sqlite3_file *pMaster = 0; i64 offset = 0; int res; + int retryCount = 0; + int nMainFile; /* Select a master journal file name */ + nMainFile = sqlite3Strlen30(zMainFile); + zMaster = sqlite3MPrintf(db, "%s-mjXXXXXX9XX", zMainFile); + if( zMaster==0 ) return SQLITE_NOMEM; do { u32 iRandom; - sqlite3DbFree(db, zMaster); + if( retryCount++>100 ){ + sqlite3_log(SQLITE_FULL, "cannot find unique master-journal"); + sqlite3OsDelete(pVfs, zMaster, 0); + break; + } sqlite3_randomness(sizeof(iRandom), &iRandom); - zMaster = sqlite3MPrintf(db, "%s-mj%08X", zMainFile, iRandom&0x7fffffff); - if( !zMaster ){ - return SQLITE_NOMEM; - } + sqlite3_snprintf(13, &zMaster[nMainFile], "-mj%06X9%02X", + (iRandom>>8)&0xffffff, iRandom&0xff); + /* The antipenultimate character of the master journal name must + ** be "9" to avoid name collisions when using 8+3 filenames. */ + assert( zMaster[sqlite3Strlen30(zMaster)-3]=='9' ); sqlite3FileSuffix3(zMainFile, zMaster); rc = sqlite3OsAccess(pVfs, zMaster, SQLITE_ACCESS_EXISTS, &res); }while( rc==SQLITE_OK && res ); if( rc==SQLITE_OK ){ /* Open the master journal. */ ADDED test/bigfile2.test Index: test/bigfile2.test ================================================================== --- /dev/null +++ test/bigfile2.test @@ -0,0 +1,59 @@ +# 2011 December 20 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this script testing the ability of SQLite to handle database +# files larger than 4GB. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix bigfile2 + +# Create a small database. +# +do_execsql_test 1.1 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); +} + +# Pad the file out to 4GB in size. Then clear the file-size field in the +# db header. This will cause SQLite to assume that the first 4GB of pages +# are actually in use and new pages will be appended to the file. +# +db close +if {[catch {fake_big_file 4096 [pwd]/test.db} msg]} { + puts "**** Unable to create a file larger than 4096 MB. *****" + finish_test + return +} +hexio_write test.db 28 00000000 + +do_test 1.2 { + file size test.db +} [expr 14 + 4096 * (1<<20)] + +# Now insert a large row. The overflow pages will be located past the 4GB +# boundary. Then, after opening and closing the database, test that the row +# can be read back in. +# +set str [string repeat k 30000] +do_test 1.3 { + sqlite3 db test.db + execsql { INSERT INTO t1 VALUES(3, $str) } + db close + sqlite3 db test.db + db one { SELECT b FROM t1 WHERE a = 3 } +} $str + +db close +file delete test.db + +finish_test Index: test/fts3auto.test ================================================================== --- test/fts3auto.test +++ test/fts3auto.test @@ -571,14 +571,14 @@ # set chunkconfig [fts3_configure_incr_load 1 1] foreach {tn create pending} { 1 "fts4(a, b)" 1 2 "fts4(a, b, order=ASC, prefix=1)" 1 - 3 "fts4(a, b, order=ASC, prefix=1,3)" 0 - 4 "fts4(a, b, order=DESC, prefix=2,4)" 0 - 5 "fts4(a, b, order=DESC, prefix=1)" 0 - 6 "fts4(a, b, order=ASC, prefix=1,3)" 0 + 3 "fts4(a, b, order=ASC, prefix=\"1,3\")" 0 + 4 "fts4(a, b, order=DESC, prefix=\"2,4\")" 0 + 5 "fts4(a, b, order=DESC, prefix=\"1\")" 0 + 6 "fts4(a, b, order=ASC, prefix=\"1,3\")" 0 } { execsql [subst { DROP TABLE IF EXISTS t1; CREATE VIRTUAL TABLE t1 USING $create; ADDED test/fts3prefix2.test Index: test/fts3prefix2.test ================================================================== --- /dev/null +++ test/fts3prefix2.test @@ -0,0 +1,62 @@ +# 2012 January 25 +# +# 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 FTS3 module. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix fts3prefix2 + +ifcapable !fts3 { + finish_test + return +} + +do_execsql_test 1.0 { PRAGMA page_size = 512 } +do_execsql_test 1.1 { + CREATE VIRTUAL TABLE t1 USING fts4(x, prefix="2,3"); + + BEGIN; + INSERT INTO t1 VALUES('T TX T TX T TX T TX T TX'); + INSERT INTO t1 SELECT * FROM t1; -- 2 + INSERT INTO t1 SELECT * FROM t1; -- 4 + INSERT INTO t1 SELECT * FROM t1; -- 8 + INSERT INTO t1 SELECT * FROM t1; -- 16 + INSERT INTO t1 SELECT * FROM t1; -- 32 + INSERT INTO t1 SELECT * FROM t1; -- 64 + INSERT INTO t1 SELECT * FROM t1; -- 128 + INSERT INTO t1 SELECT * FROM t1; -- 256 + INSERT INTO t1 SELECT * FROM t1; -- 512 + INSERT INTO t1 SELECT * FROM t1; -- 1024 + INSERT INTO t1 SELECT * FROM t1; -- 2048 + COMMIT; +} + +do_execsql_test 1.2 { + INSERT INTO t1 SELECT * FROM t1 LIMIT 10; + INSERT INTO t1 SELECT * FROM t1 LIMIT 10; + INSERT INTO t1 SELECT * FROM t1 LIMIT 10; + DELETE FROM t1 WHERE docid > 5; +} + +do_execsql_test 1.3 { + SELECT * FROM t1 WHERE t1 MATCH 'T*'; +} { + {T TX T TX T TX T TX T TX} + {T TX T TX T TX T TX T TX} + {T TX T TX T TX T TX T TX} + {T TX T TX T TX T TX T TX} + {T TX T TX T TX T TX T TX} +} + +finish_test + Index: test/multiplex.test ================================================================== --- test/multiplex.test +++ test/multiplex.test @@ -11,10 +11,20 @@ # set testdir [file dirname $argv0] source $testdir/tester.tcl source $testdir/malloc_common.tcl + +# The tests in this file assume that SQLite is compiled without +# ENABLE_8_3_NAMES. +# +ifcapable 8_3_names { + puts -nonewline "SQLite compiled with SQLITE_ENABLE_8_3_NAMES. " + puts "Skipping tests multiplex-*." + finish_test + return +} set g_chunk_size [ expr ($::SQLITE_MAX_PAGE_SIZE*16384) ] set g_max_chunks 32 # This handles appending the chunk number @@ -22,11 +32,11 @@ # SQLITE_MULTIPLEX_EXT_OVWR is defined, then # it overwrites the last 2 bytes of the # file name with the chunk number. proc multiplex_name {name chunk} { if {$chunk==0} { return $name } - set num [format "%02d" $chunk] + set num [format "%03d" $chunk] ifcapable {multiplex_ext_overwrite} { set name [string range $name 0 [expr [string length $name]-2-1]] } return $name$num } @@ -144,10 +154,13 @@ sqlite3_multiplex_initialize "" 1 multiplex_set db main 32768 16 forcedelete test.x +foreach f [glob -nocomplain {test.x*[0-9][0-9][0-9]}] { + forcedelete $f +} do_test multiplex-2.1.2 { sqlite3 db test.x execsql { PRAGMA page_size=1024; PRAGMA auto_vacuum=OFF; @@ -180,15 +193,20 @@ } {SQLITE_MISUSE} do_test multiplex-2.4.2 { execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } } {} do_test multiplex-2.4.4 { file size [multiplex_name test.x 0] } {7168} -do_test multiplex-2.4.99 { +do_test multiplex-2.4.5 { + db close + sqlite3 db test.x + db eval vacuum db close + glob test.x* +} {test.x} +do_test multiplex-2.4.99 { sqlite3_multiplex_shutdown } {SQLITE_OK} - do_test multiplex-2.5.1 { multiplex_delete test.x sqlite3_multiplex_initialize "" 1 sqlite3 db test.x ADDED test/multiplex2.test Index: test/multiplex2.test ================================================================== --- /dev/null +++ test/multiplex2.test @@ -0,0 +1,119 @@ +# 2010 October 29 +# +# 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 +source $testdir/malloc_common.tcl +source $testdir/lock_common.tcl +set testprefix multiplex2 +db close + +do_multiclient_test tn { + foreach f [glob -nocomplain test.*] { forcedelete $f } + + code1 { catch { sqlite3_multiplex_initialize "" 0 } } + code2 { catch { sqlite3_multiplex_initialize "" 0 } } + + code1 { db close } + code2 { db2 close } + + code1 { sqlite3 db test.db -vfs multiplex } + code2 { sqlite3 db2 test.db -vfs multiplex } + + code1 { sqlite3_multiplex_control db main chunk_size [expr 1024*1024] } + code2 { sqlite3_multiplex_control db2 main chunk_size [expr 1024*1024] } + + sql1 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(randomblob(10), randomblob(4000)); -- 1 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 2 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 4 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 8 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 16 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 32 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 64 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 128 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 256 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 512 + SELECT count(*) FROM t1; + } + + do_test 1.$tn.1 { sql1 { SELECT count(*) FROM t1 } } 512 + do_test 1.$tn.2 { sql2 { SELECT count(*) FROM t1 } } 512 + sql2 { DELETE FROM t1 ; VACUUM } + do_test 1.$tn.3 { sql1 { SELECT count(*) FROM t1 } } 0 + + sql1 { + INSERT INTO t1 VALUES(randomblob(10), randomblob(4000)); -- 1 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 2 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 4 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 8 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 16 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 32 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 64 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 128 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 256 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 512 + SELECT count(*) FROM t1; + } + + do_test 1.$tn.4 { sql2 { SELECT count(*) FROM t1 } } 512 +} + +catch {db close} +foreach f [glob -nocomplain test.*] { forcedelete $f } + +ifcapable 8_3_names { + sqlite3 db test.db -vfs multiplex + sqlite3_multiplex_control db main chunk_size [expr 256*1024] + + # Insert 512 * 256K (128MB) of data. If each row is around 4K, this means + # we need 32768 rows. + do_catchsql_test 2.1 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(randomblob(10), randomblob(4000)); -- 1 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 2 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 4 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 8 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 16 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 32 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 64 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 128 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 256 + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 512 + + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 1K + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 2K + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 4K + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 8K + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 16K + INSERT INTO t1 SELECT randomblob(10), randomblob(4000) FROM t1; -- 32K + + } {1 {database or disk is full}} + + do_execsql_test 2.2 { + UPDATE t1 SET a=randomblob(9), b=randomblob(3900); + PRAGMA integrity_check; + } ok + + db close + sqlite3 db test.db -vfs multiplex + sqlite3_multiplex_control db main chunk_size [expr 256*1024] + + do_execsql_test 2.3 { + PRAGMA integrity_check; + } ok +} + +catch { db close } +catch { sqlite3_multiplex_shutdown } +finish_test ADDED test/multiplex3.test Index: test/multiplex3.test ================================================================== --- /dev/null +++ test/multiplex3.test @@ -0,0 +1,133 @@ + +# 2011 December 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 contains tests for error (IO, OOM etc.) handling when using +# the multiplexor extension with 8.3 filenames. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set ::testprefix multiplex3 + +ifcapable !8_3_names { + puts -nonewline "SQLite compiled without SQLITE_ENABLE_8_3_NAMES. " + puts "Skipping tests zipvfsD-*." + finish_test + return +} + +db close +sqlite3_shutdown +sqlite3_config_uri 1 +autoinstall_test_functions + +sqlite3_multiplex_initialize "" 1 + +proc destroy_vfs_stack {} { + generic_unregister stack + sqlite3_multiplex_shutdown +} + +proc multiplex_delete_db {} { + forcedelete test.db + for {set i 1} {$i <= 1000} {incr i} { + forcedelete test.[format %03d $i] + } +} + +# Procs to save and restore the current muliplexed database. +# +proc multiplex_save_db {} { + foreach f [glob -nocomplain sv_test.*] { forcedelete $f } + foreach f [glob -nocomplain test.*] { forcecopy $f "sv_$f" } +} +proc multiplex_restore_db {} { + foreach f [glob -nocomplain test.*] {forcedelete $f} + foreach f [glob -nocomplain sv_test.*] {forcecopy $f [string range $f 3 end]} } + +proc setup_and_save_db {} { + multiplex_delete_db + sqlite3 db file:test.db?8_3_names=1 + sqlite3_multiplex_control db main chunk_size [expr 256*1024] + execsql { + CREATE TABLE t1(a PRIMARY KEY, b); + INSERT INTO t1 VALUES(randomblob(15), randomblob(2000)); + INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1; -- 2 + INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1; -- 4 + INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1; -- 8 + INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1; -- 16 + INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1; -- 32 + INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1; -- 64 + INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1; -- 128 + INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1; -- 256 + INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1; -- 512 + } + set ::cksum1 [execsql {SELECT md5sum(a, b) FROM t1 ORDER BY a}] + db close + multiplex_save_db +} + +do_test 1.0 { setup_and_save_db } {} +do_faultsim_test 1 -prep { + multiplex_restore_db + sqlite3 db file:test.db?8_3_names=1 + sqlite3_multiplex_control db main chunk_size [expr 256*1024] +} -body { + execsql { + UPDATE t1 SET a=randomblob(12), b=randomblob(1500) WHERE (rowid%32)=0 + } +} -test { + faultsim_test_result {0 {}} + if {$testrc!=0} { + set cksum2 [execsql {SELECT md5sum(a, b) FROM t1 ORDER BY a}] + if {$cksum2 != $::cksum1} { error "data mismatch" } + } +} + +#------------------------------------------------------------------------- +# The following tests verify that hot-journal rollback works. As follows: +# +# 1. Create a large database. +# 2. Set the pager cache to be very small. +# 3. Open a transaction. +# 4. Run the following 100 times: +# a. Update a row. +# b. Copy all files on disk to a new db location, including the journal. +# c. Verify that the new db can be opened and that the content matches +# the database created in step 1 (proving the journal was rolled +# back). + +do_test 2.0 { + setup_and_save_db + multiplex_restore_db + sqlite3 db file:test.db?8_3_names=1 + execsql { PRAGMA cache_size = 10 } + execsql { BEGIN } +} {} + +for {set iTest 1} {$iTest<=100} {incr iTest} { + do_test 2.$iTest { + execsql { + UPDATE t1 SET a=randomblob(12), b=randomblob(1400) WHERE rowid=5*$iTest + } + foreach f [glob -nocomplain test.*] {forcecopy $f "xx_$f"} + sqlite3 db2 file:xx_test.db?8_3_names=1 + execsql {SELECT md5sum(a, b) FROM t1 ORDER BY a} db2 + } $::cksum1 + + db2 close +} + +catch { db close } +sqlite3_multiplex_shutdown +finish_test Index: test/quota.test ================================================================== --- test/quota.test +++ test/quota.test @@ -12,10 +12,12 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl source $testdir/malloc_common.tcl +unset -nocomplain defaultVfs +set defaultVfs [file_control_vfsname db] db close do_test quota-1.1 { sqlite3_quota_initialize nosuchvfs 1 } {SQLITE_ERROR} do_test quota-1.2 { sqlite3_quota_initialize "" 1 } {SQLITE_OK} do_test quota-1.3 { sqlite3_quota_initialize "" 1 } {SQLITE_MISUSE} @@ -71,10 +73,13 @@ INSERT INTO t1 VALUES(1, randomblob(1100)); INSERT INTO t1 VALUES(2, randomblob(1100)); } set ::quota } {} +do_test quota-2.1.2.1 { + file_control_vfsname db +} quota/$defaultVfs do_test quota-2.1.3 { file size test.db } {4096} do_test quota-2.1.4 { catchsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } } {1 {database or disk is full}} do_test quota-2.1.5 { set ::quota } {4096 5120}