| /* | |
| ** 2008 April 10 | |
| ** | |
| ** 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 the implementation of an SQLite vfs wrapper that | |
| ** adds instrumentation to all vfs and file methods. C and Tcl interfaces | |
| ** are provided to control the instrumentation. | |
| */ | |
| /* | |
| ** This module contains code for a wrapper VFS that causes a log of | |
| ** most VFS calls to be written into a nominated file on disk. The log | |
| ** is stored in a compressed binary format to reduce the amount of IO | |
| ** overhead introduced into the application by logging. | |
| ** | |
| ** All calls on sqlite3_file objects except xFileControl() are logged. | |
| ** Additionally, calls to the xAccess(), xOpen(), and xDelete() | |
| ** methods are logged. The other sqlite3_vfs object methods (xDlXXX, | |
| ** xRandomness, xSleep, xCurrentTime, xGetLastError and xCurrentTimeInt64) | |
| ** are not logged. | |
| ** | |
| ** The binary log files are read using a virtual table implementation | |
| ** also contained in this file. | |
| ** | |
| ** CREATING LOG FILES: | |
| ** | |
| ** int sqlite3_vfslog_new( | |
| ** const char *zVfs, // Name of new VFS | |
| ** const char *zParentVfs, // Name of parent VFS (or NULL) | |
| ** const char *zLog // Name of log file to write to | |
| ** ); | |
| ** | |
| ** int sqlite3_vfslog_finalize(const char *zVfs); | |
| ** | |
| ** ANNOTATING LOG FILES: | |
| ** | |
| ** To write an arbitrary message into a log file: | |
| ** | |
| ** int sqlite3_vfslog_annotate(const char *zVfs, const char *zMsg); | |
| ** | |
| ** READING LOG FILES: | |
| ** | |
| ** Log files are read using the "vfslog" virtual table implementation | |
| ** in this file. To register the virtual table with SQLite, use: | |
| ** | |
| ** int sqlite3_vfslog_register(sqlite3 *db); | |
| ** | |
| ** Then, if the log file is named "vfs.log", the following SQL command: | |
| ** | |
| ** CREATE VIRTUAL TABLE v USING vfslog('vfs.log'); | |
| ** | |
| ** creates a virtual table with 6 columns, as follows: | |
| ** | |
| ** CREATE TABLE v( | |
| ** event TEXT, // "xOpen", "xRead" etc. | |
| ** file TEXT, // Name of file this call applies to | |
| ** clicks INTEGER, // Time spent in call | |
| ** rc INTEGER, // Return value | |
| ** size INTEGER, // Bytes read or written | |
| ** offset INTEGER // File offset read or written | |
| ** ); | |
| */ | |
| #include "sqlite3.h" | |
| #include <string.h> | |
| #include <assert.h> | |
| /* | |
| ** Maximum pathname length supported by the vfslog backend. | |
| */ | |
| #define INST_MAX_PATHNAME 512 | |
| #define OS_ACCESS 1 | |
| #define OS_CHECKRESERVEDLOCK 2 | |
| #define OS_CLOSE 3 | |
| #define OS_CURRENTTIME 4 | |
| #define OS_DELETE 5 | |
| #define OS_DEVCHAR 6 | |
| #define OS_FILECONTROL 7 | |
| #define OS_FILESIZE 8 | |
| #define OS_FULLPATHNAME 9 | |
| #define OS_LOCK 11 | |
| #define OS_OPEN 12 | |
| #define OS_RANDOMNESS 13 | |
| #define OS_READ 14 | |
| #define OS_SECTORSIZE 15 | |
| #define OS_SLEEP 16 | |
| #define OS_SYNC 17 | |
| #define OS_TRUNCATE 18 | |
| #define OS_UNLOCK 19 | |
| #define OS_WRITE 20 | |
| #define OS_SHMUNMAP 22 | |
| #define OS_SHMMAP 23 | |
| #define OS_SHMLOCK 25 | |
| #define OS_SHMBARRIER 26 | |
| #define OS_ANNOTATE 28 | |
| #define OS_NUMEVENTS 29 | |
| #define VFSLOG_BUFFERSIZE 8192 | |
| typedef struct VfslogVfs VfslogVfs; | |
| typedef struct VfslogFile VfslogFile; | |
| struct VfslogVfs { | |
| sqlite3_vfs base; /* VFS methods */ | |
| sqlite3_vfs *pVfs; /* Parent VFS */ | |
| int iNextFileId; /* Next file id */ | |
| sqlite3_file *pLog; /* Log file handle */ | |
| sqlite3_int64 iOffset; /* Log file offset of start of write buffer */ | |
| int nBuf; /* Number of valid bytes in aBuf[] */ | |
| char aBuf[VFSLOG_BUFFERSIZE]; /* Write buffer */ | |
| }; | |
| struct VfslogFile { | |
| sqlite3_file base; /* IO methods */ | |
| sqlite3_file *pReal; /* Underlying file handle */ | |
| sqlite3_vfs *pVfslog; /* Associated VsflogVfs object */ | |
| int iFileId; /* File id number */ | |
| }; | |
| #define REALVFS(p) (((VfslogVfs *)(p))->pVfs) | |
| /* | |
| ** Method declarations for vfslog_file. | |
| */ | |
| static int vfslogClose(sqlite3_file*); | |
| static int vfslogRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); | |
| static int vfslogWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst); | |
| static int vfslogTruncate(sqlite3_file*, sqlite3_int64 size); | |
| static int vfslogSync(sqlite3_file*, int flags); | |
| static int vfslogFileSize(sqlite3_file*, sqlite3_int64 *pSize); | |
| static int vfslogLock(sqlite3_file*, int); | |
| static int vfslogUnlock(sqlite3_file*, int); | |
| static int vfslogCheckReservedLock(sqlite3_file*, int *pResOut); | |
| static int vfslogFileControl(sqlite3_file*, int op, void *pArg); | |
| static int vfslogSectorSize(sqlite3_file*); | |
| static int vfslogDeviceCharacteristics(sqlite3_file*); | |
| static int vfslogShmLock(sqlite3_file *pFile, int ofst, int n, int flags); | |
| static int vfslogShmMap(sqlite3_file *pFile,int,int,int,volatile void **); | |
| static void vfslogShmBarrier(sqlite3_file*); | |
| static int vfslogShmUnmap(sqlite3_file *pFile, int deleteFlag); | |
| /* | |
| ** Method declarations for vfslog_vfs. | |
| */ | |
| static int vfslogOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); | |
| static int vfslogDelete(sqlite3_vfs*, const char *zName, int syncDir); | |
| static int vfslogAccess(sqlite3_vfs*, const char *zName, int flags, int *); | |
| static int vfslogFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); | |
| static void *vfslogDlOpen(sqlite3_vfs*, const char *zFilename); | |
| static void vfslogDlError(sqlite3_vfs*, int nByte, char *zErrMsg); | |
| static void (*vfslogDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void); | |
| static void vfslogDlClose(sqlite3_vfs*, void*); | |
| static int vfslogRandomness(sqlite3_vfs*, int nByte, char *zOut); | |
| static int vfslogSleep(sqlite3_vfs*, int microseconds); | |
| static int vfslogCurrentTime(sqlite3_vfs*, double*); | |
| static int vfslogGetLastError(sqlite3_vfs*, int, char *); | |
| static int vfslogCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); | |
| static sqlite3_vfs vfslog_vfs = { | |
| 1, /* iVersion */ | |
| sizeof(VfslogFile), /* szOsFile */ | |
| INST_MAX_PATHNAME, /* mxPathname */ | |
| 0, /* pNext */ | |
| 0, /* zName */ | |
| 0, /* pAppData */ | |
| vfslogOpen, /* xOpen */ | |
| vfslogDelete, /* xDelete */ | |
| vfslogAccess, /* xAccess */ | |
| vfslogFullPathname, /* xFullPathname */ | |
| vfslogDlOpen, /* xDlOpen */ | |
| vfslogDlError, /* xDlError */ | |
| vfslogDlSym, /* xDlSym */ | |
| vfslogDlClose, /* xDlClose */ | |
| vfslogRandomness, /* xRandomness */ | |
| vfslogSleep, /* xSleep */ | |
| vfslogCurrentTime, /* xCurrentTime */ | |
| vfslogGetLastError, /* xGetLastError */ | |
| vfslogCurrentTimeInt64 /* xCurrentTime */ | |
| }; | |
| static sqlite3_io_methods vfslog_io_methods = { | |
| 2, /* iVersion */ | |
| vfslogClose, /* xClose */ | |
| vfslogRead, /* xRead */ | |
| vfslogWrite, /* xWrite */ | |
| vfslogTruncate, /* xTruncate */ | |
| vfslogSync, /* xSync */ | |
| vfslogFileSize, /* xFileSize */ | |
| vfslogLock, /* xLock */ | |
| vfslogUnlock, /* xUnlock */ | |
| vfslogCheckReservedLock, /* xCheckReservedLock */ | |
| vfslogFileControl, /* xFileControl */ | |
| vfslogSectorSize, /* xSectorSize */ | |
| vfslogDeviceCharacteristics, /* xDeviceCharacteristics */ | |
| vfslogShmMap, /* xShmMap */ | |
| vfslogShmLock, /* xShmLock */ | |
| vfslogShmBarrier, /* xShmBarrier */ | |
| vfslogShmUnmap /* xShmUnmap */ | |
| }; | |
| #if defined(SQLITE_OS_UNIX) && !defined(NO_GETTOD) | |
| #include <sys/time.h> | |
| static sqlite3_uint64 vfslog_time(){ | |
| struct timeval sTime; | |
| gettimeofday(&sTime, 0); | |
| return sTime.tv_usec + (sqlite3_uint64)sTime.tv_sec * 1000000; | |
| } | |
| #elif defined(SQLITE_OS_WIN) | |
| #include <windows.h> | |
| #include <time.h> | |
| static sqlite3_uint64 vfslog_time(){ | |
| FILETIME ft; | |
| sqlite3_uint64 u64time = 0; | |
| GetSystemTimeAsFileTime(&ft); | |
| u64time |= ft.dwHighDateTime; | |
| u64time <<= 32; | |
| u64time |= ft.dwLowDateTime; | |
| /* ft is 100-nanosecond intervals, we want microseconds */ | |
| return u64time /(sqlite3_uint64)10; | |
| } | |
| #else | |
| static sqlite3_uint64 vfslog_time(){ | |
| return 0; | |
| } | |
| #endif | |
| static void vfslog_call(sqlite3_vfs *, int, int, int, int, int, int); | |
| static void vfslog_string(sqlite3_vfs *, const char *); | |
| /* | |
| ** Close an vfslog-file. | |
| */ | |
| static int vfslogClose(sqlite3_file *pFile){ | |
| sqlite3_uint64 t; | |
| int rc = SQLITE_OK; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| if( p->pReal->pMethods ){ | |
| rc = p->pReal->pMethods->xClose(p->pReal); | |
| } | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_CLOSE, p->iFileId, t, rc, 0, 0); | |
| return rc; | |
| } | |
| /* | |
| ** Read data from an vfslog-file. | |
| */ | |
| static int vfslogRead( | |
| sqlite3_file *pFile, | |
| void *zBuf, | |
| int iAmt, | |
| sqlite_int64 iOfst | |
| ){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_READ, p->iFileId, t, rc, iAmt, (int)iOfst); | |
| return rc; | |
| } | |
| /* | |
| ** Write data to an vfslog-file. | |
| */ | |
| static int vfslogWrite( | |
| sqlite3_file *pFile, | |
| const void *z, | |
| int iAmt, | |
| sqlite_int64 iOfst | |
| ){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xWrite(p->pReal, z, iAmt, iOfst); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_WRITE, p->iFileId, t, rc, iAmt, (int)iOfst); | |
| return rc; | |
| } | |
| /* | |
| ** Truncate an vfslog-file. | |
| */ | |
| static int vfslogTruncate(sqlite3_file *pFile, sqlite_int64 size){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xTruncate(p->pReal, size); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_TRUNCATE, p->iFileId, t, rc, 0, (int)size); | |
| return rc; | |
| } | |
| /* | |
| ** Sync an vfslog-file. | |
| */ | |
| static int vfslogSync(sqlite3_file *pFile, int flags){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xSync(p->pReal, flags); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_SYNC, p->iFileId, t, rc, flags, 0); | |
| return rc; | |
| } | |
| /* | |
| ** Return the current file-size of an vfslog-file. | |
| */ | |
| static int vfslogFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xFileSize(p->pReal, pSize); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_FILESIZE, p->iFileId, t, rc, 0, (int)*pSize); | |
| return rc; | |
| } | |
| /* | |
| ** Lock an vfslog-file. | |
| */ | |
| static int vfslogLock(sqlite3_file *pFile, int eLock){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xLock(p->pReal, eLock); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_LOCK, p->iFileId, t, rc, eLock, 0); | |
| return rc; | |
| } | |
| /* | |
| ** Unlock an vfslog-file. | |
| */ | |
| static int vfslogUnlock(sqlite3_file *pFile, int eLock){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xUnlock(p->pReal, eLock); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_UNLOCK, p->iFileId, t, rc, eLock, 0); | |
| return rc; | |
| } | |
| /* | |
| ** Check if another file-handle holds a RESERVED lock on an vfslog-file. | |
| */ | |
| static int vfslogCheckReservedLock(sqlite3_file *pFile, int *pResOut){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_CHECKRESERVEDLOCK, p->iFileId, t, rc, *pResOut, 0); | |
| return rc; | |
| } | |
| /* | |
| ** File control method. For custom operations on an vfslog-file. | |
| */ | |
| static int vfslogFileControl(sqlite3_file *pFile, int op, void *pArg){ | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| return p->pReal->pMethods->xFileControl(p->pReal, op, pArg); | |
| } | |
| /* | |
| ** Return the sector-size in bytes for an vfslog-file. | |
| */ | |
| static int vfslogSectorSize(sqlite3_file *pFile){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xSectorSize(p->pReal); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_SECTORSIZE, p->iFileId, t, rc, 0, 0); | |
| return rc; | |
| } | |
| /* | |
| ** Return the device characteristic flags supported by an vfslog-file. | |
| */ | |
| static int vfslogDeviceCharacteristics(sqlite3_file *pFile){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_DEVCHAR, p->iFileId, t, rc, 0, 0); | |
| return rc; | |
| } | |
| static int vfslogShmLock(sqlite3_file *pFile, int ofst, int n, int flags){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_SHMLOCK, p->iFileId, t, rc, 0, 0); | |
| return rc; | |
| } | |
| static int vfslogShmMap( | |
| sqlite3_file *pFile, | |
| int iRegion, | |
| int szRegion, | |
| int isWrite, | |
| volatile void **pp | |
| ){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_SHMMAP, p->iFileId, t, rc, 0, 0); | |
| return rc; | |
| } | |
| static void vfslogShmBarrier(sqlite3_file *pFile){ | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| p->pReal->pMethods->xShmBarrier(p->pReal); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_SHMBARRIER, p->iFileId, t, SQLITE_OK, 0, 0); | |
| } | |
| static int vfslogShmUnmap(sqlite3_file *pFile, int deleteFlag){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| t = vfslog_time(); | |
| rc = p->pReal->pMethods->xShmUnmap(p->pReal, deleteFlag); | |
| t = vfslog_time() - t; | |
| vfslog_call(p->pVfslog, OS_SHMUNMAP, p->iFileId, t, rc, 0, 0); | |
| return rc; | |
| } | |
| /* | |
| ** Open an vfslog file handle. | |
| */ | |
| static int vfslogOpen( | |
| sqlite3_vfs *pVfs, | |
| const char *zName, | |
| sqlite3_file *pFile, | |
| int flags, | |
| int *pOutFlags | |
| ){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| VfslogFile *p = (VfslogFile *)pFile; | |
| VfslogVfs *pLog = (VfslogVfs *)pVfs; | |
| pFile->pMethods = &vfslog_io_methods; | |
| p->pReal = (sqlite3_file *)&p[1]; | |
| p->pVfslog = pVfs; | |
| p->iFileId = ++pLog->iNextFileId; | |
| t = vfslog_time(); | |
| rc = REALVFS(pVfs)->xOpen(REALVFS(pVfs), zName, p->pReal, flags, pOutFlags); | |
| t = vfslog_time() - t; | |
| vfslog_call(pVfs, OS_OPEN, p->iFileId, t, rc, 0, 0); | |
| vfslog_string(pVfs, zName); | |
| return rc; | |
| } | |
| /* | |
| ** Delete the file located at zPath. If the dirSync argument is true, | |
| ** ensure the file-system modifications are synced to disk before | |
| ** returning. | |
| */ | |
| static int vfslogDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| t = vfslog_time(); | |
| rc = REALVFS(pVfs)->xDelete(REALVFS(pVfs), zPath, dirSync); | |
| t = vfslog_time() - t; | |
| vfslog_call(pVfs, OS_DELETE, 0, t, rc, dirSync, 0); | |
| vfslog_string(pVfs, zPath); | |
| return rc; | |
| } | |
| /* | |
| ** Test for access permissions. Return true if the requested permission | |
| ** is available, or false otherwise. | |
| */ | |
| static int vfslogAccess( | |
| sqlite3_vfs *pVfs, | |
| const char *zPath, | |
| int flags, | |
| int *pResOut | |
| ){ | |
| int rc; | |
| sqlite3_uint64 t; | |
| t = vfslog_time(); | |
| rc = REALVFS(pVfs)->xAccess(REALVFS(pVfs), zPath, flags, pResOut); | |
| t = vfslog_time() - t; | |
| vfslog_call(pVfs, OS_ACCESS, 0, t, rc, flags, *pResOut); | |
| vfslog_string(pVfs, zPath); | |
| return rc; | |
| } | |
| /* | |
| ** Populate buffer zOut with the full canonical pathname corresponding | |
| ** to the pathname in zPath. zOut is guaranteed to point to a buffer | |
| ** of at least (INST_MAX_PATHNAME+1) bytes. | |
| */ | |
| static int vfslogFullPathname( | |
| sqlite3_vfs *pVfs, | |
| const char *zPath, | |
| int nOut, | |
| char *zOut | |
| ){ | |
| return REALVFS(pVfs)->xFullPathname(REALVFS(pVfs), zPath, nOut, zOut); | |
| } | |
| /* | |
| ** Open the dynamic library located at zPath and return a handle. | |
| */ | |
| static void *vfslogDlOpen(sqlite3_vfs *pVfs, const char *zPath){ | |
| return REALVFS(pVfs)->xDlOpen(REALVFS(pVfs), zPath); | |
| } | |
| /* | |
| ** Populate the buffer zErrMsg (size nByte bytes) with a human readable | |
| ** utf-8 string describing the most recent error encountered associated | |
| ** with dynamic libraries. | |
| */ | |
| static void vfslogDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ | |
| REALVFS(pVfs)->xDlError(REALVFS(pVfs), nByte, zErrMsg); | |
| } | |
| /* | |
| ** Return a pointer to the symbol zSymbol in the dynamic library pHandle. | |
| */ | |
| static void (*vfslogDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){ | |
| return REALVFS(pVfs)->xDlSym(REALVFS(pVfs), p, zSym); | |
| } | |
| /* | |
| ** Close the dynamic library handle pHandle. | |
| */ | |
| static void vfslogDlClose(sqlite3_vfs *pVfs, void *pHandle){ | |
| REALVFS(pVfs)->xDlClose(REALVFS(pVfs), pHandle); | |
| } | |
| /* | |
| ** Populate the buffer pointed to by zBufOut with nByte bytes of | |
| ** random data. | |
| */ | |
| static int vfslogRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ | |
| return REALVFS(pVfs)->xRandomness(REALVFS(pVfs), nByte, zBufOut); | |
| } | |
| /* | |
| ** Sleep for nMicro microseconds. Return the number of microseconds | |
| ** actually slept. | |
| */ | |
| static int vfslogSleep(sqlite3_vfs *pVfs, int nMicro){ | |
| return REALVFS(pVfs)->xSleep(REALVFS(pVfs), nMicro); | |
| } | |
| /* | |
| ** Return the current time as a Julian Day number in *pTimeOut. | |
| */ | |
| static int vfslogCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ | |
| return REALVFS(pVfs)->xCurrentTime(REALVFS(pVfs), pTimeOut); | |
| } | |
| static int vfslogGetLastError(sqlite3_vfs *pVfs, int a, char *b){ | |
| return REALVFS(pVfs)->xGetLastError(REALVFS(pVfs), a, b); | |
| } | |
| static int vfslogCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ | |
| return REALVFS(pVfs)->xCurrentTimeInt64(REALVFS(pVfs), p); | |
| } | |
| static void vfslog_flush(VfslogVfs *p){ | |
| #ifdef SQLITE_TEST | |
| extern int sqlite3_io_error_pending; | |
| extern int sqlite3_io_error_persist; | |
| extern int sqlite3_diskfull_pending; | |
| int pending = sqlite3_io_error_pending; | |
| int persist = sqlite3_io_error_persist; | |
| int diskfull = sqlite3_diskfull_pending; | |
| sqlite3_io_error_pending = 0; | |
| sqlite3_io_error_persist = 0; | |
| sqlite3_diskfull_pending = 0; | |
| #endif | |
| if( p->nBuf ){ | |
| p->pLog->pMethods->xWrite(p->pLog, p->aBuf, p->nBuf, p->iOffset); | |
| p->iOffset += p->nBuf; | |
| p->nBuf = 0; | |
| } | |
| #ifdef SQLITE_TEST | |
| sqlite3_io_error_pending = pending; | |
| sqlite3_io_error_persist = persist; | |
| sqlite3_diskfull_pending = diskfull; | |
| #endif | |
| } | |
| static void put32bits(unsigned char *p, unsigned int v){ | |
| p[0] = v>>24; | |
| p[1] = v>>16; | |
| p[2] = v>>8; | |
| p[3] = v; | |
| } | |
| static void vfslog_call( | |
| sqlite3_vfs *pVfs, | |
| int eEvent, | |
| int iFileid, | |
| int nClick, | |
| int return_code, | |
| int size, | |
| int offset | |
| ){ | |
| VfslogVfs *p = (VfslogVfs *)pVfs; | |
| unsigned char *zRec; | |
| if( (24+p->nBuf)>sizeof(p->aBuf) ){ | |
| vfslog_flush(p); | |
| } | |
| zRec = (unsigned char *)&p->aBuf[p->nBuf]; | |
| put32bits(&zRec[0], eEvent); | |
| put32bits(&zRec[4], iFileid); | |
| put32bits(&zRec[8], nClick); | |
| put32bits(&zRec[12], return_code); | |
| put32bits(&zRec[16], size); | |
| put32bits(&zRec[20], offset); | |
| p->nBuf += 24; | |
| } | |
| static void vfslog_string(sqlite3_vfs *pVfs, const char *zStr){ | |
| VfslogVfs *p = (VfslogVfs *)pVfs; | |
| unsigned char *zRec; | |
| int nStr = zStr ? strlen(zStr) : 0; | |
| if( (4+nStr+p->nBuf)>sizeof(p->aBuf) ){ | |
| vfslog_flush(p); | |
| } | |
| zRec = (unsigned char *)&p->aBuf[p->nBuf]; | |
| put32bits(&zRec[0], nStr); | |
| if( zStr ){ | |
| memcpy(&zRec[4], zStr, nStr); | |
| } | |
| p->nBuf += (4 + nStr); | |
| } | |
| static void vfslog_finalize(VfslogVfs *p){ | |
| if( p->pLog->pMethods ){ | |
| vfslog_flush(p); | |
| p->pLog->pMethods->xClose(p->pLog); | |
| } | |
| sqlite3_free(p); | |
| } | |
| int sqlite3_vfslog_finalize(const char *zVfs){ | |
| sqlite3_vfs *pVfs; | |
| pVfs = sqlite3_vfs_find(zVfs); | |
| if( !pVfs || pVfs->xOpen!=vfslogOpen ){ | |
| return SQLITE_ERROR; | |
| } | |
| sqlite3_vfs_unregister(pVfs); | |
| vfslog_finalize((VfslogVfs *)pVfs); | |
| return SQLITE_OK; | |
| } | |
| int sqlite3_vfslog_new( | |
| const char *zVfs, /* New VFS name */ | |
| const char *zParentVfs, /* Parent VFS name (or NULL) */ | |
| const char *zLog /* Log file name */ | |
| ){ | |
| VfslogVfs *p; | |
| sqlite3_vfs *pParent; | |
| int nByte; | |
| int flags; | |
| int rc; | |
| char *zFile; | |
| int nVfs; | |
| pParent = sqlite3_vfs_find(zParentVfs); | |
| if( !pParent ){ | |
| return SQLITE_ERROR; | |
| } | |
| nVfs = strlen(zVfs); | |
| nByte = sizeof(VfslogVfs) + pParent->szOsFile + nVfs+1+pParent->mxPathname+1; | |
| p = (VfslogVfs *)sqlite3_malloc(nByte); | |
| memset(p, 0, nByte); | |
| p->pVfs = pParent; | |
| p->pLog = (sqlite3_file *)&p[1]; | |
| memcpy(&p->base, &vfslog_vfs, sizeof(sqlite3_vfs)); | |
| p->base.zName = &((char *)p->pLog)[pParent->szOsFile]; | |
| p->base.szOsFile += pParent->szOsFile; | |
| memcpy((char *)p->base.zName, zVfs, nVfs); | |
| zFile = (char *)&p->base.zName[nVfs+1]; | |
| pParent->xFullPathname(pParent, zLog, pParent->mxPathname, zFile); | |
| flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MASTER_JOURNAL; | |
| pParent->xDelete(pParent, zFile, 0); | |
| rc = pParent->xOpen(pParent, zFile, p->pLog, flags, &flags); | |
| if( rc==SQLITE_OK ){ | |
| memcpy(p->aBuf, "sqlite_ostrace1.....", 20); | |
| p->iOffset = 0; | |
| p->nBuf = 20; | |
| rc = sqlite3_vfs_register((sqlite3_vfs *)p, 1); | |
| } | |
| if( rc ){ | |
| vfslog_finalize(p); | |
| } | |
| return rc; | |
| } | |
| int sqlite3_vfslog_annotate(const char *zVfs, const char *zMsg){ | |
| sqlite3_vfs *pVfs; | |
| pVfs = sqlite3_vfs_find(zVfs); | |
| if( !pVfs || pVfs->xOpen!=vfslogOpen ){ | |
| return SQLITE_ERROR; | |
| } | |
| vfslog_call(pVfs, OS_ANNOTATE, 0, 0, 0, 0, 0); | |
| vfslog_string(pVfs, zMsg); | |
| return SQLITE_OK; | |
| } | |
| static const char *vfslog_eventname(int eEvent){ | |
| const char *zEvent = 0; | |
| switch( eEvent ){ | |
| case OS_CLOSE: zEvent = "xClose"; break; | |
| case OS_READ: zEvent = "xRead"; break; | |
| case OS_WRITE: zEvent = "xWrite"; break; | |
| case OS_TRUNCATE: zEvent = "xTruncate"; break; | |
| case OS_SYNC: zEvent = "xSync"; break; | |
| case OS_FILESIZE: zEvent = "xFilesize"; break; | |
| case OS_LOCK: zEvent = "xLock"; break; | |
| case OS_UNLOCK: zEvent = "xUnlock"; break; | |
| case OS_CHECKRESERVEDLOCK: zEvent = "xCheckResLock"; break; | |
| case OS_FILECONTROL: zEvent = "xFileControl"; break; | |
| case OS_SECTORSIZE: zEvent = "xSectorSize"; break; | |
| case OS_DEVCHAR: zEvent = "xDeviceChar"; break; | |
| case OS_OPEN: zEvent = "xOpen"; break; | |
| case OS_DELETE: zEvent = "xDelete"; break; | |
| case OS_ACCESS: zEvent = "xAccess"; break; | |
| case OS_FULLPATHNAME: zEvent = "xFullPathname"; break; | |
| case OS_RANDOMNESS: zEvent = "xRandomness"; break; | |
| case OS_SLEEP: zEvent = "xSleep"; break; | |
| case OS_CURRENTTIME: zEvent = "xCurrentTime"; break; | |
| case OS_SHMUNMAP: zEvent = "xShmUnmap"; break; | |
| case OS_SHMLOCK: zEvent = "xShmLock"; break; | |
| case OS_SHMBARRIER: zEvent = "xShmBarrier"; break; | |
| case OS_SHMMAP: zEvent = "xShmMap"; break; | |
| case OS_ANNOTATE: zEvent = "annotation"; break; | |
| } | |
| return zEvent; | |
| } | |
| typedef struct VfslogVtab VfslogVtab; | |
| typedef struct VfslogCsr VfslogCsr; | |
| /* | |
| ** Virtual table type for the vfslog reader module. | |
| */ | |
| struct VfslogVtab { | |
| sqlite3_vtab base; /* Base class */ | |
| sqlite3_file *pFd; /* File descriptor open on vfslog file */ | |
| sqlite3_int64 nByte; /* Size of file in bytes */ | |
| char *zFile; /* File name for pFd */ | |
| }; | |
| /* | |
| ** Virtual table cursor type for the vfslog reader module. | |
| */ | |
| struct VfslogCsr { | |
| sqlite3_vtab_cursor base; /* Base class */ | |
| sqlite3_int64 iRowid; /* Current rowid. */ | |
| sqlite3_int64 iOffset; /* Offset of next record in file */ | |
| char *zTransient; /* Transient 'file' string */ | |
| int nFile; /* Size of array azFile[] */ | |
| char **azFile; /* File strings */ | |
| unsigned char aBuf[1024]; /* Current vfs log entry (read from file) */ | |
| }; | |
| static unsigned int get32bits(unsigned char *p){ | |
| return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; | |
| } | |
| /* | |
| ** The argument must point to a buffer containing a nul-terminated string. | |
| ** If the string begins with an SQL quote character it is overwritten by | |
| ** the dequoted version. Otherwise the buffer is left unmodified. | |
| */ | |
| static void dequote(char *z){ | |
| char quote; /* Quote character (if any ) */ | |
| quote = z[0]; | |
| if( quote=='[' || quote=='\'' || quote=='"' || quote=='`' ){ | |
| int iIn = 1; /* Index of next byte to read from input */ | |
| int iOut = 0; /* Index of next byte to write to output */ | |
| if( quote=='[' ) quote = ']'; | |
| while( z[iIn] ){ | |
| if( z[iIn]==quote ){ | |
| if( z[iIn+1]!=quote ) break; | |
| z[iOut++] = quote; | |
| iIn += 2; | |
| }else{ | |
| z[iOut++] = z[iIn++]; | |
| } | |
| } | |
| z[iOut] = '\0'; | |
| } | |
| } | |
| #ifndef SQLITE_OMIT_VIRTUALTABLE | |
| /* | |
| ** Connect to or create a vfslog virtual table. | |
| */ | |
| static int vlogConnect( | |
| sqlite3 *db, | |
| void *pAux, | |
| int argc, const char *const*argv, | |
| sqlite3_vtab **ppVtab, | |
| char **pzErr | |
| ){ | |
| sqlite3_vfs *pVfs; /* VFS used to read log file */ | |
| int flags; /* flags passed to pVfs->xOpen() */ | |
| VfslogVtab *p; | |
| int rc; | |
| int nByte; | |
| char *zFile; | |
| *ppVtab = 0; | |
| pVfs = sqlite3_vfs_find(0); | |
| nByte = sizeof(VfslogVtab) + pVfs->szOsFile + pVfs->mxPathname; | |
| p = sqlite3_malloc(nByte); | |
| if( p==0 ) return SQLITE_NOMEM; | |
| memset(p, 0, nByte); | |
| p->pFd = (sqlite3_file *)&p[1]; | |
| p->zFile = &((char *)p->pFd)[pVfs->szOsFile]; | |
| zFile = sqlite3_mprintf("%s", argv[3]); | |
| if( !zFile ){ | |
| sqlite3_free(p); | |
| return SQLITE_NOMEM; | |
| } | |
| dequote(zFile); | |
| pVfs->xFullPathname(pVfs, zFile, pVfs->mxPathname, p->zFile); | |
| sqlite3_free(zFile); | |
| flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_MASTER_JOURNAL; | |
| rc = pVfs->xOpen(pVfs, p->zFile, p->pFd, flags, &flags); | |
| if( rc==SQLITE_OK ){ | |
| p->pFd->pMethods->xFileSize(p->pFd, &p->nByte); | |
| sqlite3_declare_vtab(db, | |
| "CREATE TABLE xxx(event, file, click, rc, size, offset)" | |
| ); | |
| *ppVtab = &p->base; | |
| }else{ | |
| sqlite3_free(p); | |
| } | |
| return rc; | |
| } | |
| /* | |
| ** There is no "best-index". This virtual table always does a linear | |
| ** scan of the binary VFS log file. | |
| */ | |
| static int vlogBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ | |
| pIdxInfo->estimatedCost = 10.0; | |
| return SQLITE_OK; | |
| } | |
| /* | |
| ** Disconnect from or destroy a vfslog virtual table. | |
| */ | |
| static int vlogDisconnect(sqlite3_vtab *pVtab){ | |
| VfslogVtab *p = (VfslogVtab *)pVtab; | |
| if( p->pFd->pMethods ){ | |
| p->pFd->pMethods->xClose(p->pFd); | |
| p->pFd->pMethods = 0; | |
| } | |
| sqlite3_free(p); | |
| return SQLITE_OK; | |
| } | |
| /* | |
| ** Open a new vfslog cursor. | |
| */ | |
| static int vlogOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ | |
| VfslogCsr *pCsr; /* Newly allocated cursor object */ | |
| pCsr = sqlite3_malloc(sizeof(VfslogCsr)); | |
| if( !pCsr ) return SQLITE_NOMEM; | |
| memset(pCsr, 0, sizeof(VfslogCsr)); | |
| *ppCursor = &pCsr->base; | |
| return SQLITE_OK; | |
| } | |
| /* | |
| ** Close a vfslog cursor. | |
| */ | |
| static int vlogClose(sqlite3_vtab_cursor *pCursor){ | |
| VfslogCsr *p = (VfslogCsr *)pCursor; | |
| int i; | |
| for(i=0; i<p->nFile; i++){ | |
| sqlite3_free(p->azFile[i]); | |
| } | |
| sqlite3_free(p->azFile); | |
| sqlite3_free(p->zTransient); | |
| sqlite3_free(p); | |
| return SQLITE_OK; | |
| } | |
| /* | |
| ** Move a vfslog cursor to the next entry in the file. | |
| */ | |
| static int vlogNext(sqlite3_vtab_cursor *pCursor){ | |
| VfslogCsr *pCsr = (VfslogCsr *)pCursor; | |
| VfslogVtab *p = (VfslogVtab *)pCursor->pVtab; | |
| int rc = SQLITE_OK; | |
| int nRead; | |
| sqlite3_free(pCsr->zTransient); | |
| pCsr->zTransient = 0; | |
| nRead = 24; | |
| if( pCsr->iOffset+nRead<=p->nByte ){ | |
| int eEvent; | |
| rc = p->pFd->pMethods->xRead(p->pFd, pCsr->aBuf, nRead, pCsr->iOffset); | |
| eEvent = get32bits(pCsr->aBuf); | |
| if( (rc==SQLITE_OK) | |
| && (eEvent==OS_OPEN || eEvent==OS_DELETE || eEvent==OS_ACCESS) | |
| ){ | |
| char buf[4]; | |
| rc = p->pFd->pMethods->xRead(p->pFd, buf, 4, pCsr->iOffset+nRead); | |
| nRead += 4; | |
| if( rc==SQLITE_OK ){ | |
| int nStr = get32bits((unsigned char *)buf); | |
| char *zStr = sqlite3_malloc(nStr+1); | |
| rc = p->pFd->pMethods->xRead(p->pFd, zStr, nStr, pCsr->iOffset+nRead); | |
| zStr[nStr] = '\0'; | |
| nRead += nStr; | |
| if( eEvent==OS_OPEN ){ | |
| int iFileid = get32bits(&pCsr->aBuf[4]); | |
| if( iFileid>=pCsr->nFile ){ | |
| int nNew = sizeof(pCsr->azFile[0])*(iFileid+1); | |
| pCsr->azFile = (char **)sqlite3_realloc(pCsr->azFile, nNew); | |
| nNew -= sizeof(pCsr->azFile[0])*pCsr->nFile; | |
| memset(&pCsr->azFile[pCsr->nFile], 0, nNew); | |
| pCsr->nFile = iFileid+1; | |
| } | |
| sqlite3_free(pCsr->azFile[iFileid]); | |
| pCsr->azFile[iFileid] = zStr; | |
| }else{ | |
| pCsr->zTransient = zStr; | |
| } | |
| } | |
| } | |
| } | |
| pCsr->iRowid += 1; | |
| pCsr->iOffset += nRead; | |
| return rc; | |
| } | |
| static int vlogEof(sqlite3_vtab_cursor *pCursor){ | |
| VfslogCsr *pCsr = (VfslogCsr *)pCursor; | |
| VfslogVtab *p = (VfslogVtab *)pCursor->pVtab; | |
| return (pCsr->iOffset>=p->nByte); | |
| } | |
| static int vlogFilter( | |
| sqlite3_vtab_cursor *pCursor, | |
| int idxNum, const char *idxStr, | |
| int argc, sqlite3_value **argv | |
| ){ | |
| VfslogCsr *pCsr = (VfslogCsr *)pCursor; | |
| pCsr->iRowid = 0; | |
| pCsr->iOffset = 20; | |
| return vlogNext(pCursor); | |
| } | |
| static int vlogColumn( | |
| sqlite3_vtab_cursor *pCursor, | |
| sqlite3_context *ctx, | |
| int i | |
| ){ | |
| unsigned int val; | |
| VfslogCsr *pCsr = (VfslogCsr *)pCursor; | |
| assert( i<7 ); | |
| val = get32bits(&pCsr->aBuf[4*i]); | |
| switch( i ){ | |
| case 0: { | |
| sqlite3_result_text(ctx, vfslog_eventname(val), -1, SQLITE_STATIC); | |
| break; | |
| } | |
| case 1: { | |
| char *zStr = pCsr->zTransient; | |
| if( val!=0 && val<pCsr->nFile ){ | |
| zStr = pCsr->azFile[val]; | |
| } | |
| sqlite3_result_text(ctx, zStr, -1, SQLITE_TRANSIENT); | |
| break; | |
| } | |
| default: | |
| sqlite3_result_int(ctx, val); | |
| break; | |
| } | |
| return SQLITE_OK; | |
| } | |
| static int vlogRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ | |
| VfslogCsr *pCsr = (VfslogCsr *)pCursor; | |
| *pRowid = pCsr->iRowid; | |
| return SQLITE_OK; | |
| } | |
| int sqlite3_vfslog_register(sqlite3 *db){ | |
| static sqlite3_module vfslog_module = { | |
| 0, /* iVersion */ | |
| vlogConnect, /* xCreate */ | |
| vlogConnect, /* xConnect */ | |
| vlogBestIndex, /* xBestIndex */ | |
| vlogDisconnect, /* xDisconnect */ | |
| vlogDisconnect, /* xDestroy */ | |
| vlogOpen, /* xOpen - open a cursor */ | |
| vlogClose, /* xClose - close a cursor */ | |
| vlogFilter, /* xFilter - configure scan constraints */ | |
| vlogNext, /* xNext - advance a cursor */ | |
| vlogEof, /* xEof - check for end of scan */ | |
| vlogColumn, /* xColumn - read data */ | |
| vlogRowid, /* xRowid - read data */ | |
| 0, /* xUpdate */ | |
| 0, /* xBegin */ | |
| 0, /* xSync */ | |
| 0, /* xCommit */ | |
| 0, /* xRollback */ | |
| 0, /* xFindMethod */ | |
| 0, /* xRename */ | |
| }; | |
| sqlite3_create_module(db, "vfslog", &vfslog_module, 0); | |
| return SQLITE_OK; | |
| } | |
| #endif /* SQLITE_OMIT_VIRTUALTABLE */ | |
| /************************************************************************** | |
| *************************************************************************** | |
| ** Tcl interface starts here. | |
| */ | |
| #if defined(SQLITE_TEST) || defined(TCLSH) | |
| #include <tcl.h> | |
| static int test_vfslog( | |
| void *clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| struct SqliteDb { sqlite3 *db; }; | |
| sqlite3 *db; | |
| Tcl_CmdInfo cmdInfo; | |
| int rc = SQLITE_ERROR; | |
| static const char *strs[] = { "annotate", "finalize", "new", "register", 0 }; | |
| enum VL_enum { VL_ANNOTATE, VL_FINALIZE, VL_NEW, VL_REGISTER }; | |
| int iSub; | |
| if( objc<2 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ..."); | |
| return TCL_ERROR; | |
| } | |
| if( Tcl_GetIndexFromObj(interp, objv[1], strs, "sub-command", 0, &iSub) ){ | |
| return TCL_ERROR; | |
| } | |
| switch( (enum VL_enum)iSub ){ | |
| case VL_ANNOTATE: { | |
| int rc; | |
| char *zVfs; | |
| char *zMsg; | |
| if( objc!=4 ){ | |
| Tcl_WrongNumArgs(interp, 3, objv, "VFS"); | |
| return TCL_ERROR; | |
| } | |
| zVfs = Tcl_GetString(objv[2]); | |
| zMsg = Tcl_GetString(objv[3]); | |
| rc = sqlite3_vfslog_annotate(zVfs, zMsg); | |
| if( rc!=SQLITE_OK ){ | |
| Tcl_AppendResult(interp, "failed", 0); | |
| return TCL_ERROR; | |
| } | |
| break; | |
| } | |
| case VL_FINALIZE: { | |
| int rc; | |
| char *zVfs; | |
| if( objc!=3 ){ | |
| Tcl_WrongNumArgs(interp, 2, objv, "VFS"); | |
| return TCL_ERROR; | |
| } | |
| zVfs = Tcl_GetString(objv[2]); | |
| rc = sqlite3_vfslog_finalize(zVfs); | |
| if( rc!=SQLITE_OK ){ | |
| Tcl_AppendResult(interp, "failed", 0); | |
| return TCL_ERROR; | |
| } | |
| break; | |
| }; | |
| case VL_NEW: { | |
| int rc; | |
| char *zVfs; | |
| char *zParent; | |
| char *zLog; | |
| if( objc!=5 ){ | |
| Tcl_WrongNumArgs(interp, 2, objv, "VFS PARENT LOGFILE"); | |
| return TCL_ERROR; | |
| } | |
| zVfs = Tcl_GetString(objv[2]); | |
| zParent = Tcl_GetString(objv[3]); | |
| zLog = Tcl_GetString(objv[4]); | |
| if( *zParent=='\0' ) zParent = 0; | |
| rc = sqlite3_vfslog_new(zVfs, zParent, zLog); | |
| if( rc!=SQLITE_OK ){ | |
| Tcl_AppendResult(interp, "failed", 0); | |
| return TCL_ERROR; | |
| } | |
| break; | |
| }; | |
| case VL_REGISTER: { | |
| char *zDb; | |
| if( objc!=3 ){ | |
| Tcl_WrongNumArgs(interp, 2, objv, "DB"); | |
| return TCL_ERROR; | |
| } | |
| #ifdef SQLITE_OMIT_VIRTUALTABLE | |
| Tcl_AppendResult(interp, "vfslog not available because of " | |
| "SQLITE_OMIT_VIRTUALTABLE", (void*)0); | |
| return TCL_ERROR; | |
| #else | |
| zDb = Tcl_GetString(objv[2]); | |
| if( Tcl_GetCommandInfo(interp, zDb, &cmdInfo) ){ | |
| db = ((struct SqliteDb*)cmdInfo.objClientData)->db; | |
| rc = sqlite3_vfslog_register(db); | |
| } | |
| if( rc!=SQLITE_OK ){ | |
| Tcl_AppendResult(interp, "bad sqlite3 handle: ", zDb, (void*)0); | |
| return TCL_ERROR; | |
| } | |
| break; | |
| #endif | |
| } | |
| } | |
| return TCL_OK; | |
| } | |
| int SqlitetestOsinst_Init(Tcl_Interp *interp){ | |
| Tcl_CreateObjCommand(interp, "vfslog", test_vfslog, 0, 0); | |
| return TCL_OK; | |
| } | |
| #endif /* SQLITE_TEST */ |