Index: ext/fts3/fts3.c ================================================================== --- ext/fts3/fts3.c +++ ext/fts3/fts3.c @@ -3659,11 +3659,11 @@ sqlite3Fts3PendingTermsClear((Fts3Table *)pVtab); return SQLITE_OK; } static const sqlite3_module fts3Module = { - /* iVersion */ 1, + /* iVersion */ 2, /* xCreate */ fts3CreateMethod, /* xConnect */ fts3ConnectMethod, /* xBestIndex */ fts3BestIndexMethod, /* xDisconnect */ fts3DisconnectMethod, /* xDestroy */ fts3DestroyMethod, Index: src/attach.c ================================================================== --- src/attach.c +++ src/attach.c @@ -68,12 +68,18 @@ int i; int rc = 0; sqlite3 *db = sqlite3_context_db_handle(context); const char *zName; const char *zFile; + char *zPath = 0; + char *zErr = 0; + unsigned int flags; Db *aNew; char *zErrDyn = 0; + sqlite3_vfs *pVfs; + const char *zVfs = db->pVfs->zName; /* Name of default (main) VFS */ + int btflags = 0; UNUSED_PARAMETER(NotUsed); zFile = (const char *)sqlite3_value_text(argv[0]); zName = (const char *)sqlite3_value_text(argv[1]); @@ -122,12 +128,22 @@ /* Open the database file. If the btree is successfully opened, use ** it to obtain the database schema. At this point the schema may ** or may not be initialised. */ - rc = sqlite3BtreeOpen(zFile, db, &aNew->pBt, 0, - db->openFlags | SQLITE_OPEN_MAIN_DB); + flags = db->openFlags; + rc = sqlite3ParseUri(zVfs, zFile, &flags, &btflags, &pVfs, &zPath, &zErr); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_NOMEM ) db->mallocFailed = 1; + sqlite3_result_error(context, zErr, -1); + sqlite3_free(zErr); + return; + } + assert( pVfs ); + flags |= SQLITE_OPEN_MAIN_DB; + rc = sqlite3BtreeOpen(pVfs, zPath, db, &aNew->pBt, btflags, flags); + sqlite3_free( zPath ); db->nDb++; if( rc==SQLITE_CONSTRAINT ){ rc = SQLITE_ERROR; zErrDyn = sqlite3MPrintf(db, "database is already attached"); }else if( rc==SQLITE_OK ){ Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -1686,17 +1686,17 @@ ** SQLITE_CONSTRAINT error. We cannot allow two or more BtShared ** objects in the same database connection since doing so will lead ** to problems with locking. */ int sqlite3BtreeOpen( + sqlite3_vfs *pVfs, /* VFS to use for this b-tree */ const char *zFilename, /* Name of the file containing the BTree database */ sqlite3 *db, /* Associated database handle */ Btree **ppBtree, /* Pointer to new Btree object written here */ int flags, /* Options */ int vfsFlags /* Flags passed through to sqlite3_vfs.xOpen() */ ){ - sqlite3_vfs *pVfs; /* The VFS to use for this btree */ BtShared *pBt = 0; /* Shared part of btree structure */ Btree *p; /* Handle to return */ sqlite3_mutex *mutexOpen = 0; /* Prevents a race condition. Ticket #3537 */ int rc = SQLITE_OK; /* Result code from this function */ u8 nReserve; /* Byte of unused space on each page */ @@ -1714,10 +1714,11 @@ const int isMemdb = (zFilename && strcmp(zFilename, ":memory:")==0) || (isTempDb && sqlite3TempInMemory(db)); #endif assert( db!=0 ); + assert( pVfs!=0 ); assert( sqlite3_mutex_held(db->mutex) ); assert( (flags&0xff)==flags ); /* flags fit in 8 bits */ /* Only a BTREE_SINGLE database can be BTREE_UNORDERED */ assert( (flags & BTREE_UNORDERED)==0 || (flags & BTREE_SINGLE)!=0 ); @@ -1732,11 +1733,10 @@ flags |= BTREE_MEMORY; } if( (vfsFlags & SQLITE_OPEN_MAIN_DB)!=0 && (isMemdb || isTempDb) ){ vfsFlags = (vfsFlags & ~SQLITE_OPEN_MAIN_DB) | SQLITE_OPEN_TEMP_DB; } - pVfs = db->pVfs; p = sqlite3MallocZero(sizeof(Btree)); if( !p ){ return SQLITE_NOMEM; } p->inTrans = TRANS_NONE; Index: src/btree.h ================================================================== --- src/btree.h +++ src/btree.h @@ -40,10 +40,11 @@ typedef struct BtCursor BtCursor; typedef struct BtShared BtShared; int sqlite3BtreeOpen( + sqlite3_vfs *pVfs, /* VFS to use with this b-tree */ const char *zFilename, /* Name of database file to open */ sqlite3 *db, /* Associated database connection */ Btree **ppBtree, /* Return open Btree* here */ int flags, /* Flags */ int vfsFlags /* Flags passed through to VFS open */ @@ -58,10 +59,11 @@ #define BTREE_OMIT_JOURNAL 1 /* Do not create or use a rollback journal */ #define BTREE_NO_READLOCK 2 /* Omit readlocks on readonly files */ #define BTREE_MEMORY 4 /* This is an in-memory DB */ #define BTREE_SINGLE 8 /* The file contains at most 1 b-tree */ #define BTREE_UNORDERED 16 /* Use of a hash implementation is OK */ +#define BTREE_READONLYSHM 32 /* Read-only SHM access is acceptable */ int sqlite3BtreeClose(Btree*); int sqlite3BtreeSetCacheSize(Btree*,int); int sqlite3BtreeSetSafetyLevel(Btree*,int,int,int); int sqlite3BtreeSyncDisabled(Btree*); Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -3441,11 +3441,11 @@ SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE | SQLITE_OPEN_TEMP_DB; - rc = sqlite3BtreeOpen(0, db, &pBt, 0, flags); + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pBt, 0, flags); if( rc!=SQLITE_OK ){ sqlite3ErrorMsg(pParse, "unable to open a temporary database " "file for storing temporary tables"); pParse->rc = rc; return 1; Index: src/global.c ================================================================== --- src/global.c +++ src/global.c @@ -127,20 +127,23 @@ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* f0..f7 ........ */ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 /* f8..ff ........ */ }; #endif - +#ifndef SQLITE_USE_URI +# define SQLITE_USE_URI 0 +#endif /* ** The following singleton contains the global configuration for ** the SQLite library. */ SQLITE_WSD struct Sqlite3Config sqlite3Config = { SQLITE_DEFAULT_MEMSTATUS, /* bMemstat */ 1, /* bCoreMutex */ SQLITE_THREADSAFE==1, /* bFullMutex */ + SQLITE_USE_URI, /* bOpenUri */ 0x7ffffffe, /* mxStrlen */ 100, /* szLookaside */ 500, /* nLookaside */ {0,0,0,0,0,0,0,0}, /* m */ {0,0,0,0,0,0,0,0,0}, /* mutex */ Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -426,10 +426,15 @@ typedef void(*LOGFUNC_t)(void*,int,const char*); sqlite3GlobalConfig.xLog = va_arg(ap, LOGFUNC_t); sqlite3GlobalConfig.pLogArg = va_arg(ap, void*); break; } + + case SQLITE_CONFIG_URI: { + sqlite3GlobalConfig.bOpenUri = va_arg(ap, int); + break; + } default: { rc = SQLITE_ERROR; break; } @@ -1795,25 +1800,270 @@ static void profile_sql(void *aux, const char *sql, u64 ns) { #pragma unused(aux) fprintf(stderr, "Query: %s\n Execution Time: %llu ms\n", sql, ns / 1000000); } #endif + +/* +** This function is used to parse both URIs and non-URI filenames passed by the +** user to API functions sqlite3_open() or sqlite3_open_v2(), and for database +** URIs specified as part of ATTACH statements. +** +** The first argument to this function is the name of the VFS to use (or +** a NULL to signify the default VFS) if the URI does not contain a "vfs=xxx" +** query parameter. The second argument contains the URI (or non-URI filename) +** itself. When this function is called the *pFlags variable should contain +** the default flags to open the database handle with. The value stored in +** *pFlags may be updated before returning if the URI filename contains +** "cache=xxx" or "mode=xxx" query parameters. +** +** The third argument, pBtflags, points to an integer containing the flags +** that will be passed as the 5th argument to sqlite3BtreeOpen (BTREE_XXX +** flags). This value will be edited if the URI filename contains a +** "readonly_shm=1" or "readonly_shm=0" query parameter. +** +** If successful, SQLITE_OK is returned. In this case *ppVfs is set to point to +** the VFS that should be used to open the database file. *pzFile is set to +** point to a buffer containing the name of the file to open. It is the +** responsibility of the caller to eventually call sqlite3_free() to release +** this buffer. +** +** If an error occurs, then an SQLite error code is returned and *pzErrMsg +** may be set to point to a buffer containing an English language error +** message. It is the responsibility of the caller to eventually release +** this buffer by calling sqlite3_free(). +*/ +int sqlite3ParseUri( + const char *zDefaultVfs, /* VFS to use if no "vfs=xxx" query option */ + const char *zUri, /* Nul-terminated URI to parse */ + unsigned int *pFlags, /* IN/OUT: SQLITE_OPEN_XXX flags */ + int *pBtflags, /* IN/OUT: BTREE_XXX flags */ + sqlite3_vfs **ppVfs, /* OUT: VFS to use */ + char **pzFile, /* OUT: Filename component of URI */ + char **pzErrMsg /* OUT: Error message (if rc!=SQLITE_OK) */ +){ + int rc = SQLITE_OK; + unsigned int flags = *pFlags; + const char *zVfs = zDefaultVfs; + char *zFile; + char c; + int nUri = sqlite3Strlen30(zUri); + + assert( *pzErrMsg==0 ); + + if( ((flags & SQLITE_OPEN_URI) || sqlite3GlobalConfig.bOpenUri) + && nUri>=5 && memcmp(zUri, "file:", 5)==0 + ){ + char *zOpt; + int eState; /* Parser state when parsing URI */ + int iIn; /* Input character index */ + int iOut = 0; /* Output character index */ + int nByte = nUri+2; /* Bytes of space to allocate */ + + /* Make sure the SQLITE_OPEN_URI flag is set to indicate to the VFS xOpen + ** method that there may be extra parameters following the file-name. */ + flags |= SQLITE_OPEN_URI; + + for(iIn=0; iIn=0 && octet<256 ); + if( octet==0 ){ + /* This branch is taken when "%00" appears within the URI. In this + ** case we ignore all text in the remainder of the path, name or + ** value currently being parsed. So ignore the current character + ** and skip to the next "?", "=" or "&", as appropriate. */ + while( (c = zUri[iIn])!=0 && c!='#' + && (eState!=0 || c!='?') + && (eState!=1 || (c!='=' && c!='&')) + && (eState!=2 || c!='&') + ){ + iIn++; + } + continue; + } + c = octet; + }else if( eState==1 && (c=='&' || c=='=') ){ + if( zFile[iOut-1]==0 ){ + /* An empty option name. Ignore this option altogether. */ + while( zUri[iIn] && zUri[iIn]!='#' && zUri[iIn-1]!='&' ) iIn++; + continue; + } + if( c=='&' ){ + zFile[iOut++] = '\0'; + }else{ + eState = 2; + } + c = 0; + }else if( (eState==0 && c=='?') || (eState==2 && c=='&') ){ + c = 0; + eState = 1; + } + zFile[iOut++] = c; + } + if( eState==1 ) zFile[iOut++] = '\0'; + zFile[iOut++] = '\0'; + zFile[iOut++] = '\0'; + + /* Check if there were any options specified that should be interpreted + ** here. Options that are interpreted here include "vfs" and those that + ** correspond to flags that may be passed to the sqlite3_open_v2() + ** method. */ + zOpt = &zFile[sqlite3Strlen30(zFile)+1]; + while( zOpt[0] ){ + int nOpt = sqlite3Strlen30(zOpt); + char *zVal = &zOpt[nOpt+1]; + int nVal = sqlite3Strlen30(zVal); + + if( nOpt==3 && memcmp("vfs", zOpt, 3)==0 ){ + zVfs = zVal; + }else if( nOpt==12 && memcmp("readonly_shm", zOpt, 12)==0 ){ + if( sqlite3Atoi(zVal) ){ + *pBtflags |= BTREE_READONLYSHM; + }else{ + *pBtflags &= ~BTREE_READONLYSHM; + } + }else{ + struct OpenMode { + const char *z; + int mode; + } *aMode = 0; + char *zModeType; + int mask; + int limit; + + if( nOpt==5 && memcmp("cache", zOpt, 5)==0 ){ + static struct OpenMode aCacheMode[] = { + { "shared", SQLITE_OPEN_SHAREDCACHE }, + { "private", SQLITE_OPEN_PRIVATECACHE }, + { 0, 0 } + }; + + mask = SQLITE_OPEN_SHAREDCACHE|SQLITE_OPEN_PRIVATECACHE; + aMode = aCacheMode; + limit = mask; + zModeType = "cache"; + } + if( nOpt==4 && memcmp("mode", zOpt, 4)==0 ){ + static struct OpenMode aOpenMode[] = { + { "ro", SQLITE_OPEN_READONLY }, + { "rw", SQLITE_OPEN_READWRITE }, + { "rwc", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE }, + { 0, 0 } + }; + + mask = SQLITE_OPEN_READONLY|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE; + aMode = aOpenMode; + limit = mask & flags; + zModeType = "access"; + } + + if( aMode ){ + int i; + int mode = 0; + for(i=0; aMode[i].z; i++){ + const char *z = aMode[i].z; + if( nVal==sqlite3Strlen30(z) && 0==memcmp(zVal, z, nVal) ){ + mode = aMode[i].mode; + break; + } + } + if( mode==0 ){ + *pzErrMsg = sqlite3_mprintf("no such %s mode: %s", zModeType, zVal); + rc = SQLITE_ERROR; + goto parse_uri_out; + } + if( mode>limit ){ + *pzErrMsg = sqlite3_mprintf("%s mode not allowed: %s", + zModeType, zVal); + rc = SQLITE_PERM; + goto parse_uri_out; + } + flags = (flags & ~mask) | mode; + } + } + + zOpt = &zVal[nVal+1]; + } + + }else{ + zFile = sqlite3_malloc(nUri+2); + if( !zFile ) return SQLITE_NOMEM; + memcpy(zFile, zUri, nUri); + zFile[nUri] = '\0'; + zFile[nUri+1] = '\0'; + } + + *ppVfs = sqlite3_vfs_find(zVfs); + if( *ppVfs==0 ){ + *pzErrMsg = sqlite3_mprintf("no such vfs: %s", zVfs); + rc = SQLITE_ERROR; + } + parse_uri_out: + if( rc!=SQLITE_OK ){ + sqlite3_free(zFile); + zFile = 0; + } + *pFlags = flags; + *pzFile = zFile; + return rc; +} + /* ** This routine does the work of opening a database on behalf of ** sqlite3_open() and sqlite3_open16(). The database filename "zFilename" ** is UTF-8 encoded. */ static int openDatabase( const char *zFilename, /* Database filename UTF-8 encoded */ sqlite3 **ppDb, /* OUT: Returned database handle */ - unsigned flags, /* Operational flags */ + unsigned int flags, /* Operational flags */ const char *zVfs /* Name of the VFS to use */ ){ - sqlite3 *db; - int rc; - int isThreadsafe; + sqlite3 *db; /* Store allocated handle here */ + int rc; /* Return code */ + int isThreadsafe; /* True for threadsafe connections */ + char *zOpen = 0; /* Filename argument to pass to BtreeOpen() */ + char *zErrMsg = 0; /* Error message from sqlite3ParseUri() */ + int btflags = 0; /* Mask of BTREE_XXX flags */ *ppDb = 0; #ifndef SQLITE_OMIT_AUTOINIT rc = sqlite3_initialize(); if( rc ) return rc; @@ -1833,11 +2083,11 @@ assert( SQLITE_OPEN_READWRITE == 0x02 ); assert( SQLITE_OPEN_CREATE == 0x04 ); testcase( (1<<(flags&7))==0x02 ); /* READONLY */ testcase( (1<<(flags&7))==0x04 ); /* READWRITE */ testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */ - if( ((1<<(flags&7)) & 0x46)==0 ) return SQLITE_MISUSE; + if( ((1<<(flags&7)) & 0x46)==0 ) return SQLITE_MISUSE_BKPT; if( sqlite3GlobalConfig.bCoreMutex==0 ){ isThreadsafe = 0; }else if( flags & SQLITE_OPEN_NOMUTEX ){ isThreadsafe = 0; @@ -1914,17 +2164,10 @@ sqlite3HashInit(&db->aCollSeq); #ifndef SQLITE_OMIT_VIRTUALTABLE sqlite3HashInit(&db->aModule); #endif - db->pVfs = sqlite3_vfs_find(zVfs); - if( !db->pVfs ){ - rc = SQLITE_ERROR; - sqlite3Error(db, rc, "no such vfs: %s", zVfs); - goto opendb_out; - } - /* Add the default collation sequence BINARY. BINARY works for both UTF-8 ** and UTF-16, so add a version for each to avoid any unnecessary ** conversions. The only error that can occur here is a malloc() failure. */ createCollation(db, "BINARY", SQLITE_UTF8, SQLITE_COLL_BINARY, 0, @@ -1943,13 +2186,23 @@ /* Also add a UTF-8 case-insensitive collation sequence. */ createCollation(db, "NOCASE", SQLITE_UTF8, SQLITE_COLL_NOCASE, 0, nocaseCollatingFunc, 0); - /* Open the backend database driver */ + /* Parse the filename/URI argument. */ db->openFlags = flags; - rc = sqlite3BtreeOpen(zFilename, db, &db->aDb[0].pBt, 0, + rc = sqlite3ParseUri( + zVfs, zFilename, &flags, &btflags, &db->pVfs, &zOpen, &zErrMsg); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_NOMEM ) db->mallocFailed = 1; + sqlite3Error(db, rc, zErrMsg ? "%s" : 0, zErrMsg); + sqlite3_free(zErrMsg); + goto opendb_out; + } + + /* Open the backend database driver */ + rc = sqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, btflags, flags | SQLITE_OPEN_MAIN_DB); if( rc!=SQLITE_OK ){ if( rc==SQLITE_IOERR_NOMEM ){ rc = SQLITE_NOMEM; } @@ -2038,10 +2291,11 @@ sqlite3GlobalConfig.nLookaside); sqlite3_wal_autocheckpoint(db, SQLITE_DEFAULT_WAL_AUTOCHECKPOINT); opendb_out: + sqlite3_free(zOpen); if( db ){ assert( db->mutex!=0 || isThreadsafe==0 || sqlite3GlobalConfig.bFullMutex==0 ); sqlite3_mutex_leave(db->mutex); } rc = sqlite3_errcode(db); @@ -2081,11 +2335,11 @@ const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb, /* OUT: SQLite db handle */ int flags, /* Flags */ const char *zVfs /* Name of VFS module to use */ ){ - return openDatabase(filename, ppDb, flags, zVfs); + return openDatabase(filename, ppDb, (unsigned int)flags, zVfs); } #ifndef SQLITE_OMIT_UTF16 /* ** Open a new database handle. Index: src/malloc.c ================================================================== --- src/malloc.c +++ src/malloc.c @@ -531,11 +531,11 @@ sqlite3_mutex_enter(mem0.mutex); sqlite3StatusSet(SQLITE_STATUS_MALLOC_SIZE, nBytes); nDiff = nNew - nOld; if( sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >= mem0.alarmThreshold-nDiff ){ - sqlite3MallocAlarm(nNew-nOld); + sqlite3MallocAlarm(nDiff); } assert( sqlite3MemdebugHasType(pOld, MEMTYPE_HEAP) ); assert( sqlite3MemdebugNoType(pOld, ~MEMTYPE_HEAP) ); pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); if( pNew==0 && mem0.alarmCallback ){ @@ -542,11 +542,11 @@ sqlite3MallocAlarm(nBytes); pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); } if( pNew ){ nNew = sqlite3MallocSize(pNew); - sqlite3StatusAdd(SQLITE_STATUS_MEMORY_USED, nDiff); + sqlite3StatusAdd(SQLITE_STATUS_MEMORY_USED, nNew-nOld); } sqlite3_mutex_leave(mem0.mutex); }else{ pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); } Index: src/os_unix.c ================================================================== --- src/os_unix.c +++ src/os_unix.c @@ -210,10 +210,11 @@ int lastErrno; /* The unix errno from last I/O error */ void *lockingContext; /* Locking style specific state */ UnixUnusedFd *pUnused; /* Pre-allocated UnixUnusedFd */ const char *zPath; /* Name of the file */ unixShm *pShm; /* Shared memory segment information */ + int readOnlyShm; /* True to open shared-memory read-only */ int szChunk; /* Configured by FCNTL_CHUNK_SIZE */ #if SQLITE_ENABLE_LOCKING_STYLE int openFlags; /* The flags specified at open() */ #endif #if SQLITE_ENABLE_LOCKING_STYLE || defined(__APPLE__) @@ -3471,10 +3472,14 @@ return SQLITE_OK; } case SQLITE_FCNTL_SIZE_HINT: { return fcntlSizeHint((unixFile *)id, *(i64 *)pArg); } + case SQLITE_FCNTL_READONLY_SHM: { + ((unixFile*)id)->readOnlyShm = (pArg!=0); + 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. @@ -3773,10 +3778,11 @@ int szRegion; /* Size of shared-memory regions */ int nRegion; /* Size of array apRegion */ char **apRegion; /* Array of mapped shared-memory regions */ int nRef; /* Number of unixShm objects pointing to this */ unixShm *pFirst; /* All unixShm objects pointing to this */ + u8 readOnly; /* True if this is a read-only mapping */ #ifdef SQLITE_DEBUG u8 exclMask; /* Mask of exclusive locks held */ u8 sharedMask; /* Mask of shared locks held */ u8 nextShmId; /* Next available unixShm.id value */ #endif @@ -4029,31 +4035,50 @@ rc = SQLITE_NOMEM; goto shm_open_err; } if( pInode->bProcessLock==0 ){ - pShmNode->h = robust_open(zShmFilename, O_RDWR|O_CREAT, - (sStat.st_mode & 0777)); + int flags = (pDbFd->readOnlyShm ? O_RDONLY : O_RDWR|O_CREAT); + pShmNode->h = robust_open(zShmFilename, flags, (sStat.st_mode & 0777)); if( pShmNode->h<0 ){ rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShmFilename); goto shm_open_err; } + pShmNode->readOnly = pDbFd->readOnlyShm; /* Check to see if another process is holding the dead-man switch. - ** If not, truncate the file to zero length. - */ - rc = SQLITE_OK; - if( unixShmSystemLock(pShmNode, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){ - if( robust_ftruncate(pShmNode->h, 0) ){ - rc = unixLogError(SQLITE_IOERR_SHMOPEN, "ftruncate", zShmFilename); - } - } - if( rc==SQLITE_OK ){ - rc = unixShmSystemLock(pShmNode, F_RDLCK, UNIX_SHM_DMS, 1); - } - if( rc ) goto shm_open_err; - } + ** If not, zero the first few bytes of the shared-memory file to make + ** sure it is not mistaken for valid by code in wal.c. Except, if this + ** is a read-only connection to the shared-memory then it is not possible + ** to check check if another process is holding a read-lock on the DMS + ** byte, as we cannot attempt a write-lock via a read-only file + ** descriptor. So in this case, we just assume the shared-memory + ** contents are Ok and proceed. */ + if( pShmNode->readOnly==0 ){ + rc = SQLITE_OK; + if( unixShmSystemLock(pShmNode, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){ + if( pDbFd->readOnlyShm ){ + rc = SQLITE_IOERR_SHMOPEN; + }else if( 4!=osWrite(pShmNode->h, "\00\00\00\00", 4) ){ + rc = unixLogError(SQLITE_IOERR_SHMOPEN, "ftruncate", zShmFilename); + } + } + if( rc==SQLITE_OK ){ + rc = unixShmSystemLock(pShmNode, F_RDLCK, UNIX_SHM_DMS, 1); + } + if( rc ) goto shm_open_err; + } + } + } + + /* If the unixShmNode is read-only, but SQLITE_FCNTL_READONLY_SHM has not + ** been set for file-descriptor pDbFd, return an error. The wal.c module + ** will then call this function again with SQLITE_FCNTL_READONLY_SHM set. + */ + else if( pShmNode->readOnly && !pDbFd->readOnlyShm ){ + rc = SQLITE_IOERR_SHMOPEN; + goto shm_open_err; } /* Make the new connection a child of the unixShmNode */ p->pShmNode = pShmNode; #ifdef SQLITE_DEBUG @@ -4172,11 +4197,11 @@ } pShmNode->apRegion = apNew; while(pShmNode->nRegion<=iRegion){ void *pMem; if( pShmNode->h>=0 ){ - pMem = mmap(0, szRegion, PROT_READ|PROT_WRITE, + pMem = mmap(0, szRegion, PROT_READ|(!pShmNode->readOnly?PROT_WRITE:0), MAP_SHARED, pShmNode->h, pShmNode->nRegion*szRegion ); if( pMem==MAP_FAILED ){ rc = unixLogError(SQLITE_IOERR_SHMMAP, "mmap", pShmNode->zFilename); goto shmpage_out; Index: src/os_win.c ================================================================== --- src/os_win.c +++ src/os_win.c @@ -2462,10 +2462,17 @@ #if !SQLITE_OS_WINCE && !defined(__CYGWIN__) int nByte; void *zConverted; char *zOut; + + /* If this path name begins with "/X:", where "X" is any alphabetic + ** character, discard the initial "/" from the pathname. + */ + if( zRelative[0]=='/' && sqlite3Isalpha(zRelative[1]) && zRelative[2]==':' ){ + zRelative++; + } /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this ** function failing. This function could fail if, for example, the ** current working directory has been unlinked. Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -618,10 +618,11 @@ u8 ckptSyncFlags; /* SYNC_NORMAL or SYNC_FULL for checkpoint */ u8 syncFlags; /* SYNC_NORMAL or SYNC_FULL otherwise */ u8 tempFile; /* zFilename is a temporary file */ u8 readOnly; /* True for a read-only database */ u8 memDb; /* True to inhibit all file I/O */ + u8 readOnlyShm; /* True if read-only shm access is Ok */ /************************************************************************** ** The following block contains those class members that change during ** routine opertion. Class members not in this block are either fixed ** when the pager is first created or else only change when there is a @@ -3015,22 +3016,23 @@ ** other writers or checkpointers. */ static int pagerBeginReadTransaction(Pager *pPager){ int rc; /* Return code */ int changed = 0; /* True if cache must be reset */ + Wal *pWal = pPager->pWal; assert( pagerUseWal(pPager) ); assert( pPager->eState==PAGER_OPEN || pPager->eState==PAGER_READER ); /* sqlite3WalEndReadTransaction() was not called for the previous ** transaction in locking_mode=EXCLUSIVE. So call it now. If we ** are in locking_mode=NORMAL and EndRead() was previously called, ** the duplicate call is harmless. */ - sqlite3WalEndReadTransaction(pPager->pWal); + sqlite3WalEndReadTransaction(pWal); - rc = sqlite3WalBeginReadTransaction(pPager->pWal, &changed); + rc = sqlite3WalBeginReadTransaction(pWal, pPager->readOnlyShm, &changed); if( rc!=SQLITE_OK || changed ){ pager_reset(pPager); } return rc; @@ -4297,10 +4299,12 @@ int nPathname = 0; /* Number of bytes in zPathname */ int useJournal = (flags & PAGER_OMIT_JOURNAL)==0; /* False to omit journal */ int noReadlock = (flags & PAGER_NO_READLOCK)!=0; /* True to omit read-lock */ int pcacheSize = sqlite3PcacheSize(); /* Bytes to allocate for PCache */ u32 szPageDflt = SQLITE_DEFAULT_PAGE_SIZE; /* Default page size */ + const char *zUri = 0; /* URI args to copy */ + int nUri = 0; /* Number of bytes of URI args at *zUri */ /* Figure out how much space is required for each journal file-handle ** (there are two of them, the main journal and the sub-journal). This ** is the maximum space required for an in-memory journal file handle ** and a regular journal file-handle. Note that a "regular journal-handle" @@ -4327,18 +4331,25 @@ /* Compute and store the full pathname in an allocated buffer pointed ** to by zPathname, length nPathname. Or, if this is a temporary file, ** leave both nPathname and zPathname set to 0. */ if( zFilename && zFilename[0] ){ + const char *z; nPathname = pVfs->mxPathname+1; zPathname = sqlite3Malloc(nPathname*2); if( zPathname==0 ){ return SQLITE_NOMEM; } zPathname[0] = 0; /* Make sure initialized even if FullPathname() fails */ rc = sqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname); nPathname = sqlite3Strlen30(zPathname); + z = zUri = &zFilename[sqlite3Strlen30(zFilename)+1]; + while( *z ){ + z += sqlite3Strlen30(z)+1; + z += sqlite3Strlen30(z)+1; + } + nUri = &z[1] - zUri; if( rc==SQLITE_OK && nPathname+8>pVfs->mxPathname ){ /* This branch is taken when the journal path required by ** the database being opened will be more than pVfs->mxPathname ** bytes in length. This means the database cannot be opened, ** as it will not be possible to open the journal file or even @@ -4367,11 +4378,11 @@ pPtr = (u8 *)sqlite3MallocZero( ROUND8(sizeof(*pPager)) + /* Pager structure */ ROUND8(pcacheSize) + /* PCache object */ ROUND8(pVfs->szOsFile) + /* The main db file */ journalFileSize * 2 + /* The two journal files */ - nPathname + 1 + /* zFilename */ + nPathname + 1 + nUri + /* zFilename */ nPathname + 8 + 1 /* zJournal */ #ifndef SQLITE_OMIT_WAL + nPathname + 4 + 1 /* zWal */ #endif ); @@ -4389,12 +4400,13 @@ assert( EIGHT_BYTE_ALIGNMENT(pPager->jfd) ); /* Fill in the Pager.zFilename and Pager.zJournal buffers, if required. */ if( zPathname ){ assert( nPathname>0 ); - pPager->zJournal = (char*)(pPtr += nPathname + 1); + pPager->zJournal = (char*)(pPtr += nPathname + 1 + nUri); memcpy(pPager->zFilename, zPathname, nPathname); + memcpy(&pPager->zFilename[nPathname+1], zUri, nUri); memcpy(pPager->zJournal, zPathname, nPathname); memcpy(&pPager->zJournal[nPathname], "-journal", 8); #ifndef SQLITE_OMIT_WAL pPager->zWal = &pPager->zJournal[nPathname+8+1]; memcpy(pPager->zWal, zPathname, nPathname); @@ -4402,10 +4414,11 @@ #endif sqlite3_free(zPathname); } pPager->pVfs = pVfs; pPager->vfsFlags = vfsFlags; + pPager->readOnlyShm = (flags & PAGER_READONLYSHM)!=0; /* Open the pager file. */ if( zFilename && zFilename[0] ){ int fout = 0; /* VFS flags returned by xOpen() */ Index: src/pager.h ================================================================== --- src/pager.h +++ src/pager.h @@ -58,10 +58,11 @@ ** NOTE: These values must match the corresponding BTREE_ values in btree.h. */ #define PAGER_OMIT_JOURNAL 0x0001 /* Do not use a rollback journal */ #define PAGER_NO_READLOCK 0x0002 /* Omit readlocks on readonly files */ #define PAGER_MEMORY 0x0004 /* In-memory database */ +#define PAGER_READONLYSHM 0x0020 /* Read-only SHM access is acceptable */ /* ** Valid values for the second argument to sqlite3PagerLockingMode(). */ #define PAGER_LOCKINGMODE_QUERY -1 Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -452,10 +452,13 @@ #define SQLITE_IOERR_SEEK (SQLITE_IOERR | (22<<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)) +#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) +#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8)) + /* ** CAPI3REF: Flags For File Open Operations ** ** These bit values are intended for use in the ** 3rd parameter to the [sqlite3_open_v2()] interface and @@ -478,10 +481,11 @@ #define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_WAL 0x00080000 /* VFS only */ +#define SQLITE_OPEN_URI 0x00100000 /* Ok for sqlite3_open_v2() */ /* Reserved: 0x00F00000 */ /* ** CAPI3REF: Device Characteristics @@ -730,19 +734,41 @@ ** when [PRAGMA synchronous | PRAGMA synchronous=OFF] is set, but most ** VFSes do not need this signal and should silently ignore this opcode. ** Applications should not call [sqlite3_file_control()] with this ** opcode as doing so may disrupt the operation of the specialized VFSes ** that do require it. +** +** The [SQLITE_FCNTL_READONLY_SHM] may be generated internally by SQLite if +** the "readonly_shm=1" URI option is specified when the database is opened. +** The fourth argument passed to the VFS xFileControl methods is a pointer +** to a variable of type "int" containing the value 1 or 0. If the variable +** contains the value 1, then this indicates to the VFS that a read-only +** mapping of the shared-memory region is acceptable. If it is set to 0, then +** this indicates that a read-write mapping is required (as normal). If +** a read-only mapping is returned, then the VFS may also return read-only +** mappings for any subsequent requests via the same file-descriptor - +** regardless of the value most recently configured using +** SQLITE_FCNTL_READONLY_SHM. +** +** In practice, if "readonly_shm=1" is specified and the first attempt to +** map a shared-memory region fails, then this file-control is invoked with +** the argument variable set to 1 and a second attempt to map the shared-memory +** region is made. If this mapping succeeds, then the connection continues +** with the read-only mapping. Otherwise, if it fails, SQLITE_CANTOPEN is +** returned to the caller. Whether or not the second (read-only) mapping +** attempt succeeds, the file-control is invoked again with the argument +** variable set to 0. */ #define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_GET_LOCKPROXYFILE 2 #define SQLITE_SET_LOCKPROXYFILE 3 #define SQLITE_LAST_ERRNO 4 #define SQLITE_FCNTL_SIZE_HINT 5 #define SQLITE_FCNTL_CHUNK_SIZE 6 #define SQLITE_FCNTL_FILE_POINTER 7 #define SQLITE_FCNTL_SYNC_OMITTED 8 +#define SQLITE_FCNTL_READONLY_SHM 9 /* ** CAPI3REF: Mutex Handle ** @@ -755,10 +781,11 @@ */ typedef struct sqlite3_mutex sqlite3_mutex; /* ** CAPI3REF: OS Interface Object +** KEYWORDS: VFS VFSes ** ** An instance of the sqlite3_vfs object defines the interface between ** the SQLite core and the underlying operating system. The "vfs" ** in the name of the object stands for "virtual file system". ** @@ -1425,10 +1452,22 @@ ** The SQLite logging interface is not reentrant; the logger function ** supplied by the application must not invoke any SQLite interface. ** In a multi-threaded application, the application-defined logger ** function must be threadsafe. ** +**
SQLITE_CONFIG_URI +**
This option takes a single argument of type int. If non-zero, then +** URI handling is globally enabled. If the parameter is zero, then URI handling +** is globally disabled. If URI handling is globally enabled, all filenames +** passed to [sqlite3_open()], [sqlite3_open_v2()], [sqlite3_open16()] or +** specified as part of [ATTACH] commands are interpreted as URIs, regardless +** of whether or not the [SQLITE_OPEN_URI] flag is set when the database +** connection is opened. If it is globally disabled, filenames are +** only interpreted as URIs if the SQLITE_OPEN_URI flag is set when the +** database connection is opened. By default, URI handling is globally +** disabled. The default value may be changed by compiling with the +** [SQLITE_USE_URI] symbol defined. ** */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ #define SQLITE_CONFIG_SERIALIZED 3 /* nil */ @@ -1443,10 +1482,11 @@ /* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ #define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ #define SQLITE_CONFIG_PCACHE 14 /* sqlite3_pcache_methods* */ #define SQLITE_CONFIG_GETPCACHE 15 /* sqlite3_pcache_methods* */ #define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ +#define SQLITE_CONFIG_URI 17 /* int */ /* ** CAPI3REF: Database Connection Configuration Options ** ** These constants are the available integer configuration options that @@ -2322,11 +2362,11 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); /* ** CAPI3REF: Opening A New Database Connection ** -** ^These routines open an SQLite database file whose name is given by the +** ^These routines open an SQLite database file as specified by the ** filename argument. ^The filename argument is interpreted as UTF-8 for ** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte ** order for sqlite3_open16(). ^(A [database connection] handle is usually ** returned in *ppDb, even if an error occurs. The only exception is that ** if SQLite is unable to allocate memory to hold the [sqlite3] object, @@ -2384,10 +2424,15 @@ ** ^The [SQLITE_OPEN_SHAREDCACHE] flag causes the database connection to be ** eligible to use [shared cache mode], regardless of whether or not shared ** cache is enabled using [sqlite3_enable_shared_cache()]. ^The ** [SQLITE_OPEN_PRIVATECACHE] flag causes the database connection to not ** participate in [shared cache mode] even if it is enabled. +** +** ^The fourth parameter to sqlite3_open_v2() is the name of the +** [sqlite3_vfs] object that defines the operating system interface that +** the new database connection should use. ^If the fourth parameter is +** a NULL pointer then the default [sqlite3_vfs] object is used. ** ** ^If the filename is ":memory:", then a private, temporary in-memory database ** is created for the connection. ^This in-memory database will vanish when ** the database connection is closed. Future versions of SQLite might ** make use of additional special filenames that begin with the ":" character. @@ -2397,14 +2442,120 @@ ** ** ^If the filename is an empty string, then a private, temporary ** on-disk database will be created. ^This private database will be ** automatically deleted as soon as the database connection is closed. ** -** ^The fourth parameter to sqlite3_open_v2() is the name of the -** [sqlite3_vfs] object that defines the operating system interface that -** the new database connection should use. ^If the fourth parameter is -** a NULL pointer then the default [sqlite3_vfs] object is used. +** ^If URI filename interpretation is enabled, and the filename argument +** begins with "file:", then the filename is interpreted as a URI. ^URI +** filename interpretation is enabled if the [SQLITE_OPEN_URI] flag is +** is set in the fourth argument to sqlite3_open_v2(), or if it has +** been enabled globally using the [SQLITE_CONFIG_URI] option with the +** [sqlite3_config()] method. +** +** URI filenames are parsed according to RFC 1738. If the URI contains an +** 'authority', then it must be either an empty string or the string +** "localhost". ^If the authority is not an empty string or "localhost", an +** error is returned to the caller. ^The 'fragment' component of a URI, if +** present, is always ignored. +** +** ^SQLite uses the 'path' component of the URI as the path to the database file +** to open. ^If the path begins with a '/' character, then it is interpreted as +** an absolute path. ^If it does not begin with a '/', it is interpreted as a +** relative path. ^On windows, the first component of an absolute path +** is a drive specification (e.g. "C:"). +** +** The query component of a URI may contain parameters that are interpreted +** either by SQLite itself, or by a [sqlite3_vfs | custom VFS implementation]. +** SQLite interprets the following four query parameters: +** +** +** +** ^Specifying an unknown parameter in the query component of a URI is not an +** error. +** +** URI filename examples: +** +** +**
URI filenames Results +**
file:data.db +** Open the file "data.db" in the current directory. +**
file:/home/fred/data.db
+** file:///home/fred/data.db
+** file://localhost/home/fred/data.db
+** Open the database file "/home/fred/data.db". +**
file://darkstar/home/fred/data.db +** An error. "darkstar" is not a recognized authority. +**
+** file:///C:/Documents%20and%20Settings/fred/Desktop/data.db +** Windows only: Open the file "data.db" on fred's desktop on drive +** C:. Note that the %20 escaping in this example is not strictly +** necessary - space characters can be used literally +** in URI filenames. +**
file:data.db?mode=ro&cache=private +** Open file "data.db" in the current directory for read-only access. +** Regardless of whether or not shared-cache mode is enabled by +** default, use a private cache. +**
file:/home/fred/data.db?vfs=unix-nolock +** Open file "/home/fred/data.db". Use the special VFS "unix-nolock". +**
file:data.db?mode=readonly +** An error. "readonly" is not a valid option for the "mode" parameter. +**
+** +** ^URI hexadecimal escape sequences (%HH) are supported within the path and +** query components of a URI. A hexadecimal escape sequence consists of a +** percent sign - "%" - followed by exactly two hexadecimal digits +** specifying an octet value. ^Before the path or query components of a +** URI filename are interpreted, they are encoded using UTF-8 and all +** hexadecimal escape sequences replaced by a single byte containing the +** corresponding octet. If this process generates an invalid UTF-8 encoding, +** the results are undefined. ** ** Note to Windows users: The encoding used for the filename argument ** of sqlite3_open() and sqlite3_open_v2() must be UTF-8, not whatever ** codepage is currently defined. Filenames containing international ** characters must be converted to UTF-8 prior to passing them into @@ -4609,12 +4760,12 @@ int (*xRollback)(sqlite3_vtab *pVTab); int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName, void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg); int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); - /* The methods above are in version 0 of the sqlite_module object. Those - ** below are for version 1 and greater. */ + /* The methods above are in version 1 of the sqlite_module object. Those + ** below are for version 2 and greater. */ int (*xSavepoint)(sqlite3_vtab *pVTab, int); int (*xRelease)(sqlite3_vtab *pVTab, int); int (*xRollbackTo)(sqlite3_vtab *pVTab, int); }; @@ -6423,41 +6574,40 @@ ** These macros define the various options to the ** [sqlite3_vtab_config()] interface that [virtual table] implementations ** can use to customize and optimize their behavior. ** **
-**
SQLITE_VTAB_CONSTRAINT_SUPPORT -**
If the second argument to [sqlite3_vtab_config()] is -** SQLITE_VTAB_CONSTRAINT_SUPPORT, then SQLite expects this function to -** have been called with three arguments, the third of which being of -** type 'int'. If the third argument is zero, then the virtual table -** is indicating that it does not support constraints. In this case if -** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], the entire -** statement is rolled back as if [ON CONFLICT | OR ABORT] had been -** specified as part of the users SQL statement, regardless of the actual -** ON CONFLICT mode specified. -** -** If the third argument passed is non-zero, then the virtual table -** implementation must guarantee that if [xUpdate] returns -** [SQLITE_CONSTRAINT], it does so before any modifications to internal -** or persistent data structures have been made. If the [ON CONFLICT] -** mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite is able to roll back -** a statement or database transaction, and abandon or continue processing -** the current SQL statement as appropriate. If the ON CONFLICT mode is -** REPLACE and the [xUpdate] method returns [SQLITE_CONSTRAINT], SQLite -** handles this as if the ON CONFLICT mode had been ABORT. -** -** Virtual table implementations that are required to handle OR REPLACE -** must do so within the xUpdate method. If a call to the -** [sqlite3_vtab_on_conflict()] function indicates that the current ON -** CONFLICT policy is REPLACE, the virtual table implementation should -** silently replace the appropriate rows within the xUpdate callback and -** return SQLITE_OK. Or, if this is not possible, it may return -** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT -** constraint handling. -**
-** +**
SQLITE_VTAB_CONSTRAINT_SUPPORT +**
Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported, +** where X is an integer. If X is zero, then the [virtual table] whose +** [xCreate] or [xConnect] method invoked [sqlite3_vtab_config()] does not +** support constraints. In this configuration (which is the default) if +** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire +** statement is rolled back as if [ON CONFLICT | OR ABORT] had been +** specified as part of the users SQL statement, regardless of the actual +** ON CONFLICT mode specified. +** +** If X is non-zero, then the virtual table implementation guarantees +** that if [xUpdate] returns [SQLITE_CONSTRAINT], it will do so before +** any modifications to internal or persistent data structures have been made. +** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite +** is able to roll back a statement or database transaction, and abandon +** or continue processing the current SQL statement as appropriate. +** If the ON CONFLICT mode is REPLACE and the [xUpdate] method returns +** [SQLITE_CONSTRAINT], SQLite handles this as if the ON CONFLICT mode +** had been ABORT. +** +** Virtual table implementations that are required to handle OR REPLACE +** must do so within the [xUpdate] method. If a call to the +** [sqlite3_vtab_on_conflict()] function indicates that the current ON +** CONFLICT policy is REPLACE, the virtual table implementation should +** silently replace the appropriate rows within the xUpdate callback and +** return SQLITE_OK. Or, if this is not possible, it may return +** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT +** constraint handling. +** */ #define SQLITE_VTAB_CONSTRAINT_SUPPORT 1 /* ** CAPI3REF: Determine The Virtual Table Conflict Policy Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -801,11 +801,11 @@ struct sqlite3 { sqlite3_vfs *pVfs; /* OS Interface */ int nDb; /* Number of backends currently in use */ Db *aDb; /* All backends */ int flags; /* Miscellaneous flags. See below */ - int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */ + unsigned int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */ int errCode; /* Most recent error code (SQLITE_*) */ int errMask; /* & result codes with this before returning */ u8 autoCommit; /* The auto-commit flag. */ u8 temp_store; /* 1: file 2: memory 0: default */ u8 mallocFailed; /* True if we have seen a malloc failure */ @@ -2421,10 +2421,11 @@ */ struct Sqlite3Config { int bMemstat; /* True to enable memory status */ int bCoreMutex; /* True to enable core mutexing */ int bFullMutex; /* True to enable full mutexing */ + int bOpenUri; /* True to interpret filenames as URIs */ int mxStrlen; /* Maximum string length */ int szLookaside; /* Default lookaside buffer size */ int nLookaside; /* Default lookaside buffer count */ sqlite3_mem_methods m; /* Low-level memory allocation interface */ sqlite3_mutex_methods mutex; /* Low-level mutex interface */ @@ -2670,10 +2671,12 @@ void sqlite3AddCheckConstraint(Parse*, Expr*); void sqlite3AddColumnType(Parse*,Token*); void sqlite3AddDefaultValue(Parse*,ExprSpan*); void sqlite3AddCollateType(Parse*, Token*); void sqlite3EndTable(Parse*,Token*,Token*,Select*); +int sqlite3ParseUri(const char*,const char*,unsigned int*,int*, + sqlite3_vfs**,char**,char **); Bitvec *sqlite3BitvecCreate(u32); int sqlite3BitvecTest(Bitvec*, u32); int sqlite3BitvecSet(Bitvec*, u32); void sqlite3BitvecClear(Bitvec*, u32, void*); @@ -2920,10 +2923,11 @@ int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity); char sqlite3ExprAffinity(Expr *pExpr); int sqlite3Atoi64(const char*, i64*, int, u8); void sqlite3Error(sqlite3*, int, const char*,...); void *sqlite3HexToBlob(sqlite3*, const char *z, int n); +u8 sqlite3HexToInt(int h); int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); const char *sqlite3ErrStr(int); int sqlite3ReadSchema(Parse *pParse); CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int); CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName); Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -161,10 +161,13 @@ 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_READONLY_RECOVERY: zName = "SQLITE_READONLY_RECOVERY"; break; + case SQLITE_READONLY_CANTLOCK: zName = "SQLITE_READONLY_CANTLOCK"; break; default: zName = "SQLITE_Unknown"; break; } return zName; } #define t1ErrorName sqlite3TestErrorName @@ -3860,10 +3863,80 @@ if( sqlite3TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR; Tcl_AppendResult(interp, zBuf, 0); return TCL_OK; } + +/* +** Usage: sqlite3_open_v2 FILENAME FLAGS VFS +*/ +static int test_open_v2( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + const char *zFilename; + const char *zVfs; + int flags = 0; + sqlite3 *db; + int rc; + char zBuf[100]; + + int nFlag; + Tcl_Obj **apFlag; + int i; + + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 1, objv, "FILENAME FLAGS VFS"); + return TCL_ERROR; + } + zFilename = Tcl_GetString(objv[1]); + zVfs = Tcl_GetString(objv[3]); + if( zVfs[0]==0x00 ) zVfs = 0; + + rc = Tcl_ListObjGetElements(interp, objv[2], &nFlag, &apFlag); + if( rc!=TCL_OK ) return rc; + for(i=0; ipFd = pFd; /* Evaluate the Tcl script: ** - ** SCRIPT xOpen FILENAME + ** SCRIPT xOpen FILENAME KEY-VALUE-ARGS ** ** If the script returns an SQLite error code other than SQLITE_OK, an ** error is returned to the caller. If it returns SQLITE_OK, the new ** connection is named "anon". Otherwise, the value returned by the ** script is used as the connection name. */ Tcl_ResetResult(p->interp); if( p->pScript && p->mask&TESTVFS_OPEN_MASK ){ - tvfsExecTcl(p, "xOpen", Tcl_NewStringObj(pFd->zFilename, -1), 0, 0); + Tcl_Obj *pArg = Tcl_NewObj(); + Tcl_IncrRefCount(pArg); + if( flags&SQLITE_OPEN_MAIN_DB ){ + const char *z = &zName[strlen(zName)+1]; + while( *z ){ + Tcl_ListObjAppendElement(0, pArg, Tcl_NewStringObj(z, -1)); + z += strlen(z) + 1; + Tcl_ListObjAppendElement(0, pArg, Tcl_NewStringObj(z, -1)); + z += strlen(z) + 1; + } + } + tvfsExecTcl(p, "xOpen", Tcl_NewStringObj(pFd->zFilename, -1), pArg, 0); + Tcl_DecrRefCount(pArg); if( tvfsResultCode(p, &rc) ){ if( rc!=SQLITE_OK ) return rc; }else{ pId = Tcl_GetObjResult(p->interp); } @@ -661,10 +674,18 @@ sqlite3_vfs *pVfs, const char *zPath, int nOut, char *zOut ){ + Testvfs *p = (Testvfs *)pVfs->pAppData; + if( p->pScript && p->mask&TESTVFS_FULLPATHNAME_MASK ){ + int rc; + tvfsExecTcl(p, "xFullPathname", Tcl_NewStringObj(zPath, -1), 0, 0); + if( tvfsResultCode(p, &rc) ){ + if( rc!=SQLITE_OK ) return rc; + } + } return sqlite3OsFullPathname(PARENTVFS(pVfs), zPath, nOut, zOut); } #ifndef SQLITE_OMIT_LOAD_EXTENSION /* @@ -1026,22 +1047,23 @@ case CMD_FILTER: { static struct VfsMethod { char *zName; int mask; } vfsmethod [] = { - { "xShmOpen", TESTVFS_SHMOPEN_MASK }, - { "xShmLock", TESTVFS_SHMLOCK_MASK }, - { "xShmBarrier", TESTVFS_SHMBARRIER_MASK }, - { "xShmUnmap", TESTVFS_SHMCLOSE_MASK }, - { "xShmMap", TESTVFS_SHMMAP_MASK }, - { "xSync", TESTVFS_SYNC_MASK }, - { "xDelete", TESTVFS_DELETE_MASK }, - { "xWrite", TESTVFS_WRITE_MASK }, - { "xTruncate", TESTVFS_TRUNCATE_MASK }, - { "xOpen", TESTVFS_OPEN_MASK }, - { "xClose", TESTVFS_CLOSE_MASK }, - { "xAccess", TESTVFS_ACCESS_MASK }, + { "xShmOpen", TESTVFS_SHMOPEN_MASK }, + { "xShmLock", TESTVFS_SHMLOCK_MASK }, + { "xShmBarrier", TESTVFS_SHMBARRIER_MASK }, + { "xShmUnmap", TESTVFS_SHMCLOSE_MASK }, + { "xShmMap", TESTVFS_SHMMAP_MASK }, + { "xSync", TESTVFS_SYNC_MASK }, + { "xDelete", TESTVFS_DELETE_MASK }, + { "xWrite", TESTVFS_WRITE_MASK }, + { "xTruncate", TESTVFS_TRUNCATE_MASK }, + { "xOpen", TESTVFS_OPEN_MASK }, + { "xClose", TESTVFS_CLOSE_MASK }, + { "xAccess", TESTVFS_ACCESS_MASK }, + { "xFullPathname", TESTVFS_FULLPATHNAME_MASK }, }; Tcl_Obj **apElem = 0; int nElem = 0; int i; int mask = 0; Index: src/util.c ================================================================== --- src/util.c +++ src/util.c @@ -981,27 +981,25 @@ p[3] = (u8)v; } -#if !defined(SQLITE_OMIT_BLOB_LITERAL) || defined(SQLITE_HAS_CODEC) /* ** Translate a single byte of Hex into an integer. ** This routine only works if h really is a valid hexadecimal ** character: 0..9a..fA..F */ -static u8 hexToInt(int h){ +u8 sqlite3HexToInt(int h){ assert( (h>='0' && h<='9') || (h>='a' && h<='f') || (h>='A' && h<='F') ); #ifdef SQLITE_ASCII h += 9*(1&(h>>6)); #endif #ifdef SQLITE_EBCDIC h += 9*(1&~(h>>4)); #endif return (u8)(h & 0xf); } -#endif /* !SQLITE_OMIT_BLOB_LITERAL || SQLITE_HAS_CODEC */ #if !defined(SQLITE_OMIT_BLOB_LITERAL) || defined(SQLITE_HAS_CODEC) /* ** Convert a BLOB literal of the form "x'hhhhhh'" into its binary ** value. Return a pointer to its binary value. Space to hold the @@ -1014,11 +1012,11 @@ zBlob = (char *)sqlite3DbMallocRaw(db, n/2 + 1); n--; if( zBlob ){ for(i=0; ip1>=0 ); pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, 1); if( pCx==0 ) goto no_mem; pCx->nullRow = 1; - rc = sqlite3BtreeOpen(0, db, &pCx->pBt, + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBt, BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, vfsFlags); if( rc==SQLITE_OK ){ rc = sqlite3BtreeBeginTrans(pCx->pBt, 1); } if( rc==SQLITE_OK ){ Index: src/vtab.c ================================================================== --- src/vtab.c +++ src/vtab.c @@ -872,11 +872,11 @@ assert( op==SAVEPOINT_RELEASE||op==SAVEPOINT_ROLLBACK||op==SAVEPOINT_BEGIN ); if( db->aVTrans ){ int i; for(i=0; rc==SQLITE_OK && inVTrans; i++){ const sqlite3_module *pMod = db->aVTrans[i]->pMod->pModule; - if( pMod->iVersion>=1 ){ + if( pMod->iVersion>=2 ){ int (*xMethod)(sqlite3_vtab *, int); switch( op ){ case SAVEPOINT_BEGIN: xMethod = pMod->xSavepoint; break; @@ -999,11 +999,11 @@ ** The results of this routine are undefined unless it is called from ** within an xUpdate method. */ int sqlite3_vtab_on_conflict(sqlite3 *db){ static const unsigned char aMap[] = { - SQLITE_ROLLBACK, SQLITE_IGNORE, SQLITE_ABORT, SQLITE_FAIL, SQLITE_REPLACE + SQLITE_ROLLBACK, SQLITE_ABORT, SQLITE_FAIL, SQLITE_IGNORE, SQLITE_REPLACE }; assert( OE_Rollback==1 && OE_Abort==2 && OE_Fail==3 ); assert( OE_Ignore==4 && OE_Replace==5 ); assert( db->vtabOnConflict>=1 && db->vtabOnConflict<=5 ); return (int)aMap[db->vtabOnConflict-1]; @@ -1025,11 +1025,11 @@ case SQLITE_VTAB_CONSTRAINT_SUPPORT: { VtabCtx *p = db->pVtabCtx; if( !p ){ rc = SQLITE_MISUSE_BKPT; }else{ - assert( (p->pTab->tabFlags & TF_Virtual)!=0 ); + assert( p->pTab==0 || (p->pTab->tabFlags & TF_Virtual)!=0 ); p->pVTable->bConstraint = (u8)va_arg(ap, int); } break; } default: Index: src/wal.c ================================================================== --- src/wal.c +++ src/wal.c @@ -404,10 +404,18 @@ ) /* ** An open write-ahead log file is represented by an instance of the ** following object. +** +** The readOnlyShm variable is normally set to 0. If it is set to 1, then +** the connection to shared-memory is read-only. This means it cannot +** be written at all (even when read-locking the database). If it is set +** to 2, then the shared-memory region is not yet open, but a read-only +** connection is acceptable. In this case when the shared-memory is opened +** (see function walIndexPage()), readOnlyShm is set to either 0 or 1 as +** appropriate. */ struct Wal { sqlite3_vfs *pVfs; /* The VFS used to create pDbFd */ sqlite3_file *pDbFd; /* File handle for the database file */ sqlite3_file *pWalFd; /* File handle for WAL file */ @@ -418,10 +426,11 @@ i16 readLock; /* Which read lock is being held. -1 for none */ u8 exclusiveMode; /* Non-zero if connection is in exclusive mode */ u8 writeLock; /* True if in a write transaction */ u8 ckptLock; /* True if holding a checkpoint lock */ u8 readOnly; /* True if the WAL file is open read-only */ + u8 readOnlyShm; /* True if the SHM file is open read-only */ WalIndexHdr hdr; /* Wal-index header for current transaction */ const char *zWalName; /* Name of WAL file */ u32 nCkpt; /* Checkpoint sequence counter in the wal-header */ #ifdef SQLITE_DEBUG u8 lockError; /* True if a locking error has occurred */ @@ -526,15 +535,27 @@ if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM; }else{ rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] ); + if( rc==SQLITE_CANTOPEN && pWal->readOnlyShm>1 ){ + assert( iPage==0 ); + sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_READONLY_SHM, (void*)1); + rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, + pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] + ); + if( rc==SQLITE_OK ){ + pWal->readOnly = pWal->readOnlyShm = 1; + } + sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_READONLY_SHM, (void*)0); + } } } - *ppPage = pWal->apWiData[iPage]; + assert( iPage==0 || *ppPage || rc!=SQLITE_OK ); + if( pWal->readOnlyShm>1 ) pWal->readOnlyShm = 0; return rc; } /* ** Return a pointer to the WalCkptInfo structure in the wal-index. @@ -770,19 +791,21 @@ SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED); WALTRACE(("WAL%p: release SHARED-%s\n", pWal, walLockName(lockIdx))); } static int walLockExclusive(Wal *pWal, int lockIdx, int n){ int rc; + assert( pWal->readOnlyShm==0 ); if( pWal->exclusiveMode ) return SQLITE_OK; rc = sqlite3OsShmLock(pWal->pDbFd, lockIdx, n, SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE); WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal, walLockName(lockIdx), n, rc ? "failed" : "ok")); VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && rc!=SQLITE_BUSY); ) return rc; } static void walUnlockExclusive(Wal *pWal, int lockIdx, int n){ + assert( pWal->readOnlyShm==0 ); if( pWal->exclusiveMode ) return; (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, n, SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE); WALTRACE(("WAL%p: release EXCLUSIVE-%s cnt=%d\n", pWal, walLockName(lockIdx), n)); @@ -1054,10 +1077,11 @@ */ assert( pWal->ckptLock==1 || pWal->ckptLock==0 ); assert( WAL_ALL_BUT_WRITE==WAL_WRITE_LOCK+1 ); assert( WAL_CKPT_LOCK==WAL_ALL_BUT_WRITE ); assert( pWal->writeLock ); + assert( pWal->readOnlyShm==0 ); iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock; nLock = SQLITE_SHM_NLOCK - iLock; rc = walLockExclusive(pWal, iLock, nLock); if( rc ){ return rc; @@ -1893,37 +1917,45 @@ rc = walIndexPage(pWal, 0, &page0); if( rc!=SQLITE_OK ){ return rc; }; assert( page0 || pWal->writeLock==0 ); + assert( pWal->readOnlyShm==0 || pWal->readOnlyShm==1 ); /* If the first page of the wal-index has been mapped, try to read the ** wal-index header immediately, without holding any lock. This usually ** works, but may fail if the wal-index header is corrupt or currently ** being modified by another thread or process. */ badHdr = (page0 ? walIndexTryHdr(pWal, pChanged) : 1); /* If the first attempt failed, it might have been due to a race - ** with a writer. So get a WRITE lock and try again. + ** with a writer. So lock the WAL_WRITE_LOCK byte and try again. */ assert( badHdr==0 || pWal->writeLock==0 ); - if( badHdr && SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){ - pWal->writeLock = 1; - if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){ - badHdr = walIndexTryHdr(pWal, pChanged); - if( badHdr ){ - /* If the wal-index header is still malformed even while holding - ** a WRITE lock, it can only mean that the header is corrupted and - ** needs to be reconstructed. So run recovery to do exactly that. - */ - rc = walIndexRecover(pWal); - *pChanged = 1; - } - } - pWal->writeLock = 0; - walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); + if( badHdr ){ + if( pWal->readOnlyShm ){ + if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){ + walUnlockShared(pWal, WAL_WRITE_LOCK); + rc = SQLITE_READONLY_RECOVERY; + } + }else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){ + pWal->writeLock = 1; + if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){ + badHdr = walIndexTryHdr(pWal, pChanged); + if( badHdr ){ + /* If the wal-index header is still malformed even while holding + ** a WRITE lock, it can only mean that the header is corrupted and + ** needs to be reconstructed. So run recovery to do exactly that. + */ + rc = walIndexRecover(pWal); + *pChanged = 1; + } + } + pWal->writeLock = 0; + walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); + } } /* If the header is read successfully, check the version number to make ** sure the wal-index was not constructed with some future format that ** this version of SQLite cannot understand. @@ -2106,11 +2138,11 @@ mxI = i; } } /* There was once an "if" here. The extra "{" is to preserve indentation. */ { - if( mxReadMark < pWal->hdr.mxFrame || mxI==0 ){ + if( pWal->readOnlyShm==0 && (mxReadMark < pWal->hdr.mxFrame || mxI==0) ){ for(i=1; iaReadMark[i] = pWal->hdr.mxFrame; mxI = i; @@ -2121,11 +2153,12 @@ } } } if( mxI==0 ){ assert( rc==SQLITE_BUSY ); - return WAL_RETRY; + assert( rc==SQLITE_BUSY || (pWal->readOnlyShm && rc==SQLITE_OK) ); + return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK; } rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); if( rc ){ return rc==SQLITE_BUSY ? WAL_RETRY : rc; @@ -2176,17 +2209,22 @@ ** If the database contents have changes since the previous read ** transaction, then *pChanged is set to 1 before returning. The ** Pager layer will use this to know that is cache is stale and ** needs to be flushed. */ -int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ +int sqlite3WalBeginReadTransaction(Wal *pWal, int readOnlyShm, int *pChanged){ int rc; /* Return code */ int cnt = 0; /* Number of TryBeginRead attempts */ + if( pWal->nWiData==0 || pWal->apWiData[0]==0 ){ + assert( readOnlyShm==0 || readOnlyShm==1 ); + pWal->readOnlyShm = readOnlyShm*2; + } do{ rc = walTryBeginRead(pWal, pChanged, 0, ++cnt); }while( rc==WAL_RETRY ); + assert( rc || pWal->readOnlyShm==0 || (readOnlyShm && pWal->readOnlyShm==1) ); testcase( (rc&0xff)==SQLITE_BUSY ); testcase( (rc&0xff)==SQLITE_IOERR ); testcase( rc==SQLITE_PROTOCOL ); testcase( rc==SQLITE_OK ); return rc; @@ -2357,10 +2395,11 @@ assert( pWal->readLock>=0 ); if( pWal->readOnly ){ return SQLITE_READONLY; } + assert( pWal->readOnlyShm==0 ); /* Only one writer allowed at a time. Get the write lock. Return ** SQLITE_BUSY if unable. */ rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); @@ -2746,10 +2785,14 @@ assert( pWal->ckptLock==0 ); assert( pWal->writeLock==0 ); WALTRACE(("WAL%p: checkpoint begins\n", pWal)); + if( pWal->readOnlyShm ){ + return SQLITE_READONLY; + } + rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); if( rc ){ /* Usually this is SQLITE_BUSY meaning that another thread or process ** is already running a checkpoint, or maybe a recovery. But it might ** also be SQLITE_IOERR. */ Index: src/wal.h ================================================================== --- src/wal.h +++ src/wal.h @@ -20,11 +20,11 @@ #include "sqliteInt.h" #ifdef SQLITE_OMIT_WAL # define sqlite3WalOpen(x,y,z) 0 # define sqlite3WalClose(w,x,y,z) 0 -# define sqlite3WalBeginReadTransaction(y,z) 0 +# define sqlite3WalBeginReadTransaction(x,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 @@ -54,11 +54,11 @@ ** at an instant in time. sqlite3WalOpenSnapshot gets a read lock and ** preserves the current state even if the other threads or processes ** write to or checkpoint the WAL. sqlite3WalCloseSnapshot() closes the ** transaction and releases the lock. */ -int sqlite3WalBeginReadTransaction(Wal *pWal, int *); +int sqlite3WalBeginReadTransaction(Wal *pWal, int, 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); Index: test/alter.test ================================================================== --- test/alter.test +++ test/alter.test @@ -648,11 +648,11 @@ } {4 5} # Ticket #1665: Make sure ALTER TABLE ADD COLUMN works on a table # that includes a COLLATE clause. # -do_test alter-7.1 { +do_realnum_test alter-7.1 { execsql { CREATE TABLE t1(a TEXT COLLATE BINARY); ALTER TABLE t1 ADD COLUMN b INTEGER COLLATE NOCASE; INSERT INTO t1 VALUES(1,'-2'); INSERT INTO t1 VALUES(5.4e-08,'5.4e-08'); Index: test/cast.test ================================================================== --- test/cast.test +++ test/cast.test @@ -232,11 +232,11 @@ execsql {SELECT CAST(9223372036854774800 AS integer)} } 9223372036854774800 do_test cast-3.2 { execsql {SELECT CAST(9223372036854774800 AS numeric)} } 9223372036854774800 -do_test cast-3.3 { +do_realnum_test cast-3.3 { execsql {SELECT CAST(9223372036854774800 AS real)} } 9.22337203685477e+18 do_test cast-3.4 { execsql {SELECT CAST(CAST(9223372036854774800 AS real) AS integer)} } 9223372036854774784 @@ -244,11 +244,11 @@ execsql {SELECT CAST(-9223372036854774800 AS integer)} } -9223372036854774800 do_test cast-3.6 { execsql {SELECT CAST(-9223372036854774800 AS numeric)} } -9223372036854774800 -do_test cast-3.7 { +do_realnum_test cast-3.7 { execsql {SELECT CAST(-9223372036854774800 AS real)} } -9.22337203685477e+18 do_test cast-3.8 { execsql {SELECT CAST(CAST(-9223372036854774800 AS real) AS integer)} } -9223372036854774784 @@ -256,11 +256,11 @@ execsql {SELECT CAST('9223372036854774800' AS integer)} } 9223372036854774800 do_test cast-3.12 { execsql {SELECT CAST('9223372036854774800' AS numeric)} } 9223372036854774800 -do_test cast-3.13 { +do_realnum_test cast-3.13 { execsql {SELECT CAST('9223372036854774800' AS real)} } 9.22337203685477e+18 ifcapable long_double { do_test cast-3.14 { execsql {SELECT CAST(CAST('9223372036854774800' AS real) AS integer)} @@ -270,11 +270,11 @@ execsql {SELECT CAST('-9223372036854774800' AS integer)} } -9223372036854774800 do_test cast-3.16 { execsql {SELECT CAST('-9223372036854774800' AS numeric)} } -9223372036854774800 -do_test cast-3.17 { +do_realnum_test cast-3.17 { execsql {SELECT CAST('-9223372036854774800' AS real)} } -9.22337203685477e+18 ifcapable long_double { do_test cast-3.18 { execsql {SELECT CAST(CAST('-9223372036854774800' AS real) AS integer)} @@ -285,11 +285,11 @@ execsql {SELECT CAST(x'39323233333732303336383534373734383030' AS integer)} } 9223372036854774800 do_test cast-3.22 { execsql {SELECT CAST(x'39323233333732303336383534373734383030' AS numeric)} } 9223372036854774800 - do_test cast-3.23 { + do_realnum_test cast-3.23 { execsql {SELECT CAST(x'39323233333732303336383534373734383030' AS real)} } 9.22337203685477e+18 ifcapable long_double { do_test cast-3.24 { execsql { ADDED test/e_uri.test Index: test/e_uri.test ================================================================== --- /dev/null +++ test/e_uri.test @@ -0,0 +1,456 @@ +# 2011 May 06 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix e_uri + +db close + +proc parse_uri {uri} { + testvfs tvfs2 + testvfs tvfs + tvfs filter xOpen + tvfs script parse_uri_open_cb + + set ::uri_open [list] + set DB [sqlite3_open_v2 $uri { + SQLITE_OPEN_READWRITE SQLITE_OPEN_CREATE SQLITE_OPEN_WAL + } tvfs] + sqlite3_close $DB + tvfs delete + tvfs2 delete + + set ::uri_open +} +proc parse_uri_open_cb {method file arglist} { + set ::uri_open [list $file $arglist] +} + +proc open_uri_error {uri} { + set flags {SQLITE_OPEN_READWRITE SQLITE_OPEN_CREATE SQLITE_OPEN_WAL} + set DB [sqlite3_open_v2 $uri $flags ""] + set e [sqlite3_errmsg $DB] + sqlite3_close $DB + set e +} + +# EVIDENCE-OF: R-35840-33204 If URI filename interpretation is enabled, +# and the filename argument begins with "file:", then the filename is +# interpreted as a URI. +# +# EVIDENCE-OF: R-00067-59538 URI filename interpretation is enabled if +# the SQLITE_OPEN_URI flag is is set in the fourth argument to +# sqlite3_open_v2(), or if it has been enabled globally using the +# SQLITE_CONFIG_URI option with the sqlite3_config() method. +# +if {$tcl_platform(platform) == "unix"} { + set flags [list SQLITE_OPEN_READWRITE SQLITE_OPEN_CREATE] + + # Tests with SQLITE_CONFIG_URI configured to false. URI intepretation is + # only enabled if the SQLITE_OPEN_URI flag is specified. + sqlite3_shutdown + sqlite3_config_uri 0 + do_test 1.1 { + forcedelete file:test.db test.db + set DB [sqlite3_open_v2 file:test.db [concat $flags SQLITE_OPEN_URI] ""] + list [file exists file:test.db] [file exists test.db] + } {0 1} + do_test 1.2 { + forcedelete file:test.db2 test.db2 + set STMT [sqlite3_prepare $DB "ATTACH 'file:test.db2' AS aux" -1 dummy] + sqlite3_step $STMT + sqlite3_finalize $STMT + list [file exists file:test.db2] [file exists test.db2] + } {0 1} + sqlite3_close $DB + do_test 1.3 { + forcedelete file:test.db test.db + set DB [sqlite3_open_v2 file:test.db [concat $flags] ""] + list [file exists file:test.db] [file exists test.db] + } {1 0} + do_test 1.4 { + forcedelete file:test.db2 test.db2 + set STMT [sqlite3_prepare $DB "ATTACH 'file:test.db2' AS aux" -1 dummy] + sqlite3_step $STMT + sqlite3_finalize $STMT + list [file exists file:test.db2] [file exists test.db2] + } {1 0} + sqlite3_close $DB + + # Tests with SQLITE_CONFIG_URI configured to true. URI intepretation is + # enabled with or without SQLITE_OPEN_URI. + # + sqlite3_shutdown + sqlite3_config_uri 1 + do_test 1.5 { + forcedelete file:test.db test.db + set DB [sqlite3_open_v2 file:test.db [concat $flags SQLITE_OPEN_URI] ""] + list [file exists file:test.db] [file exists test.db] + } {0 1} + do_test 1.6 { + forcedelete file:test.db2 test.db2 + set STMT [sqlite3_prepare $DB "ATTACH 'file:test.db2' AS aux" -1 dummy] + sqlite3_step $STMT + sqlite3_finalize $STMT + list [file exists file:test.db2] [file exists test.db2] + } {0 1} + sqlite3_close $DB + do_test 1.7 { + forcedelete file:test.db test.db + set DB [sqlite3_open_v2 file:test.db [concat $flags] ""] + list [file exists file:test.db] [file exists test.db] + } {0 1} + do_test 1.8 { + forcedelete file:test.db2 test.db2 + set STMT [sqlite3_prepare $DB "ATTACH 'file:test.db2' AS aux" -1 dummy] + sqlite3_step $STMT + sqlite3_finalize $STMT + list [file exists file:test.db2] [file exists test.db2] + } {0 1} + sqlite3_close $DB +} + +# EVIDENCE-OF: R-17482-00398 If the authority is not an empty string or +# "localhost", an error is returned to the caller. +# +if {$tcl_platform(platform) == "unix"} { + set flags [list SQLITE_OPEN_READWRITE SQLITE_OPEN_CREATE SQLITE_OPEN_URI] + foreach {tn uri error} " + 1 {file://localhost[pwd]/test.db} {not an error} + 2 {file://[pwd]/test.db} {not an error} + 3 {file://x[pwd]/test.db} {invalid uri authority: x} + 4 {file://invalid[pwd]/test.db} {invalid uri authority: invalid} + " { + do_test 2.$tn { + set DB [sqlite3_open_v2 $uri $flags ""] + set e [sqlite3_errmsg $DB] + sqlite3_close $DB + set e + } $error + } +} + +# EVIDENCE-OF: R-43804-65312 The 'fragment' component of a URI, if +# present, is always ignored. +# +# It is difficult to test that something is ignore correctly. So these tests +# just show that adding a fragment does not interfere with the pathname or +# parameters passed through to the VFS xOpen() methods. +# +if {$tcl_platform(platform) == "unix"} { + foreach {tn uri parse} " + 1 {file:test.db#abc} {[pwd]/test.db {}} + 2 {file:test.db?a=b#abc} {[pwd]/test.db {a b}} + 3 {file:test.db?a=b#?c=d} {[pwd]/test.db {a b}} + " { + do_test 3.$tn { parse_uri $uri } $parse + } +} + +# EVIDENCE-OF: R-00273-20588 SQLite uses the 'path' component of the URI +# as the path to the database file to open. +# +# EVIDENCE-OF: R-28659-11035 If the path begins with a '/' character, +# then it is interpreted as an absolute path. +# +# EVIDENCE-OF: R-39349-47203 If it does not begin with a '/', it is +# interpreted as a relative path. +# +if {$tcl_platform(platform) == "unix"} { + foreach {tn uri parse} " + 1 {file:test.db} {[pwd]/test.db {}} + 2 {file:/test.db} {/test.db {}} + 3 {file:///test.db} {/test.db {}} + 4 {file://localhost/test.db} {/test.db {}} + 5 {file:/a/b/c/test.db} {/a/b/c/test.db {}} + " { + do_test 4.$tn { parse_uri $uri } $parse + } +} + +# EVIDENCE-OF: R-01612-30877 The "vfs" parameter may be used to specify +# the name of a VFS object that provides the operating system interface +# that should be used to access the database file on disk. +# +# The above is tested by cases 1.* below. +# +# EVIDENCE-OF: R-52293-58497 If this option is set to an empty string +# the default VFS object is used. +# +# The above is tested by cases 2.* below. +# +# EVIDENCE-OF: R-31855-18665 If sqlite3_open_v2() is used and the vfs +# option is present, then the VFS specified by the option takes +# precedence over the value passed as the fourth parameter to +# sqlite3_open_v2(). +# +# The above is tested by cases 3.* below. +# +proc vfs_open_cb {name args} { + set ::vfs $name +} +foreach {name default} {vfs1 0 vfs2 0 vfs3 1} { + testvfs $name -default $default + $name filter xOpen + $name script [list vfs_open_cb $name] +} +foreach {tn uri defvfs vfs} { + 1.1 "file:test.db?vfs=vfs1" "" vfs1 + 1.2 "file:test.db?vfs=vfs2" "" vfs2 + + 2.1 "file:test.db" vfs1 vfs1 + 2.2 "file:test.db?vfs=" vfs1 vfs3 + + 3.1 "file:test.db?vfs=vfs1" vfs2 vfs1 + 3.2 "file:test.db?vfs=vfs2" vfs1 vfs2 + 3.3 "file:test.db?xvfs=vfs1" vfs2 vfs2 + 3.4 "file:test.db?xvfs=vfs2" vfs1 vfs1 +} { + do_test 5.$tn { + set flags [list SQLITE_OPEN_READWRITE SQLITE_OPEN_CREATE SQLITE_OPEN_URI] + sqlite3_close [ + sqlite3_open_v2 $uri $flags $defvfs + ] + set ::vfs + } $vfs +} +vfs1 delete +vfs2 delete +vfs3 delete + +# EVIDENCE-OF: R-48365-36308 Specifying an unknown VFS is an error. +# +set flags [list SQLITE_OPEN_READWRITE SQLITE_OPEN_CREATE SQLITE_OPEN_URI] +do_test 6.1 { + set DB [sqlite3_open_v2 file:test.db?vfs=nosuchvfs $flags ""] + set errmsg [sqlite3_errmsg $DB] + sqlite3_close $DB + set errmsg +} {no such vfs: nosuchvfs} + + +# EVIDENCE-OF: R-60479-64270 The mode parameter may be set to either +# "ro", "rw" or "rwc". Attempting to set it to any other value is an +# error +# +sqlite3 db test.db +db close +foreach {tn uri error} " + 1 {file:test.db?mode=ro} {not an error} + 2 {file:test.db?mode=rw} {not an error} + 3 {file:test.db?mode=rwc} {not an error} + 4 {file:test.db?mode=Ro} {no such access mode: Ro} + 5 {file:test.db?mode=Rw} {no such access mode: Rw} + 6 {file:test.db?mode=Rwc} {no such access mode: Rwc} +" { + do_test 7.$tn { open_uri_error $uri } $error +} + + +# EVIDENCE-OF: R-09651-31805 If "ro" is specified, then the database is +# opened for read-only access, just as if the SQLITE_OPEN_READONLY flag +# had been set in the third argument to sqlite3_prepare_v2(). +# +# EVIDENCE-OF: R-40137-26050 If the mode option is set to "rw", then the +# database is opened for read-write (but not create) access, as if +# SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had been set. +# +# EVIDENCE-OF: R-26845-32976 Value "rwc" is equivalent to setting both +# SQLITE_OPEN_READWRITE and SQLITE_OPEN_CREATE. +# +sqlite3_shutdown +sqlite3_config_uri 1 +foreach {tn uri read write create} { + 1 {file:test.db?mode=ro} 1 0 0 + 2 {file:test.db?mode=rw} 1 1 0 + 3 {file:test.db?mode=rwc} 1 1 1 +} { + set RES(c,0) {1 {unable to open database file}} + set RES(c,1) {0 {}} + set RES(w,0) {1 {attempt to write a readonly database}} + set RES(w,1) {0 {}} + set RES(r,0) {1 {this never happens}} + set RES(r,1) {0 {a b}} + + # Test CREATE access: + forcedelete test.db + do_test 8.$tn.c { list [catch { sqlite3 db $uri } msg] $msg } $RES(c,$create) + catch { db close } + + sqlite3 db test.db + db eval { CREATE TABLE t1(a, b) ; INSERT INTO t1 VALUES('a', 'b') ;} + db close + + # Test READ access: + do_test 8.$tn.r { + sqlite3 db $uri + catchsql { SELECT * FROM t1 } + } $RES(r,$read) + + # Test WRITE access: + do_test 8.$tn.w { + sqlite3 db $uri + catchsql { INSERT INTO t1 VALUES(1, 2) } + } $RES(w,$write) + + catch {db close} +} + +# EVIDENCE-OF: R-56032-32287 If sqlite3_open_v2() is used, it is an +# error to specify a value for the mode parameter that is less +# restrictive than that specified by the flags passed as the third +# parameter. +# +forcedelete test.db +sqlite3 db test.db +db close +foreach {tn uri flags error} { + 1 {file:test.db?mode=ro} ro {not an error} + 2 {file:test.db?mode=ro} rw {not an error} + 3 {file:test.db?mode=ro} rwc {not an error} + + 4 {file:test.db?mode=rw} ro {access mode not allowed: rw} + 5 {file:test.db?mode=rw} rw {not an error} + 6 {file:test.db?mode=rw} rwc {not an error} + + 7 {file:test.db?mode=rwc} ro {access mode not allowed: rwc} + 8 {file:test.db?mode=rwc} rw {access mode not allowed: rwc} + 9 {file:test.db?mode=rwc} rwc {not an error} +} { + set f(ro) [list SQLITE_OPEN_READONLY SQLITE_OPEN_URI] + set f(rw) [list SQLITE_OPEN_READWRITE SQLITE_OPEN_URI] + set f(rwc) [list SQLITE_OPEN_READWRITE SQLITE_OPEN_CREATE SQLITE_OPEN_URI] + + set DB [sqlite3_open_v2 $uri $f($flags) ""] + set e [sqlite3_errmsg $DB] + sqlite3_close $DB + + do_test 9.$tn { set e } $error +} + +# EVIDENCE-OF: R-23182-54295 The cache parameter may be set to either +# "shared" or "private". +sqlite3 db test.db +db close +foreach {tn uri error} " + 1 {file:test.db?cache=private} {not an error} + 2 {file:test.db?cache=shared} {not an error} + 3 {file:test.db?cache=yes} {no such cache mode: yes} + 4 {file:test.db?cache=} {no such cache mode: } +" { + do_test 10.$tn { open_uri_error $uri } $error +} + +# EVIDENCE-OF: R-23027-03515 Setting it to "shared" is equivalent to +# setting the SQLITE_OPEN_SHAREDCACHE bit in the flags argument passed +# to sqlite3_open_v2(). +# +# EVIDENCE-OF: R-49793-28525 Setting the cache parameter to "private" is +# equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit. +# +# EVIDENCE-OF: R-19510-48080 If sqlite3_open_v2() is used and the +# "cache" parameter is present in a URI filename, its value overrides +# any behaviour requested by setting SQLITE_OPEN_PRIVATECACHE or +# SQLITE_OPEN_SHAREDCACHE flag. +# +set orig [sqlite3_enable_shared_cache] +foreach {tn uri flags shared_default isshared} { + 1.1 "file:test.db" "" 0 0 + 1.2 "file:test.db" "" 1 1 + 1.3 "file:test.db" private 0 0 + 1.4 "file:test.db" private 1 0 + 1.5 "file:test.db" shared 0 1 + 1.6 "file:test.db" shared 1 1 + + 2.1 "file:test.db?cache=private" "" 0 0 + 2.2 "file:test.db?cache=private" "" 1 0 + 2.3 "file:test.db?cache=private" private 0 0 + 2.4 "file:test.db?cache=private" private 1 0 + 2.5 "file:test.db?cache=private" shared 0 0 + 2.6 "file:test.db?cache=private" shared 1 0 + + 3.1 "file:test.db?cache=shared" "" 0 1 + 3.2 "file:test.db?cache=shared" "" 1 1 + 3.3 "file:test.db?cache=shared" private 0 1 + 3.4 "file:test.db?cache=shared" private 1 1 + 3.5 "file:test.db?cache=shared" shared 0 1 + 3.6 "file:test.db?cache=shared" shared 1 1 +} { + forcedelete test.db + sqlite3_enable_shared_cache 1 + sqlite3 db test.db + sqlite3_enable_shared_cache 0 + + db eval { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES('ok'); + } + + unset -nocomplain f + set f() {SQLITE_OPEN_READWRITE SQLITE_OPEN_CREATE SQLITE_OPEN_URI} + set f(shared) [concat $f() SQLITE_OPEN_SHAREDCACHE] + set f(private) [concat $f() SQLITE_OPEN_PRIVATECACHE] + + sqlite3_enable_shared_cache $shared_default + set DB [sqlite3_open_v2 $uri $f($flags) ""] + + set STMT [sqlite3_prepare $DB "SELECT * FROM t1" -1 dummy] + + db eval { + BEGIN; + INSERT INTO t1 VALUES('ko'); + } + + sqlite3_step $STMT + sqlite3_finalize $STMT + + set RES(0) {not an error} + set RES(1) {database table is locked: t1} + + do_test 11.$tn { sqlite3_errmsg $DB } $RES($isshared) + + sqlite3_close $DB + db close +} +sqlite3_enable_shared_cache $orig + +# EVIDENCE-OF: R-63472-46769 Specifying an unknown parameter in the +# query component of a URI is not an error. +# +do_test 12.1 { + parse_uri file://localhost/test.db?an=unknown¶meter=is&ok= +} {/test.db {an unknown parameter is ok {}}} +do_test 12.2 { + parse_uri file://localhost/test.db?an&unknown¶meter&is&ok +} {/test.db {an {} unknown {} parameter {} is {} ok {}}} + +# EVIDENCE-OF: R-27458-04043 URI hexadecimal escape sequences (%HH) are +# supported within the path and query components of a URI. +# +# EVIDENCE-OF: R-52765-50368 Before the path or query components of a +# URI filename are interpreted, they are encoded using UTF-8 and all +# hexadecimal escape sequences replaced by a single byte containing the +# corresponding octet. +# +# The second of the two statements above is tested by creating a +# multi-byte utf-8 character using a sequence of %HH escapes. +# +foreach {tn uri parse} " + 1 {file:/test.%64%62} {/test.db {}} + 2 {file:/test.db?%68%65%6c%6c%6f=%77%6f%72%6c%64} {/test.db {hello world}} + 3 {file:/%C3%BF.db} {/\xFF.db {}} +" { + do_test 13.$tn { parse_uri $uri } $parse +} + +finish_test Index: test/expr.test ================================================================== --- test/expr.test +++ test/expr.test @@ -30,10 +30,15 @@ proc test_expr {name settings expr result} { do_test $name [format { execsql {BEGIN; UPDATE test1 SET %s; SELECT %s FROM test1; ROLLBACK;} } $settings $expr] $result } +proc test_realnum_expr {name settings expr result} { + do_realnum_test $name [format { + execsql {BEGIN; UPDATE test1 SET %s; SELECT %s FROM test1; ROLLBACK;} + } $settings $expr] $result +} test_expr expr-1.1 {i1=10, i2=20} {i1+i2} 30 test_expr expr-1.2 {i1=10, i2=20} {i1-i2} -10 test_expr expr-1.3 {i1=10, i2=20} {i1*i2} 200 test_expr expr-1.4 {i1=10, i2=20} {i1/i2} 0 @@ -162,11 +167,11 @@ test_expr expr-1.104 {i1=0} {(-9223372036854775808.0 % -1)} 0.0 test_expr expr-1.105 {i1=0} {(-9223372036854775808.0 / -1)>1} 1 } if {[working_64bit_int]} { - test_expr expr-1.106 {i1=0} {-9223372036854775808/-1} 9.22337203685478e+18 + test_realnum_expr expr-1.106 {i1=0} {-9223372036854775808/-1} 9.22337203685478e+18 } test_expr expr-1.107 {i1=0} {-9223372036854775808%-1} 0 test_expr expr-1.108 {i1=0} {1%0} {{}} test_expr expr-1.109 {i1=0} {1/0} {{}} @@ -201,104 +206,104 @@ {CASE WHEN i1 IS NOT i2 THEN 'yes' ELSE 'no' END} no ifcapable floatingpoint {if {[working_64bit_int]} { test_expr expr-1.200\ {i1=9223372036854775806, i2=1} {i1+i2} 9223372036854775807 - test_expr expr-1.201\ - {i1=9223372036854775806, i2=2} {i1+i2} 9.22337203685478e+18 - test_expr expr-1.202\ - {i1=9223372036854775806, i2=100000} {i1+i2} 9.22337203685488e+18 - test_expr expr-1.203\ - {i1=9223372036854775807, i2=0} {i1+i2} 9223372036854775807 - test_expr expr-1.204\ - {i1=9223372036854775807, i2=1} {i1+i2} 9.22337203685478e+18 - test_expr expr-1.205\ - {i2=9223372036854775806, i1=1} {i1+i2} 9223372036854775807 - test_expr expr-1.206\ - {i2=9223372036854775806, i1=2} {i1+i2} 9.22337203685478e+18 - test_expr expr-1.207\ - {i2=9223372036854775806, i1=100000} {i1+i2} 9.22337203685488e+18 - test_expr expr-1.208\ - {i2=9223372036854775807, i1=0} {i1+i2} 9223372036854775807 - test_expr expr-1.209\ - {i2=9223372036854775807, i1=1} {i1+i2} 9.22337203685478e+18 - test_expr expr-1.210\ - {i1=-9223372036854775807, i2=-1} {i1+i2} -9223372036854775808 - test_expr expr-1.211\ - {i1=-9223372036854775807, i2=-2} {i1+i2} -9.22337203685478e+18 - test_expr expr-1.212\ - {i1=-9223372036854775807, i2=-100000} {i1+i2} -9.22337203685488e+18 - test_expr expr-1.213\ - {i1=-9223372036854775808, i2=0} {i1+i2} -9223372036854775808 - test_expr expr-1.214\ - {i1=-9223372036854775808, i2=-1} {i1+i2} -9.22337203685478e+18 - test_expr expr-1.215\ - {i2=-9223372036854775807, i1=-1} {i1+i2} -9223372036854775808 - test_expr expr-1.216\ - {i2=-9223372036854775807, i1=-2} {i1+i2} -9.22337203685478e+18 - test_expr expr-1.217\ - {i2=-9223372036854775807, i1=-100000} {i1+i2} -9.22337203685488e+18 - test_expr expr-1.218\ - {i2=-9223372036854775808, i1=0} {i1+i2} -9223372036854775808 - test_expr expr-1.219\ - {i2=-9223372036854775808, i1=-1} {i1+i2} -9.22337203685478e+18 - test_expr expr-1.220\ - {i1=9223372036854775806, i2=-1} {i1-i2} 9223372036854775807 - test_expr expr-1.221\ - {i1=9223372036854775806, i2=-2} {i1-i2} 9.22337203685478e+18 - test_expr expr-1.222\ - {i1=9223372036854775806, i2=-100000} {i1-i2} 9.22337203685488e+18 - test_expr expr-1.223\ - {i1=9223372036854775807, i2=0} {i1-i2} 9223372036854775807 - test_expr expr-1.224\ - {i1=9223372036854775807, i2=-1} {i1-i2} 9.22337203685478e+18 - test_expr expr-1.225\ - {i2=-9223372036854775806, i1=1} {i1-i2} 9223372036854775807 - test_expr expr-1.226\ - {i2=-9223372036854775806, i1=2} {i1-i2} 9.22337203685478e+18 - test_expr expr-1.227\ - {i2=-9223372036854775806, i1=100000} {i1-i2} 9.22337203685488e+18 - test_expr expr-1.228\ - {i2=-9223372036854775807, i1=0} {i1-i2} 9223372036854775807 - test_expr expr-1.229\ - {i2=-9223372036854775807, i1=1} {i1-i2} 9.22337203685478e+18 - test_expr expr-1.230\ - {i1=-9223372036854775807, i2=1} {i1-i2} -9223372036854775808 - test_expr expr-1.231\ - {i1=-9223372036854775807, i2=2} {i1-i2} -9.22337203685478e+18 - test_expr expr-1.232\ - {i1=-9223372036854775807, i2=100000} {i1-i2} -9.22337203685488e+18 - test_expr expr-1.233\ - {i1=-9223372036854775808, i2=0} {i1-i2} -9223372036854775808 - test_expr expr-1.234\ - {i1=-9223372036854775808, i2=1} {i1-i2} -9.22337203685478e+18 - test_expr expr-1.235\ - {i2=9223372036854775807, i1=-1} {i1-i2} -9223372036854775808 - test_expr expr-1.236\ - {i2=9223372036854775807, i1=-2} {i1-i2} -9.22337203685478e+18 - test_expr expr-1.237\ - {i2=9223372036854775807, i1=-100000} {i1-i2} -9.22337203685488e+18 - test_expr expr-1.238\ - {i2=9223372036854775807, i1=0} {i1-i2} -9223372036854775807 - test_expr expr-1.239\ - {i2=9223372036854775807, i1=-1} {i1-i2} -9223372036854775808 - - test_expr expr-1.250\ - {i1=4294967296, i2=2147483648} {i1*i2} 9.22337203685478e+18 - test_expr expr-1.251\ - {i1=4294967296, i2=2147483647} {i1*i2} 9223372032559808512 - test_expr expr-1.252\ - {i1=-4294967296, i2=2147483648} {i1*i2} -9223372036854775808 - test_expr expr-1.253\ - {i1=-4294967296, i2=2147483647} {i1*i2} -9223372032559808512 - test_expr expr-1.254\ - {i1=4294967296, i2=-2147483648} {i1*i2} -9223372036854775808 - test_expr expr-1.255\ - {i1=4294967296, i2=-2147483647} {i1*i2} -9223372032559808512 - test_expr expr-1.256\ - {i1=-4294967296, i2=-2147483648} {i1*i2} 9.22337203685478e+18 - test_expr expr-1.257\ + test_realnum_expr expr-1.201\ + {i1=9223372036854775806, i2=2} {i1+i2} 9.22337203685478e+18 + test_realnum_expr expr-1.202\ + {i1=9223372036854775806, i2=100000} {i1+i2} 9.22337203685488e+18 + test_realnum_expr expr-1.203\ + {i1=9223372036854775807, i2=0} {i1+i2} 9223372036854775807 + test_realnum_expr expr-1.204\ + {i1=9223372036854775807, i2=1} {i1+i2} 9.22337203685478e+18 + test_realnum_expr expr-1.205\ + {i2=9223372036854775806, i1=1} {i1+i2} 9223372036854775807 + test_realnum_expr expr-1.206\ + {i2=9223372036854775806, i1=2} {i1+i2} 9.22337203685478e+18 + test_realnum_expr expr-1.207\ + {i2=9223372036854775806, i1=100000} {i1+i2} 9.22337203685488e+18 + test_realnum_expr expr-1.208\ + {i2=9223372036854775807, i1=0} {i1+i2} 9223372036854775807 + test_realnum_expr expr-1.209\ + {i2=9223372036854775807, i1=1} {i1+i2} 9.22337203685478e+18 + test_realnum_expr expr-1.210\ + {i1=-9223372036854775807, i2=-1} {i1+i2} -9223372036854775808 + test_realnum_expr expr-1.211\ + {i1=-9223372036854775807, i2=-2} {i1+i2} -9.22337203685478e+18 + test_realnum_expr expr-1.212\ + {i1=-9223372036854775807, i2=-100000} {i1+i2} -9.22337203685488e+18 + test_realnum_expr expr-1.213\ + {i1=-9223372036854775808, i2=0} {i1+i2} -9223372036854775808 + test_realnum_expr expr-1.214\ + {i1=-9223372036854775808, i2=-1} {i1+i2} -9.22337203685478e+18 + test_realnum_expr expr-1.215\ + {i2=-9223372036854775807, i1=-1} {i1+i2} -9223372036854775808 + test_realnum_expr expr-1.216\ + {i2=-9223372036854775807, i1=-2} {i1+i2} -9.22337203685478e+18 + test_realnum_expr expr-1.217\ + {i2=-9223372036854775807, i1=-100000} {i1+i2} -9.22337203685488e+18 + test_realnum_expr expr-1.218\ + {i2=-9223372036854775808, i1=0} {i1+i2} -9223372036854775808 + test_realnum_expr expr-1.219\ + {i2=-9223372036854775808, i1=-1} {i1+i2} -9.22337203685478e+18 + test_realnum_expr expr-1.220\ + {i1=9223372036854775806, i2=-1} {i1-i2} 9223372036854775807 + test_realnum_expr expr-1.221\ + {i1=9223372036854775806, i2=-2} {i1-i2} 9.22337203685478e+18 + test_realnum_expr expr-1.222\ + {i1=9223372036854775806, i2=-100000} {i1-i2} 9.22337203685488e+18 + test_realnum_expr expr-1.223\ + {i1=9223372036854775807, i2=0} {i1-i2} 9223372036854775807 + test_realnum_expr expr-1.224\ + {i1=9223372036854775807, i2=-1} {i1-i2} 9.22337203685478e+18 + test_realnum_expr expr-1.225\ + {i2=-9223372036854775806, i1=1} {i1-i2} 9223372036854775807 + test_realnum_expr expr-1.226\ + {i2=-9223372036854775806, i1=2} {i1-i2} 9.22337203685478e+18 + test_realnum_expr expr-1.227\ + {i2=-9223372036854775806, i1=100000} {i1-i2} 9.22337203685488e+18 + test_realnum_expr expr-1.228\ + {i2=-9223372036854775807, i1=0} {i1-i2} 9223372036854775807 + test_realnum_expr expr-1.229\ + {i2=-9223372036854775807, i1=1} {i1-i2} 9.22337203685478e+18 + test_realnum_expr expr-1.230\ + {i1=-9223372036854775807, i2=1} {i1-i2} -9223372036854775808 + test_realnum_expr expr-1.231\ + {i1=-9223372036854775807, i2=2} {i1-i2} -9.22337203685478e+18 + test_realnum_expr expr-1.232\ + {i1=-9223372036854775807, i2=100000} {i1-i2} -9.22337203685488e+18 + test_realnum_expr expr-1.233\ + {i1=-9223372036854775808, i2=0} {i1-i2} -9223372036854775808 + test_realnum_expr expr-1.234\ + {i1=-9223372036854775808, i2=1} {i1-i2} -9.22337203685478e+18 + test_realnum_expr expr-1.235\ + {i2=9223372036854775807, i1=-1} {i1-i2} -9223372036854775808 + test_realnum_expr expr-1.236\ + {i2=9223372036854775807, i1=-2} {i1-i2} -9.22337203685478e+18 + test_realnum_expr expr-1.237\ + {i2=9223372036854775807, i1=-100000} {i1-i2} -9.22337203685488e+18 + test_realnum_expr expr-1.238\ + {i2=9223372036854775807, i1=0} {i1-i2} -9223372036854775807 + test_realnum_expr expr-1.239\ + {i2=9223372036854775807, i1=-1} {i1-i2} -9223372036854775808 + + test_realnum_expr expr-1.250\ + {i1=4294967296, i2=2147483648} {i1*i2} 9.22337203685478e+18 + test_realnum_expr expr-1.251\ + {i1=4294967296, i2=2147483647} {i1*i2} 9223372032559808512 + test_realnum_expr expr-1.252\ + {i1=-4294967296, i2=2147483648} {i1*i2} -9223372036854775808 + test_realnum_expr expr-1.253\ + {i1=-4294967296, i2=2147483647} {i1*i2} -9223372032559808512 + test_realnum_expr expr-1.254\ + {i1=4294967296, i2=-2147483648} {i1*i2} -9223372036854775808 + test_realnum_expr expr-1.255\ + {i1=4294967296, i2=-2147483647} {i1*i2} -9223372032559808512 + test_realnum_expr expr-1.256\ + {i1=-4294967296, i2=-2147483648} {i1*i2} 9.22337203685478e+18 + test_realnum_expr expr-1.257\ {i1=-4294967296, i2=-2147483647} {i1*i2} 9223372032559808512 }} ifcapable floatingpoint { @@ -881,11 +886,11 @@ SELECT (CASE WHEN a>4 THEN 1 ELSE 0) FROM test1; } } {1 {near ")": syntax error}} ifcapable floatingpoint { - do_test expr-13.1 { + do_realnum_test expr-13.1 { execsql { SELECT 12345678901234567890; } } {1.23456789012346e+19} } @@ -906,33 +911,33 @@ } # If the value is too large, use String->Float conversion. # ifcapable floatingpoint { - do_test expr-13.4 { + do_realnum_test expr-13.4 { execsql { SELECT 0+'9223372036854775808' } } {9.22337203685478e+18} - do_test expr-13.5 { + do_realnum_test expr-13.5 { execsql { SELECT '9223372036854775808'+0 } } {9.22337203685478e+18} } # Use String->float conversion if the value is explicitly a floating # point value. # -do_test expr-13.6 { +do_realnum_test expr-13.6 { execsql { SELECT 0+'9223372036854775807.0' } } {9.22337203685478e+18} -do_test expr-13.7 { +do_realnum_test expr-13.7 { execsql { SELECT '9223372036854775807.0'+0 } } {9.22337203685478e+18} finish_test Index: test/lock_common.tcl ================================================================== --- test/lock_common.tcl +++ test/lock_common.tcl @@ -53,12 +53,12 @@ proc csql3 {sql} { list [catch { sql3 $sql } msg] $msg } uplevel set $varname $tn uplevel $script - code2 { db2 close } - code3 { db3 close } + catch { code2 { db2 close } } + catch { code3 { db3 close } } catch { close $::code2_chan } catch { close $::code3_chan } catch { db close } } } Index: test/nan.test ================================================================== --- test/nan.test +++ test/nan.test @@ -40,35 +40,35 @@ sqlite3_step $::STMT sqlite3_reset $::STMT db eval {SELECT x, typeof(x) FROM t1} } {{} null} if {$tcl_platform(platform) != "symbian"} { - do_test nan-1.1.2 { + do_realnum_test nan-1.1.2 { sqlite3_bind_double $::STMT 1 +Inf sqlite3_step $::STMT sqlite3_reset $::STMT db eval {SELECT x, typeof(x) FROM t1} } {{} null inf real} - do_test nan-1.1.3 { + do_realnum_test nan-1.1.3 { sqlite3_bind_double $::STMT 1 -Inf sqlite3_step $::STMT sqlite3_reset $::STMT db eval {SELECT x, typeof(x) FROM t1} } {{} null inf real -inf real} - do_test nan-1.1.4 { + do_realnum_test nan-1.1.4 { sqlite3_bind_double $::STMT 1 -NaN sqlite3_step $::STMT sqlite3_reset $::STMT db eval {SELECT x, typeof(x) FROM t1} } {{} null inf real -inf real {} null} - do_test nan-1.1.5 { + do_realnum_test nan-1.1.5 { sqlite3_bind_double $::STMT 1 NaN0 sqlite3_step $::STMT sqlite3_reset $::STMT db eval {SELECT x, typeof(x) FROM t1} } {{} null inf real -inf real {} null {} null} - do_test nan-1.1.6 { + do_realnum_test nan-1.1.6 { sqlite3_bind_double $::STMT 1 -NaN0 sqlite3_step $::STMT sqlite3_reset $::STMT db eval {SELECT x, typeof(x) FROM t1} } {{} null inf real -inf real {} null {} null {} null} @@ -229,16 +229,16 @@ if {$tcl_platform(platform) != "symbian"} { # Do not run these tests on Symbian, as the Tcl port doesn't like to # convert from floating point value "-inf" to a string. # - do_test nan-4.7 { + do_realnum_test nan-4.7 { db eval {DELETE FROM t1} db eval "INSERT INTO t1 VALUES([string repeat 9 309].0)" db eval {SELECT x, typeof(x) FROM t1} } {inf real} - do_test nan-4.8 { + do_realnum_test nan-4.8 { db eval {DELETE FROM t1} db eval "INSERT INTO t1 VALUES(-[string repeat 9 309].0)" db eval {SELECT x, typeof(x) FROM t1} } {-inf real} } @@ -311,15 +311,15 @@ -[string repeat 0 10000].[string repeat 0 323][string repeat 9 10000] db eval "INSERT INTO t1 VALUES($small)" db eval {SELECT CAST(x AS text), typeof(x) FROM t1} } {-9.88131291682493e-324 real} -do_test nan-4.20 { +do_realnum_test nan-4.20 { db eval {DELETE FROM t1} set big [string repeat 9 10000].0e-9000 db eval "INSERT INTO t1 VALUES($big)" db eval {SELECT x, typeof(x) FROM t1} } {inf real} finish_test Index: test/pager1.test ================================================================== --- test/pager1.test +++ test/pager1.test @@ -1651,11 +1651,11 @@ # to the associated journal file will be longer than sqlite3_vfs.mxPathname. # testvfs tv -default 1 tv script xOpenCb tv filter xOpen -proc xOpenCb {method filename} { +proc xOpenCb {method filename args} { set ::file_len [string length $filename] } sqlite3 db test.db db close tv delete @@ -2379,10 +2379,11 @@ #------------------------------------------------------------------------- # Test that if the "page-size" field in a journal-header is 0, the journal # file can still be rolled back. This is required for backward compatibility - # versions of SQLite prior to 3.5.8 always set this field to zero. # +if {$tcl_platform(platform)=="unix"} { do_test pager1-31.1 { faultsim_delete_and_reopen execsql { PRAGMA cache_size = 10; PRAGMA page_size = 1024; @@ -2406,9 +2407,9 @@ hexio_write test.db2-journal 24 00000000 sqlite3 db2 test.db2 execsql { PRAGMA integrity_check } db2 } {ok} - +} finish_test Index: test/tester.tcl ================================================================== --- test/tester.tcl +++ test/tester.tcl @@ -351,10 +351,19 @@ } else { puts " Ok" } flush stdout } + +proc realnum_normalize {r} { + string map {1.#INF inf} [regsub -all {(e[+-])0+} $r {\1}] +} +proc do_realnum_test {name cmd expected} { + uplevel [list do_test $name [ + subst -nocommands { realnum_normalize [ $cmd ] } + ] [realnum_normalize $expected]] +} proc fix_testname {varname} { upvar $varname testname if {[info exists ::testprefix] && [string is digit [string range $testname 0 0]] Index: test/tkt3838.test ================================================================== --- test/tkt3838.test +++ test/tkt3838.test @@ -23,11 +23,11 @@ ifcapable !altertable { finish_test return } -do_test tkt3838-1.1 { +do_realnum_test tkt3838-1.1 { db eval { PRAGMA encoding=UTF16; CREATE TABLE t1(x); INSERT INTO t1 VALUES(1); ALTER TABLE t1 ADD COLUMN b INTEGER DEFAULT '999'; Index: test/tkt3922.test ================================================================== --- test/tkt3922.test +++ test/tkt3922.test @@ -34,25 +34,25 @@ INSERT INTO t1 VALUES('-1'); SELECT a, typeof(a) FROM t1; } } {-1 integer} } -do_test tkt3922.2 { +do_realnum_test tkt3922.2 { execsql { DELETE FROM t1; INSERT INTO t1 VALUES('-9223372036854775809'); SELECT a, typeof(a) FROM t1; } } {-9.22337203685478e+18 real} -do_test tkt3922.3 { +do_realnum_test tkt3922.3 { execsql { DELETE FROM t1; INSERT INTO t1 VALUES('-9223372036854776832'); SELECT a, typeof(a) FROM t1; } } {-9.22337203685478e+18 real} -do_test tkt3922.4 { +do_realnum_test tkt3922.4 { execsql { DELETE FROM t1; INSERT INTO t1 VALUES('-9223372036854776833'); SELECT a, typeof(a) FROM t1; } @@ -76,14 +76,14 @@ INSERT INTO t1 VALUES('1'); SELECT a, typeof(a) FROM t1; } } {1 integer} } -do_test tkt3922.6 { +do_realnum_test tkt3922.6 { execsql { DELETE FROM t1; INSERT INTO t1 VALUES('9223372036854775808'); SELECT a, typeof(a) FROM t1; } } {9.22337203685478e+18 real} finish_test ADDED test/uri.test Index: test/uri.test ================================================================== --- /dev/null +++ test/uri.test @@ -0,0 +1,309 @@ +# 2011 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Test organization: +# +# 1.*: That file names are correctly extracted from URIs. +# 2.*: That URI options (query parameters) are correctly extracted from URIs. +# 3.*: That specifying an unknown VFS causes an error. +# 4.*: Tests for specifying other options (other than "vfs"). +# 5.*: Test using a different VFS with an attached database. +# 6.*: Test that authorities other than "" and localhost cause errors. +# 7.*: Test that a read-write db can be attached to a read-only connection. +# + +set testprefix uri +db close +sqlite3_shutdown +sqlite3_config_uri 1 + +#------------------------------------------------------------------------- +# Test that file names are correctly extracted from URIs. +# +foreach {tn uri file} { + 1 test.db test.db + 2 file:test.db test.db + 3 file://PWD/test.db test.db + 4 file:PWD/test.db test.db + 5 file:test.db?mork=1 test.db + 6 file:test.db?mork=1&tonglor=2 test.db + 7 file:test.db?mork=1#boris test.db + 8 file:test.db#boris test.db + 9 test.db#boris test.db#boris + 10 file:test%2Edb test.db + 11 file file + 12 http:test.db http:test.db + 13 file:test.db%00extra test.db + 14 file:test%00.db%00extra test + + 15 test.db?mork=1#boris test.db?mork=1#boris + 16 file://localhostPWD/test.db%3Fhello test.db?hello +} { + + if {$tcl_platform(platform)=="windows"} { + if {$tn>14} break + set uri [string map [list PWD /[pwd]] $uri] + } else { + set uri [string map [list PWD [pwd]] $uri] + } + + forcedelete $file + do_test 1.$tn.1 { file exists $file } 0 + set DB [sqlite3_open $uri] + do_test 1.$tn.2 { file exists $file } 1 + sqlite3_close $DB + forcedelete $file + + do_test 1.$tn.3 { file exists $file } 0 + sqlite3 db xxx.db + catchsql { ATTACH $uri AS aux } + do_test 1.$tn.4 { file exists $file } 1 + db close +} + +#------------------------------------------------------------------------- +# Test that URI query parameters are passed through to the VFS layer +# correctly. +# +testvfs tvfs2 +testvfs tvfs -default 1 +tvfs filter xOpen +tvfs script open_method +proc open_method {method file arglist} { + set ::arglist $arglist +} +foreach {tn uri kvlist} { + 1 file:test.db?hello=world {hello world} + 2 file:test.db?hello&world {hello {} world {}} + 3 file:test.db?hello=1&world=2&vfs=tvfs {hello 1 world 2 vfs tvfs} + 4 file:test.db?hello=1&world=2&vfs=tvfs2 {} + 5 file:test.db?%68%65%6C%6C%6F=%77%6F%72%6C%64 {hello world} + 6 file:test%00.db?hello%00extra=world%00ex {hello world} + 7 file:test%00.db?hello%00=world%00 {hello world} + 8 file:test%00.db?=world&xyz=abc {xyz abc} + 9 file:test.db?%00hello=world&xyz=abc {xyz abc} + 10 file:test.db?hello=%00world&xyz= {hello {} xyz {}} + 11 file:test.db?=#ravada {} + 12 file:test.db?&&&&&&&&hello=world&&&&&&& {hello world} + + 13 test.db?&&&&&&&&hello=world&&&&&&& {} + 14 http:test.db?hello&world {} +} { + + if {$tcl_platform(platform) == "windows" && $tn>12} { + continue + } + + set ::arglist "" + set DB [sqlite3_open $uri] + do_test 2.$tn.1 { set ::arglist } $kvlist + sqlite3_close $DB + + sqlite3 db xxx.db + set ::arglist "" + execsql { ATTACH $uri AS aux } + do_test 2.$tn.2 { set ::arglist } $kvlist + db close +} +tvfs delete +tvfs2 delete + +#------------------------------------------------------------------------- +# Test that specifying a non-existent VFS raises an error. +# +do_test 3.1 { + list [catch { sqlite3 db "file:test.db?vfs=nosuchvfs" } msg] $msg +} {1 {no such vfs: nosuchvfs}} + +#------------------------------------------------------------------------- +# Test some of the other options (other than "vfs"). +# +foreach {tn mode create_ok write_ok readonly_ok} { + 1 ro 0 0 1 + 2 rw 0 1 0 + 3 rwc 1 1 0 +} { + catch { db close } + forcedelete test.db + + set A(1) {0 {}} + set A(0) {1 {unable to open database file}} + do_test 4.1.$tn.1 { + list [catch {sqlite3 db "file:test.db?mode=$mode"} msg] $msg + } $A($create_ok) + + catch { db close } + forcedelete test.db + sqlite3 db test.db + db eval { CREATE TABLE t1(a, b) } + db close + + set A(1) {0 {}} + set A(0) {1 {attempt to write a readonly database}} + do_test 4.1.$tn.2 { + sqlite3 db "file:test.db?mode=$mode" + catchsql { INSERT INTO t1 VALUES(1, 2) } + } $A($write_ok) + + set A(1) {0 {}} + set A(0) [list 1 "access mode not allowed: $mode"] + do_test 4.1.$tn.3 { + list [catch {sqlite3 db "file:test.db?mode=$mode" -readonly 1} msg] $msg + } $A($readonly_ok) +} + +set orig [sqlite3_enable_shared_cache] +foreach {tn options sc_default is_shared} { + 1 "" 1 1 + 2 "cache=private" 1 0 + 3 "cache=shared" 1 1 + 4 "" 0 0 + 5 "cache=private" 0 0 + 6 "cache=shared" 0 1 +} { + catch { db close } + forcedelete test.db + + sqlite3_enable_shared_cache 1 + sqlite3 db2 test.db + db2 eval {CREATE TABLE t1(a, b)} + + sqlite3_enable_shared_cache $sc_default + sqlite3 db "file:test.db?$options" + db eval {SELECT * FROM t1} + + set A(1) {1 {database table is locked: t1}} + set A(0) {0 {}} + do_test 4.2.$tn { + db2 eval {BEGIN; INSERT INTO t1 VALUES(1, 2);} + catchsql { SELECT * FROM t1 } + } $A($is_shared) + + db2 close +} + +do_test 4.3.1 { + list [catch {sqlite3 db "file:test.db?mode=rc"} msg] $msg +} {1 {no such access mode: rc}} +do_test 4.3.2 { + list [catch {sqlite3 db "file:test.db?cache=public"} msg] $msg +} {1 {no such cache mode: public}} + +#------------------------------------------------------------------------- +# Test that things work if an ATTACHed database uses a different VFS than +# the main database. The important point is that for all operations +# involving the ATTACHed database, the correct versions of the following +# VFS are used for all operations involving the attached database. +# +# xOpen +# xDelete +# xAccess +# xFullPathname +# + +# This block of code creates two VFS - "tvfs1" and "tvfs2". Each time one +# of the above methods is called using "tvfs1", global variable ::T1(X) is +# set, where X is the file-name the method is called on. Calls to the above +# methods using "tvfs2" set entries in the global T2 array. +# +testvfs tvfs1 +tvfs1 filter {xOpen xDelete xAccess xFullPathname} +tvfs1 script tvfs1_callback +proc tvfs1_callback {method filename args} { + set ::T1([file tail $filename]) 1 +} +testvfs tvfs2 +tvfs2 filter {xOpen xDelete xAccess xFullPathname} +tvfs2 script tvfs2_callback +proc tvfs2_callback {method filename args} { + set ::T2([file tail $filename]) 1 +} + +catch {db close} +eval forcedelete [glob test.db*] +do_test 5.1.1 { + sqlite3 db file:test.db1?vfs=tvfs1 + execsql { + ATTACH 'file:test.db2?vfs=tvfs2' AS aux; + PRAGMA main.journal_mode = PERSIST; + PRAGMA aux.journal_mode = PERSIST; + CREATE TABLE t1(a, b); + CREATE TABLE aux.t2(a, b); + PRAGMA main.journal_mode = WAL; + PRAGMA aux.journal_mode = WAL; + INSERT INTO t1 VALUES('x', 'y'); + INSERT INTO t2 VALUES('x', 'y'); + } + lsort [array names ::T1] +} {test.db1 test.db1-journal test.db1-wal} + +do_test 5.1.2 { + lsort [array names ::T2] +} {test.db2 test.db2-journal test.db2-wal} + +db close +tvfs1 delete +tvfs2 delete + +#------------------------------------------------------------------------- +# Check that only "" and "localhost" are acceptable as authorities. +# +catch {db close} +foreach {tn uri res} { + 1 "file://localhost/PWD/test.db" {not an error} + 2 "file:///PWD/test.db" {not an error} + 3 "file:/PWD/test.db" {not an error} + 4 "file://l%6Fcalhost/PWD/test.db" {invalid uri authority: l%6Fcalhost} + 5 "file://lbcalhost/PWD/test.db" {invalid uri authority: lbcalhost} + 6 "file://x/PWD/test.db" {invalid uri authority: x} +} { + + if {$tcl_platform(platform)=="windows"} { + set uri [string map [list PWD [string range [pwd] 3 end]] $uri] + } else { + set uri [string map [list PWD [string range [pwd] 1 end]] $uri] + } + + do_test 6.$tn { + set DB [sqlite3_open $uri] + sqlite3_errmsg $DB + } $res + catch { sqlite3_close $DB } +} + +forcedelete test.db test.db2 +do_test 7.1 { + sqlite3 db test.db + execsql { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + ATTACH 'test.db2' AS aux; + CREATE TABLE aux.t2(a, b); + INSERT INTO t1 VALUES('a', 'b'); + } + db close +} {} +do_test 7.2 { + sqlite3 db file:test.db?mode=ro + execsql { ATTACH 'file:test.db2?mode=rw' AS aux } +} {} +do_execsql_test 7.3 { + INSERT INTO t2 VALUES('c', 'd') +} {} +do_catchsql_test 7.4 { + INSERT INTO t1 VALUES(3, 4) +} {1 {attempt to write a readonly database}} + +finish_test ADDED test/walro.test Index: test/walro.test ================================================================== --- /dev/null +++ test/walro.test @@ -0,0 +1,159 @@ +# 2011 May 09 +# +# 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 using WAL databases in read-only mode. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +set ::testprefix walro + + +do_multiclient_test tn { + # These tests are only going to work on unix. + # + if {$tcl_platform(platform) != "unix"} continue + + # Do not run tests with the connections in the same process. + # + if {$tn==2} continue + + # Close all connections and delete the database. + # + code1 { db close } + code2 { db2 close } + code3 { db3 close } + forcedelete test.db + forcedelete walro + + foreach c {code1 code2 code3} { + $c { + sqlite3_shutdown + sqlite3_config_uri 1 + } + } + + file mkdir walro + + do_test 1.1.1 { + code2 { sqlite3 db2 test.db } + sql2 { + PRAGMA journal_mode = WAL; + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES('a', 'b'); + } + file exists test.db-shm + } {1} + + do_test 1.1.2 { + file attributes test.db-shm -permissions r--r--r-- + code1 { sqlite3 db file:test.db?readonly_shm=1 } + } {} + + do_test 1.1.3 { sql1 "SELECT * FROM t1" } {a b} + do_test 1.1.4 { sql2 "INSERT INTO t1 VALUES('c', 'd')" } {} + do_test 1.1.5 { sql1 "SELECT * FROM t1" } {a b c d} + + # Check that the read-only connection cannot write or checkpoint the db. + # + do_test 1.1.6 { + csql1 "INSERT INTO t1 VALUES('e', 'f')" + } {1 {attempt to write a readonly database}} + do_test 1.1.7 { + csql1 "PRAGMA wal_checkpoint" + } {1 {attempt to write a readonly database}} + + do_test 1.1.9 { sql2 "INSERT INTO t1 VALUES('e', 'f')" } {} + do_test 1.1.10 { sql1 "SELECT * FROM t1" } {a b c d e f} + + do_test 1.1.11 { + sql2 { + INSERT INTO t1 VALUES('g', 'h'); + PRAGMA wal_checkpoint; + } + set {} {} + } {} + do_test 1.1.12 { sql1 "SELECT * FROM t1" } {a b c d e f g h} + do_test 1.1.13 { sql2 "INSERT INTO t1 VALUES('i', 'j')" } {} + + do_test 1.2.1 { + code2 { db2 close } + code1 { db close } + list [file exists test.db-wal] [file exists test.db-shm] + } {1 1} + do_test 1.2.2 { + code1 { sqlite3 db file:test.db?readonly_shm=1 } + sql1 { SELECT * FROM t1 } + } {a b c d e f g h i j} + + do_test 1.2.3 { + code1 { db close } + file attributes test.db-shm -permissions rw-r--r-- + hexio_write test.db-shm 0 01020304 + file attributes test.db-shm -permissions r--r--r-- + code1 { sqlite3 db file:test.db?readonly_shm=1 } + csql1 { SELECT * FROM t1 } + } {1 {attempt to write a readonly database}} + do_test 1.2.4 { + code1 { sqlite3_extended_errcode db } + } {SQLITE_READONLY_RECOVERY} + + do_test 1.2.5 { + file attributes test.db-shm -permissions rw-r--r-- + code2 { sqlite3 db2 test.db } + sql2 "SELECT * FROM t1" + } {a b c d e f g h i j} + file attributes test.db-shm -permissions r--r--r-- + do_test 1.2.6 { sql1 "SELECT * FROM t1" } {a b c d e f g h i j} + + do_test 1.2.7 { + sql2 { + PRAGMA wal_checkpoint; + INSERT INTO t1 VALUES('k', 'l'); + } + set {} {} + } {} + do_test 1.2.8 { sql1 "SELECT * FROM t1" } {a b c d e f g h i j k l} + + # Now check that if the readonly_shm option is not supplied, or if it + # is set to zero, it is not possible to connect to the database without + # read-write access to the shm. + do_test 1.3.1 { + code1 { db close } + code1 { sqlite3 db test.db } + csql1 { SELECT * FROM t1 } + } {1 {unable to open database file}} + + # Also test that if the -shm file can be opened for read/write access, + # it is, even if readonly_shm=1 is present in the URI. + do_test 1.3.2.1 { + code1 { db close } + code2 { db2 close } + file exists test.db-shm + } {0} + do_test 1.3.2.2 { + code1 { sqlite3 db file:test.db?readonly_shm=1 } + sql1 { SELECT * FROM t1 } + } {a b c d e f g h i j k l} + do_test 1.3.2.3 { + code1 { db close } + close [open test.db-shm w] + file attributes test.db-shm -permissions r--r--r-- + code1 { sqlite3 db file:test.db?readonly_shm=1 } + csql1 { SELECT * FROM t1 } + } {1 {attempt to write a readonly database}} + do_test 1.3.2.4 { + code1 { sqlite3_extended_errcode db } + } {SQLITE_READONLY_RECOVERY} +} + +finish_test