+ pCur->nPrefix = pCur->nLine - i;
+ if( pCur->nPrefix>0 ){
+ pCur->zPrefix = sqlite3_mprintf("%.*s", pCur->nPrefix, pCur->zLine + i);
+ if( pCur->zPrefix==0 ) return SQLITE_NOMEM;
+ }
+ }
+ pCur->iRowid = 0;
+ pCur->ePhase = COMPLETION_FIRST_PHASE;
+ return completionNext(pVtabCursor);
+}
+
+/*
+** SQLite will invoke this method one or more times while planning a query
+** that uses the completion virtual table. This routine needs to create
+** a query plan for each invocation and compute an estimated cost for that
+** plan.
+**
+** There are two hidden parameters that act as arguments to the table-valued
+** function: "prefix" and "wholeline". Bit 0 of idxNum is set if "prefix"
+** is available and bit 1 is set if "wholeline" is available.
+*/
+static int completionBestIndex(
+ sqlite3_vtab *tab,
+ sqlite3_index_info *pIdxInfo
+){
+ int i; /* Loop over constraints */
+ int idxNum = 0; /* The query plan bitmask */
+ int prefixIdx = -1; /* Index of the start= constraint, or -1 if none */
+ int wholelineIdx = -1; /* Index of the stop= constraint, or -1 if none */
+ int nArg = 0; /* Number of arguments that completeFilter() expects */
+ const struct sqlite3_index_constraint *pConstraint;
+
+ (void)(tab); /* Unused parameter */
+ pConstraint = pIdxInfo->aConstraint;
+ for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
+ if( pConstraint->usable==0 ) continue;
+ if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
+ switch( pConstraint->iColumn ){
+ case COMPLETION_COLUMN_PREFIX:
+ prefixIdx = i;
+ idxNum |= 1;
+ break;
+ case COMPLETION_COLUMN_WHOLELINE:
+ wholelineIdx = i;
+ idxNum |= 2;
+ break;
+ }
+ }
+ if( prefixIdx>=0 ){
+ pIdxInfo->aConstraintUsage[prefixIdx].argvIndex = ++nArg;
+ pIdxInfo->aConstraintUsage[prefixIdx].omit = 1;
+ }
+ if( wholelineIdx>=0 ){
+ pIdxInfo->aConstraintUsage[wholelineIdx].argvIndex = ++nArg;
+ pIdxInfo->aConstraintUsage[wholelineIdx].omit = 1;
+ }
+ pIdxInfo->idxNum = idxNum;
+ pIdxInfo->estimatedCost = (double)5000 - 1000*nArg;
+ pIdxInfo->estimatedRows = 500 - 100*nArg;
+ return SQLITE_OK;
+}
+
+/*
+** This following structure defines all the methods for the
+** completion virtual table.
+*/
+static sqlite3_module completionModule = {
+ 0, /* iVersion */
+ 0, /* xCreate */
+ completionConnect, /* xConnect */
+ completionBestIndex, /* xBestIndex */
+ completionDisconnect, /* xDisconnect */
+ 0, /* xDestroy */
+ completionOpen, /* xOpen - open a cursor */
+ completionClose, /* xClose - close a cursor */
+ completionFilter, /* xFilter - configure scan constraints */
+ completionNext, /* xNext - advance a cursor */
+ completionEof, /* xEof - check for end of scan */
+ completionColumn, /* xColumn - read data */
+ completionRowid, /* xRowid - read data */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0 /* xShadowName */
+};
+
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+int sqlite3CompletionVtabInit(sqlite3 *db){
+ int rc = SQLITE_OK;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ rc = sqlite3_create_module(db, "completion", &completionModule, 0);
+#endif
+ return rc;
+}
+
+#ifdef _WIN32
+
+#endif
+int sqlite3_completion_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ int rc = SQLITE_OK;
+ SQLITE_EXTENSION_INIT2(pApi);
+ (void)(pzErrMsg); /* Unused parameter */
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ rc = sqlite3CompletionVtabInit(db);
+#endif
+ return rc;
+}
+
+/************************* End ../ext/misc/completion.c ********************/
+/************************* Begin ../ext/misc/appendvfs.c ******************/
+/*
+** 2017-10-20
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file implements a VFS shim that allows an SQLite database to be
+** appended onto the end of some other file, such as an executable.
+**
+** A special record must appear at the end of the file that identifies the
+** file as an appended database and provides an offset to page 1. For
+** best performance page 1 should be located at a disk page boundary, though
+** that is not required.
+**
+** When opening a database using this VFS, the connection might treat
+** the file as an ordinary SQLite database, or it might treat is as a
+** database appended onto some other file. Here are the rules:
+**
+** (1) When opening a new empty file, that file is treated as an ordinary
+** database.
+**
+** (2) When opening a file that begins with the standard SQLite prefix
+** string "SQLite format 3", that file is treated as an ordinary
+** database.
+**
+** (3) When opening a file that ends with the appendvfs trailer string
+** "Start-Of-SQLite3-NNNNNNNN" that file is treated as an appended
+** database.
+**
+** (4) If none of the above apply and the SQLITE_OPEN_CREATE flag is
+** set, then a new database is appended to the already existing file.
+**
+** (5) Otherwise, SQLITE_CANTOPEN is returned.
+**
+** To avoid unnecessary complications with the PENDING_BYTE, the size of
+** the file containing the database is limited to 1GB. This VFS will refuse
+** to read or write past the 1GB mark. This restriction might be lifted in
+** future versions. For now, if you need a large database, then keep the
+** database in a separate file.
+**
+** If the file being opened is not an appended database, then this shim is
+** a pass-through into the default underlying VFS.
+**/
+SQLITE_EXTENSION_INIT1
+#include <string.h>
+#include <assert.h>
+
+/* The append mark at the end of the database is:
+**
+** Start-Of-SQLite3-NNNNNNNN
+** 123456789 123456789 12345
+**
+** The NNNNNNNN represents a 64-bit big-endian unsigned integer which is
+** the offset to page 1.
+*/
+#define APND_MARK_PREFIX "Start-Of-SQLite3-"
+#define APND_MARK_PREFIX_SZ 17
+#define APND_MARK_SIZE 25
+
+/*
+** Maximum size of the combined prefix + database + append-mark. This
+** must be less than 0x40000000 to avoid locking issues on Windows.
+*/
+#define APND_MAX_SIZE (65536*15259)
+
+/*
+** Forward declaration of objects used by this utility
+*/
+typedef struct sqlite3_vfs ApndVfs;
+typedef struct ApndFile ApndFile;
+
+/* Access to a lower-level VFS that (might) implement dynamic loading,
+** access to randomness, etc.
+*/
+#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData))
+#define ORIGFILE(p) ((sqlite3_file*)(((ApndFile*)(p))+1))
+
+/* An open file */
+struct ApndFile {
+ sqlite3_file base; /* IO methods */
+ sqlite3_int64 iPgOne; /* File offset to page 1 */
+ sqlite3_int64 iMark; /* Start of the append-mark */
+};
+
+/*
+** Methods for ApndFile
+*/
+static int apndClose(sqlite3_file*);
+static int apndRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
+static int apndWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
+static int apndTruncate(sqlite3_file*, sqlite3_int64 size);
+static int apndSync(sqlite3_file*, int flags);
+static int apndFileSize(sqlite3_file*, sqlite3_int64 *pSize);
+static int apndLock(sqlite3_file*, int);
+static int apndUnlock(sqlite3_file*, int);
+static int apndCheckReservedLock(sqlite3_file*, int *pResOut);
+static int apndFileControl(sqlite3_file*, int op, void *pArg);
+static int apndSectorSize(sqlite3_file*);
+static int apndDeviceCharacteristics(sqlite3_file*);
+static int apndShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
+static int apndShmLock(sqlite3_file*, int offset, int n, int flags);
+static void apndShmBarrier(sqlite3_file*);
+static int apndShmUnmap(sqlite3_file*, int deleteFlag);
+static int apndFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
+static int apndUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
+
+/*
+** Methods for ApndVfs
+*/
+static int apndOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
+static int apndDelete(sqlite3_vfs*, const char *zName, int syncDir);
+static int apndAccess(sqlite3_vfs*, const char *zName, int flags, int *);
+static int apndFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
+static void *apndDlOpen(sqlite3_vfs*, const char *zFilename);
+static void apndDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
+static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void);
+static void apndDlClose(sqlite3_vfs*, void*);
+static int apndRandomness(sqlite3_vfs*, int nByte, char *zOut);
+static int apndSleep(sqlite3_vfs*, int microseconds);
+static int apndCurrentTime(sqlite3_vfs*, double*);
+static int apndGetLastError(sqlite3_vfs*, int, char *);
+static int apndCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
+static int apndSetSystemCall(sqlite3_vfs*, const char*,sqlite3_syscall_ptr);
+static sqlite3_syscall_ptr apndGetSystemCall(sqlite3_vfs*, const char *z);
+static const char *apndNextSystemCall(sqlite3_vfs*, const char *zName);
+
+static sqlite3_vfs apnd_vfs = {
+ 3, /* iVersion (set when registered) */
+ 0, /* szOsFile (set when registered) */
+ 1024, /* mxPathname */
+ 0, /* pNext */
+ "apndvfs", /* zName */
+ 0, /* pAppData (set when registered) */
+ apndOpen, /* xOpen */
+ apndDelete, /* xDelete */
+ apndAccess, /* xAccess */
+ apndFullPathname, /* xFullPathname */
+ apndDlOpen, /* xDlOpen */
+ apndDlError, /* xDlError */
+ apndDlSym, /* xDlSym */
+ apndDlClose, /* xDlClose */
+ apndRandomness, /* xRandomness */
+ apndSleep, /* xSleep */
+ apndCurrentTime, /* xCurrentTime */
+ apndGetLastError, /* xGetLastError */
+ apndCurrentTimeInt64, /* xCurrentTimeInt64 */
+ apndSetSystemCall, /* xSetSystemCall */
+ apndGetSystemCall, /* xGetSystemCall */
+ apndNextSystemCall /* xNextSystemCall */
+};
+
+static const sqlite3_io_methods apnd_io_methods = {
+ 3, /* iVersion */
+ apndClose, /* xClose */
+ apndRead, /* xRead */
+ apndWrite, /* xWrite */
+ apndTruncate, /* xTruncate */
+ apndSync, /* xSync */
+ apndFileSize, /* xFileSize */
+ apndLock, /* xLock */
+ apndUnlock, /* xUnlock */
+ apndCheckReservedLock, /* xCheckReservedLock */
+ apndFileControl, /* xFileControl */
+ apndSectorSize, /* xSectorSize */
+ apndDeviceCharacteristics, /* xDeviceCharacteristics */
+ apndShmMap, /* xShmMap */
+ apndShmLock, /* xShmLock */
+ apndShmBarrier, /* xShmBarrier */
+ apndShmUnmap, /* xShmUnmap */
+ apndFetch, /* xFetch */
+ apndUnfetch /* xUnfetch */
+};
+
+
+
+/*
+** Close an apnd-file.
+*/
+static int apndClose(sqlite3_file *pFile){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xClose(pFile);
+}
+
+/*
+** Read data from an apnd-file.
+*/
+static int apndRead(
+ sqlite3_file *pFile,
+ void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ ApndFile *p = (ApndFile *)pFile;
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xRead(pFile, zBuf, iAmt, iOfst+p->iPgOne);
+}
+
+/*
+** Add the append-mark onto the end of the file.
+*/
+static int apndWriteMark(ApndFile *p, sqlite3_file *pFile){
+ int i;
+ unsigned char a[APND_MARK_SIZE];
+ memcpy(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ);
+ for(i=0; i<8; i++){
+ a[APND_MARK_PREFIX_SZ+i] = (p->iPgOne >> (56 - i*8)) & 0xff;
+ }
+ return pFile->pMethods->xWrite(pFile, a, APND_MARK_SIZE, p->iMark);
+}
+
+/*
+** Write data to an apnd-file.
+*/
+static int apndWrite(
+ sqlite3_file *pFile,
+ const void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ int rc;
+ ApndFile *p = (ApndFile *)pFile;
+ pFile = ORIGFILE(pFile);
+ if( iOfst+iAmt>=APND_MAX_SIZE ) return SQLITE_FULL;
+ rc = pFile->pMethods->xWrite(pFile, zBuf, iAmt, iOfst+p->iPgOne);
+ if( rc==SQLITE_OK && iOfst + iAmt + p->iPgOne > p->iMark ){
+ sqlite3_int64 sz = 0;
+ rc = pFile->pMethods->xFileSize(pFile, &sz);
+ if( rc==SQLITE_OK ){
+ p->iMark = sz - APND_MARK_SIZE;
+ if( iOfst + iAmt + p->iPgOne > p->iMark ){
+ p->iMark = p->iPgOne + iOfst + iAmt;
+ rc = apndWriteMark(p, pFile);
+ }
+ }
+ }
+ return rc;
+}
+
+/*
+** Truncate an apnd-file.
+*/
+static int apndTruncate(sqlite3_file *pFile, sqlite_int64 size){
+ int rc;
+ ApndFile *p = (ApndFile *)pFile;
+ pFile = ORIGFILE(pFile);
+ rc = pFile->pMethods->xTruncate(pFile, size+p->iPgOne+APND_MARK_SIZE);
+ if( rc==SQLITE_OK ){
+ p->iMark = p->iPgOne+size;
+ rc = apndWriteMark(p, pFile);
+ }
+ return rc;
+}
+
+/*
+** Sync an apnd-file.
+*/
+static int apndSync(sqlite3_file *pFile, int flags){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xSync(pFile, flags);
+}
+
+/*
+** Return the current file-size of an apnd-file.
+*/
+static int apndFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+ ApndFile *p = (ApndFile *)pFile;
+ int rc;
+ pFile = ORIGFILE(p);
+ rc = pFile->pMethods->xFileSize(pFile, pSize);
+ if( rc==SQLITE_OK && p->iPgOne ){
+ *pSize -= p->iPgOne + APND_MARK_SIZE;
+ }
+ return rc;
+}
+
+/*
+** Lock an apnd-file.
+*/
+static int apndLock(sqlite3_file *pFile, int eLock){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xLock(pFile, eLock);
+}
+
+/*
+** Unlock an apnd-file.
+*/
+static int apndUnlock(sqlite3_file *pFile, int eLock){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xUnlock(pFile, eLock);
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on an apnd-file.
+*/
+static int apndCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xCheckReservedLock(pFile, pResOut);
+}
+
+/*
+** File control method. For custom operations on an apnd-file.
+*/
+static int apndFileControl(sqlite3_file *pFile, int op, void *pArg){
+ ApndFile *p = (ApndFile *)pFile;
+ int rc;
+ pFile = ORIGFILE(pFile);
+ rc = pFile->pMethods->xFileControl(pFile, op, pArg);
+ if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){
+ *(char**)pArg = sqlite3_mprintf("apnd(%lld)/%z", p->iPgOne, *(char**)pArg);
+ }
+ return rc;
+}
+
+/*
+** Return the sector-size in bytes for an apnd-file.
+*/
+static int apndSectorSize(sqlite3_file *pFile){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xSectorSize(pFile);
+}
+
+/*
+** Return the device characteristic flags supported by an apnd-file.
+*/
+static int apndDeviceCharacteristics(sqlite3_file *pFile){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xDeviceCharacteristics(pFile);
+}
+
+/* Create a shared memory file mapping */
+static int apndShmMap(
+ sqlite3_file *pFile,
+ int iPg,
+ int pgsz,
+ int bExtend,
+ void volatile **pp
+){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xShmMap(pFile,iPg,pgsz,bExtend,pp);
+}
+
+/* Perform locking on a shared-memory segment */
+static int apndShmLock(sqlite3_file *pFile, int offset, int n, int flags){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xShmLock(pFile,offset,n,flags);
+}
+
+/* Memory barrier operation on shared memory */
+static void apndShmBarrier(sqlite3_file *pFile){
+ pFile = ORIGFILE(pFile);
+ pFile->pMethods->xShmBarrier(pFile);
+}
+
+/* Unmap a shared memory segment */
+static int apndShmUnmap(sqlite3_file *pFile, int deleteFlag){
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xShmUnmap(pFile,deleteFlag);
+}
+
+/* Fetch a page of a memory-mapped file */
+static int apndFetch(
+ sqlite3_file *pFile,
+ sqlite3_int64 iOfst,
+ int iAmt,
+ void **pp
+){
+ ApndFile *p = (ApndFile *)pFile;
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xFetch(pFile, iOfst+p->iPgOne, iAmt, pp);
+}
+
+/* Release a memory-mapped page */
+static int apndUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
+ ApndFile *p = (ApndFile *)pFile;
+ pFile = ORIGFILE(pFile);
+ return pFile->pMethods->xUnfetch(pFile, iOfst+p->iPgOne, pPage);
+}
+
+/*
+** Check to see if the file is an ordinary SQLite database file.
+*/
+static int apndIsOrdinaryDatabaseFile(sqlite3_int64 sz, sqlite3_file *pFile){
+ int rc;
+ char zHdr[16];
+ static const char aSqliteHdr[] = "SQLite format 3";
+ if( sz<512 ) return 0;
+ rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), 0);
+ if( rc ) return 0;
+ return memcmp(zHdr, aSqliteHdr, sizeof(zHdr))==0;
+}
+
+/*
+** Try to read the append-mark off the end of a file. Return the
+** start of the appended database if the append-mark is present. If
+** there is no append-mark, return -1;
+*/
+static sqlite3_int64 apndReadMark(sqlite3_int64 sz, sqlite3_file *pFile){
+ int rc, i;
+ sqlite3_int64 iMark;
+ unsigned char a[APND_MARK_SIZE];
+
+ if( sz<=APND_MARK_SIZE ) return -1;
+ rc = pFile->pMethods->xRead(pFile, a, APND_MARK_SIZE, sz-APND_MARK_SIZE);
+ if( rc ) return -1;
+ if( memcmp(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ)!=0 ) return -1;
+ iMark = ((sqlite3_int64)(a[APND_MARK_PREFIX_SZ]&0x7f))<<56;
+ for(i=1; i<8; i++){
+ iMark += (sqlite3_int64)a[APND_MARK_PREFIX_SZ+i]<<(56-8*i);
+ }
+ return iMark;
+}
+
+/*
+** Open an apnd file handle.
+*/
+static int apndOpen(
+ sqlite3_vfs *pVfs,
+ const char *zName,
+ sqlite3_file *pFile,
+ int flags,
+ int *pOutFlags
+){
+ ApndFile *p;
+ sqlite3_file *pSubFile;
+ sqlite3_vfs *pSubVfs;
+ int rc;
+ sqlite3_int64 sz;
+ pSubVfs = ORIGVFS(pVfs);
+ if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){
+ return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags);
+ }
+ p = (ApndFile*)pFile;
+ memset(p, 0, sizeof(*p));
+ pSubFile = ORIGFILE(pFile);
+ p->base.pMethods = &apnd_io_methods;
+ rc = pSubVfs->xOpen(pSubVfs, zName, pSubFile, flags, pOutFlags);
+ if( rc ) goto apnd_open_done;
+ rc = pSubFile->pMethods->xFileSize(pSubFile, &sz);
+ if( rc ){
+ pSubFile->pMethods->xClose(pSubFile);
+ goto apnd_open_done;
+ }
+ if( apndIsOrdinaryDatabaseFile(sz, pSubFile) ){
+ memmove(pFile, pSubFile, pSubVfs->szOsFile);
+ return SQLITE_OK;
+ }
+ p->iMark = 0;
+ p->iPgOne = apndReadMark(sz, pFile);
+ if( p->iPgOne>0 ){
+ return SQLITE_OK;
+ }
+ if( (flags & SQLITE_OPEN_CREATE)==0 ){
+ pSubFile->pMethods->xClose(pSubFile);
+ rc = SQLITE_CANTOPEN;
+ }
+ p->iPgOne = (sz+0xfff) & ~(sqlite3_int64)0xfff;
+apnd_open_done:
+ if( rc ) pFile->pMethods = 0;
+ return rc;
+}
+
+/*
+** All other VFS methods are pass-thrus.
+*/
+static int apndDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+ return ORIGVFS(pVfs)->xDelete(ORIGVFS(pVfs), zPath, dirSync);
+}
+static int apndAccess(
+ sqlite3_vfs *pVfs,
+ const char *zPath,
+ int flags,
+ int *pResOut
+){
+ return ORIGVFS(pVfs)->xAccess(ORIGVFS(pVfs), zPath, flags, pResOut);
+}
+static int apndFullPathname(
+ sqlite3_vfs *pVfs,
+ const char *zPath,
+ int nOut,
+ char *zOut
+){
+ return ORIGVFS(pVfs)->xFullPathname(ORIGVFS(pVfs),zPath,nOut,zOut);
+}
+static void *apndDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+ return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath);
+}
+static void apndDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+ ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg);
+}
+static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
+ return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym);
+}
+static void apndDlClose(sqlite3_vfs *pVfs, void *pHandle){
+ ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle);
+}
+static int apndRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+ return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut);
+}
+static int apndSleep(sqlite3_vfs *pVfs, int nMicro){
+ return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro);
+}
+static int apndCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
+ return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut);
+}
+static int apndGetLastError(sqlite3_vfs *pVfs, int a, char *b){
+ return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b);
+}
+static int apndCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){
+ return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p);
+}
+static int apndSetSystemCall(
+ sqlite3_vfs *pVfs,
+ const char *zName,
+ sqlite3_syscall_ptr pCall
+){
+ return ORIGVFS(pVfs)->xSetSystemCall(ORIGVFS(pVfs),zName,pCall);
+}
+static sqlite3_syscall_ptr apndGetSystemCall(
+ sqlite3_vfs *pVfs,
+ const char *zName
+){
+ return ORIGVFS(pVfs)->xGetSystemCall(ORIGVFS(pVfs),zName);
+}
+static const char *apndNextSystemCall(sqlite3_vfs *pVfs, const char *zName){
+ return ORIGVFS(pVfs)->xNextSystemCall(ORIGVFS(pVfs), zName);
+}
+
+
+#ifdef _WIN32
+
+#endif
+/*
+** This routine is called when the extension is loaded.
+** Register the new VFS.
+*/
+int sqlite3_appendvfs_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ int rc = SQLITE_OK;
+ sqlite3_vfs *pOrig;
+ SQLITE_EXTENSION_INIT2(pApi);
+ (void)pzErrMsg;
+ (void)db;
+ pOrig = sqlite3_vfs_find(0);
+ apnd_vfs.iVersion = pOrig->iVersion;
+ apnd_vfs.pAppData = pOrig;
+ apnd_vfs.szOsFile = pOrig->szOsFile + sizeof(ApndFile);
+ rc = sqlite3_vfs_register(&apnd_vfs, 0);
+#ifdef APPENDVFS_TEST
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_auto_extension((void(*)(void))apndvfsRegister);
+ }
+#endif
+ if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
+ return rc;
+}
+
+/************************* End ../ext/misc/appendvfs.c ********************/
+/************************* Begin ../ext/misc/memtrace.c ******************/
+/*
+** 2019-01-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file implements an extension that uses the SQLITE_CONFIG_MALLOC
+** mechanism to add a tracing layer on top of SQLite. If this extension
+** is registered prior to sqlite3_initialize(), it will cause all memory
+** allocation activities to be logged on standard output, or to some other
+** FILE specified by the initializer.
+**
+** This file needs to be compiled into the application that uses it.
+**
+** This extension is used to implement the --memtrace option of the
+** command-line shell.
+*/
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+/* The original memory allocation routines */
+static sqlite3_mem_methods memtraceBase;
+static FILE *memtraceOut;
+
+/* Methods that trace memory allocations */
+static void *memtraceMalloc(int n){
+ if( memtraceOut ){
+ fprintf(memtraceOut, "MEMTRACE: allocate %d bytes\n",
+ memtraceBase.xRoundup(n));
+ }
+ return memtraceBase.xMalloc(n);
+}
+static void memtraceFree(void *p){
+ if( p==0 ) return;
+ if( memtraceOut ){
+ fprintf(memtraceOut, "MEMTRACE: free %d bytes\n", memtraceBase.xSize(p));
+ }
+ memtraceBase.xFree(p);
+}
+static void *memtraceRealloc(void *p, int n){
+ if( p==0 ) return memtraceMalloc(n);
+ if( n==0 ){
+ memtraceFree(p);
+ return 0;
+ }
+ if( memtraceOut ){
+ fprintf(memtraceOut, "MEMTRACE: resize %d -> %d bytes\n",
+ memtraceBase.xSize(p), memtraceBase.xRoundup(n));
+ }
+ return memtraceBase.xRealloc(p, n);
+}
+static int memtraceSize(void *p){
+ return memtraceBase.xSize(p);
+}
+static int memtraceRoundup(int n){
+ return memtraceBase.xRoundup(n);
+}
+static int memtraceInit(void *p){
+ return memtraceBase.xInit(p);
+}
+static void memtraceShutdown(void *p){
+ memtraceBase.xShutdown(p);
+}
+
+/* The substitute memory allocator */
+static sqlite3_mem_methods ersaztMethods = {
+ memtraceMalloc,
+ memtraceFree,
+ memtraceRealloc,
+ memtraceSize,
+ memtraceRoundup,
+ memtraceInit,
+ memtraceShutdown,
+ 0
+};
+
+/* Begin tracing memory allocations to out. */
+int sqlite3MemTraceActivate(FILE *out){
+ int rc = SQLITE_OK;
+ if( memtraceBase.xMalloc==0 ){
+ rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &memtraceBase);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &ersaztMethods);
+ }
+ }
+ memtraceOut = out;
+ return rc;
+}
+
+/* Deactivate memory tracing */
+int sqlite3MemTraceDeactivate(void){
+ int rc = SQLITE_OK;
+ if( memtraceBase.xMalloc!=0 ){
+ rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &memtraceBase);
+ if( rc==SQLITE_OK ){
+ memset(&memtraceBase, 0, sizeof(memtraceBase));
+ }
+ }
+ memtraceOut = 0;
+ return rc;
+}
+
+/************************* End ../ext/misc/memtrace.c ********************/
+#ifdef SQLITE_HAVE_ZLIB
+/************************* Begin ../ext/misc/zipfile.c ******************/
+/*
+** 2017-12-26
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file implements a virtual table for reading and writing ZIP archive
+** files.
+**
+** Usage example:
+**
+** SELECT name, sz, datetime(mtime,'unixepoch') FROM zipfile($filename);
+**
+** Current limitations:
+**
+** * No support for encryption
+** * No support for ZIP archives spanning multiple files
+** * No support for zip64 extensions
+** * Only the "inflate/deflate" (zlib) compression method is supported
+*/
+SQLITE_EXTENSION_INIT1
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <zlib.h>
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+#ifndef SQLITE_AMALGAMATION
+
+/* typedef sqlite3_int64 i64; */
+/* typedef unsigned char u8; */
+typedef unsigned short u16;
+typedef unsigned long u32;
+#define MIN(a,b) ((a)<(b) ? (a) : (b))
+
+#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST)
+# define ALWAYS(X) (1)
+# define NEVER(X) (0)
+#elif !defined(NDEBUG)
+# define ALWAYS(X) ((X)?1:(assert(0),0))
+# define NEVER(X) ((X)?(assert(0),1):0)
+#else
+# define ALWAYS(X) (X)
+# define NEVER(X) (X)
+#endif
+
+#endif /* SQLITE_AMALGAMATION */
+
+/*
+** Definitions for mode bitmasks S_IFDIR, S_IFREG and S_IFLNK.
+**
+** In some ways it would be better to obtain these values from system
+** header files. But, the dependency is undesirable and (a) these
+** have been stable for decades, (b) the values are part of POSIX and
+** are also made explicit in [man stat], and (c) are part of the
+** file format for zip archives.
+*/
+#ifndef S_IFDIR
+# define S_IFDIR 0040000
+#endif
+#ifndef S_IFREG
+# define S_IFREG 0100000
+#endif
+#ifndef S_IFLNK
+# define S_IFLNK 0120000
+#endif
+
+static const char ZIPFILE_SCHEMA[] =
+ "CREATE TABLE y("
+ "name PRIMARY KEY," /* 0: Name of file in zip archive */
+ "mode," /* 1: POSIX mode for file */
+ "mtime," /* 2: Last modification time (secs since 1970)*/
+ "sz," /* 3: Size of object */
+ "rawdata," /* 4: Raw data */
+ "data," /* 5: Uncompressed data */
+ "method," /* 6: Compression method (integer) */
+ "z HIDDEN" /* 7: Name of zip file */
+ ") WITHOUT ROWID;";
+
+#define ZIPFILE_F_COLUMN_IDX 7 /* Index of column "file" in the above */
+#define ZIPFILE_BUFFER_SIZE (64*1024)
+
+
+/*
+** Magic numbers used to read and write zip files.
+**
+** ZIPFILE_NEWENTRY_MADEBY:
+** Use this value for the "version-made-by" field in new zip file
+** entries. The upper byte indicates "unix", and the lower byte
+** indicates that the zip file matches pkzip specification 3.0.
+** This is what info-zip seems to do.
+**
+** ZIPFILE_NEWENTRY_REQUIRED:
+** Value for "version-required-to-extract" field of new entries.
+** Version 2.0 is required to support folders and deflate compression.
+**
+** ZIPFILE_NEWENTRY_FLAGS:
+** Value for "general-purpose-bit-flags" field of new entries. Bit
+** 11 means "utf-8 filename and comment".
+**
+** ZIPFILE_SIGNATURE_CDS:
+** First 4 bytes of a valid CDS record.
+**
+** ZIPFILE_SIGNATURE_LFH:
+** First 4 bytes of a valid LFH record.
+**
+** ZIPFILE_SIGNATURE_EOCD
+** First 4 bytes of a valid EOCD record.
+*/
+#define ZIPFILE_EXTRA_TIMESTAMP 0x5455
+#define ZIPFILE_NEWENTRY_MADEBY ((3<<8) + 30)
+#define ZIPFILE_NEWENTRY_REQUIRED 20
+#define ZIPFILE_NEWENTRY_FLAGS 0x800
+#define ZIPFILE_SIGNATURE_CDS 0x02014b50
+#define ZIPFILE_SIGNATURE_LFH 0x04034b50
+#define ZIPFILE_SIGNATURE_EOCD 0x06054b50
+
+/*
+** The sizes of the fixed-size part of each of the three main data
+** structures in a zip archive.
+*/
+#define ZIPFILE_LFH_FIXED_SZ 30
+#define ZIPFILE_EOCD_FIXED_SZ 22
+#define ZIPFILE_CDS_FIXED_SZ 46
+
+/*
+*** 4.3.16 End of central directory record:
+***
+*** end of central dir signature 4 bytes (0x06054b50)
+*** number of this disk 2 bytes
+*** number of the disk with the
+*** start of the central directory 2 bytes
+*** total number of entries in the
+*** central directory on this disk 2 bytes
+*** total number of entries in
+*** the central directory 2 bytes
+*** size of the central directory 4 bytes
+*** offset of start of central
+*** directory with respect to
+*** the starting disk number 4 bytes
+*** .ZIP file comment length 2 bytes
+*** .ZIP file comment (variable size)
+*/
+typedef struct ZipfileEOCD ZipfileEOCD;
+struct ZipfileEOCD {
+ u16 iDisk;
+ u16 iFirstDisk;
+ u16 nEntry;
+ u16 nEntryTotal;
+ u32 nSize;
+ u32 iOffset;
+};
+
+/*
+*** 4.3.12 Central directory structure:
+***
+*** ...
+***
+*** central file header signature 4 bytes (0x02014b50)
+*** version made by 2 bytes
+*** version needed to extract 2 bytes
+*** general purpose bit flag 2 bytes
+*** compression method 2 bytes
+*** last mod file time 2 bytes
+*** last mod file date 2 bytes
+*** crc-32 4 bytes
+*** compressed size 4 bytes
+*** uncompressed size 4 bytes
+*** file name length 2 bytes
+*** extra field length 2 bytes
+*** file comment length 2 bytes
+*** disk number start 2 bytes
+*** internal file attributes 2 bytes
+*** external file attributes 4 bytes
+*** relative offset of local header 4 bytes
+*/
+typedef struct ZipfileCDS ZipfileCDS;
+struct ZipfileCDS {
+ u16 iVersionMadeBy;
+ u16 iVersionExtract;
+ u16 flags;
+ u16 iCompression;
+ u16 mTime;
+ u16 mDate;
+ u32 crc32;
+ u32 szCompressed;
+ u32 szUncompressed;
+ u16 nFile;
+ u16 nExtra;
+ u16 nComment;
+ u16 iDiskStart;
+ u16 iInternalAttr;
+ u32 iExternalAttr;
+ u32 iOffset;
+ char *zFile; /* Filename (sqlite3_malloc()) */
+};
+
+/*
+*** 4.3.7 Local file header:
+***
+*** local file header signature 4 bytes (0x04034b50)
+*** version needed to extract 2 bytes
+*** general purpose bit flag 2 bytes
+*** compression method 2 bytes
+*** last mod file time 2 bytes
+*** last mod file date 2 bytes
+*** crc-32 4 bytes
+*** compressed size 4 bytes
+*** uncompressed size 4 bytes
+*** file name length 2 bytes
+*** extra field length 2 bytes
+***
+*/
+typedef struct ZipfileLFH ZipfileLFH;
+struct ZipfileLFH {
+ u16 iVersionExtract;
+ u16 flags;
+ u16 iCompression;
+ u16 mTime;
+ u16 mDate;
+ u32 crc32;
+ u32 szCompressed;
+ u32 szUncompressed;
+ u16 nFile;
+ u16 nExtra;
+};
+
+typedef struct ZipfileEntry ZipfileEntry;
+struct ZipfileEntry {
+ ZipfileCDS cds; /* Parsed CDS record */
+ u32 mUnixTime; /* Modification time, in UNIX format */
+ u8 *aExtra; /* cds.nExtra+cds.nComment bytes of extra data */
+ i64 iDataOff; /* Offset to data in file (if aData==0) */
+ u8 *aData; /* cds.szCompressed bytes of compressed data */
+ ZipfileEntry *pNext; /* Next element in in-memory CDS */
+};
+
+/*
+** Cursor type for zipfile tables.
+*/
+typedef struct ZipfileCsr ZipfileCsr;
+struct ZipfileCsr {
+ sqlite3_vtab_cursor base; /* Base class - must be first */
+ i64 iId; /* Cursor ID */
+ u8 bEof; /* True when at EOF */
+ u8 bNoop; /* If next xNext() call is no-op */
+
+ /* Used outside of write transactions */
+ FILE *pFile; /* Zip file */
+ i64 iNextOff; /* Offset of next record in central directory */
+ ZipfileEOCD eocd; /* Parse of central directory record */
+
+ ZipfileEntry *pFreeEntry; /* Free this list when cursor is closed or reset */
+ ZipfileEntry *pCurrent; /* Current entry */
+ ZipfileCsr *pCsrNext; /* Next cursor on same virtual table */
+};
+
+typedef struct ZipfileTab ZipfileTab;
+struct ZipfileTab {
+ sqlite3_vtab base; /* Base class - must be first */
+ char *zFile; /* Zip file this table accesses (may be NULL) */
+ sqlite3 *db; /* Host database connection */
+ u8 *aBuffer; /* Temporary buffer used for various tasks */
+
+ ZipfileCsr *pCsrList; /* List of cursors */
+ i64 iNextCsrid;
+
+ /* The following are used by write transactions only */
+ ZipfileEntry *pFirstEntry; /* Linked list of all files (if pWriteFd!=0) */
+ ZipfileEntry *pLastEntry; /* Last element in pFirstEntry list */
+ FILE *pWriteFd; /* File handle open on zip archive */
+ i64 szCurrent; /* Current size of zip archive */
+ i64 szOrig; /* Size of archive at start of transaction */
+};
+
+/*
+** Set the error message contained in context ctx to the results of
+** vprintf(zFmt, ...).
+*/
+static void zipfileCtxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){
+ char *zMsg = 0;
+ va_list ap;
+ va_start(ap, zFmt);
+ zMsg = sqlite3_vmprintf(zFmt, ap);
+ sqlite3_result_error(ctx, zMsg, -1);
+ sqlite3_free(zMsg);
+ va_end(ap);
+}
+
+/*
+** If string zIn is quoted, dequote it in place. Otherwise, if the string
+** is not quoted, do nothing.
+*/
+static void zipfileDequote(char *zIn){
+ char q = zIn[0];
+ if( q=='"' || q=='\'' || q=='`' || q=='[' ){
+ int iIn = 1;
+ int iOut = 0;
+ if( q=='[' ) q = ']';
+ while( ALWAYS(zIn[iIn]) ){
+ char c = zIn[iIn++];
+ if( c==q && zIn[iIn++]!=q ) break;
+ zIn[iOut++] = c;
+ }
+ zIn[iOut] = '\0';
+ }
+}
+
+/*
+** Construct a new ZipfileTab virtual table object.
+**
+** argv[0] -> module name ("zipfile")
+** argv[1] -> database name
+** argv[2] -> table name
+** argv[...] -> "column name" and other module argument fields.
+*/
+static int zipfileConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ int nByte = sizeof(ZipfileTab) + ZIPFILE_BUFFER_SIZE;
+ int nFile = 0;
+ const char *zFile = 0;
+ ZipfileTab *pNew = 0;
+ int rc;
+
+ /* If the table name is not "zipfile", require that the argument be
+ ** specified. This stops zipfile tables from being created as:
+ **
+ ** CREATE VIRTUAL TABLE zzz USING zipfile();
+ **
+ ** It does not prevent:
+ **
+ ** CREATE VIRTUAL TABLE zipfile USING zipfile();
+ */
+ assert( 0==sqlite3_stricmp(argv[0], "zipfile") );
+ if( (0!=sqlite3_stricmp(argv[2], "zipfile") && argc<4) || argc>4 ){
+ *pzErr = sqlite3_mprintf("zipfile constructor requires one argument");
+ return SQLITE_ERROR;
+ }
+
+ if( argc>3 ){
+ zFile = argv[3];
+ nFile = (int)strlen(zFile)+1;
+ }
+
+ rc = sqlite3_declare_vtab(db, ZIPFILE_SCHEMA);
+ if( rc==SQLITE_OK ){
+ pNew = (ZipfileTab*)sqlite3_malloc64((sqlite3_int64)nByte+nFile);
+ if( pNew==0 ) return SQLITE_NOMEM;
+ memset(pNew, 0, nByte+nFile);
+ pNew->db = db;
+ pNew->aBuffer = (u8*)&pNew[1];
+ if( zFile ){
+ pNew->zFile = (char*)&pNew->aBuffer[ZIPFILE_BUFFER_SIZE];
+ memcpy(pNew->zFile, zFile, nFile);
+ zipfileDequote(pNew->zFile);
+ }
+ }
+ *ppVtab = (sqlite3_vtab*)pNew;
+ return rc;
+}
+
+/*
+** Free the ZipfileEntry structure indicated by the only argument.
+*/
+static void zipfileEntryFree(ZipfileEntry *p){
+ if( p ){
+ sqlite3_free(p->cds.zFile);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Release resources that should be freed at the end of a write
+** transaction.
+*/
+static void zipfileCleanupTransaction(ZipfileTab *pTab){
+ ZipfileEntry *pEntry;
+ ZipfileEntry *pNext;
+
+ if( pTab->pWriteFd ){
+ fclose(pTab->pWriteFd);
+ pTab->pWriteFd = 0;
+ }
+ for(pEntry=pTab->pFirstEntry; pEntry; pEntry=pNext){
+ pNext = pEntry->pNext;
+ zipfileEntryFree(pEntry);
+ }
+ pTab->pFirstEntry = 0;
+ pTab->pLastEntry = 0;
+ pTab->szCurrent = 0;
+ pTab->szOrig = 0;
+}
+
+/*
+** This method is the destructor for zipfile vtab objects.
+*/
+static int zipfileDisconnect(sqlite3_vtab *pVtab){
+ zipfileCleanupTransaction((ZipfileTab*)pVtab);
+ sqlite3_free(pVtab);
+ return SQLITE_OK;
+}
+
+/*
+** Constructor for a new ZipfileCsr object.
+*/
+static int zipfileOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){
+ ZipfileTab *pTab = (ZipfileTab*)p;
+ ZipfileCsr *pCsr;
+ pCsr = sqlite3_malloc(sizeof(*pCsr));
+ *ppCsr = (sqlite3_vtab_cursor*)pCsr;
+ if( pCsr==0 ){
+ return SQLITE_NOMEM;
+ }
+ memset(pCsr, 0, sizeof(*pCsr));
+ pCsr->iId = ++pTab->iNextCsrid;
+ pCsr->pCsrNext = pTab->pCsrList;
+ pTab->pCsrList = pCsr;
+ return SQLITE_OK;
+}
+
+/*
+** Reset a cursor back to the state it was in when first returned
+** by zipfileOpen().
+*/
+static void zipfileResetCursor(ZipfileCsr *pCsr){
+ ZipfileEntry *p;
+ ZipfileEntry *pNext;
+
+ pCsr->bEof = 0;
+ if( pCsr->pFile ){
+ fclose(pCsr->pFile);
+ pCsr->pFile = 0;
+ zipfileEntryFree(pCsr->pCurrent);
+ pCsr->pCurrent = 0;
+ }
+
+ for(p=pCsr->pFreeEntry; p; p=pNext){
+ pNext = p->pNext;
+ zipfileEntryFree(p);
+ }
+}
+
+/*
+** Destructor for an ZipfileCsr.
+*/
+static int zipfileClose(sqlite3_vtab_cursor *cur){
+ ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ ZipfileTab *pTab = (ZipfileTab*)(pCsr->base.pVtab);
+ ZipfileCsr **pp;
+ zipfileResetCursor(pCsr);
+
+ /* Remove this cursor from the ZipfileTab.pCsrList list. */
+ for(pp=&pTab->pCsrList; *pp!=pCsr; pp=&((*pp)->pCsrNext));
+ *pp = pCsr->pCsrNext;
+
+ sqlite3_free(pCsr);
+ return SQLITE_OK;
+}
+
+/*
+** Set the error message for the virtual table associated with cursor
+** pCsr to the results of vprintf(zFmt, ...).
+*/
+static void zipfileTableErr(ZipfileTab *pTab, const char *zFmt, ...){
+ va_list ap;
+ va_start(ap, zFmt);
+ sqlite3_free(pTab->base.zErrMsg);
+ pTab->base.zErrMsg = sqlite3_vmprintf(zFmt, ap);
+ va_end(ap);
+}
+static void zipfileCursorErr(ZipfileCsr *pCsr, const char *zFmt, ...){
+ va_list ap;
+ va_start(ap, zFmt);
+ sqlite3_free(pCsr->base.pVtab->zErrMsg);
+ pCsr->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap);
+ va_end(ap);
+}
+
+/*
+** Read nRead bytes of data from offset iOff of file pFile into buffer
+** aRead[]. Return SQLITE_OK if successful, or an SQLite error code
+** otherwise.
+**
+** If an error does occur, output variable (*pzErrmsg) may be set to point
+** to an English language error message. It is the responsibility of the
+** caller to eventually free this buffer using
+** sqlite3_free().
+*/
+static int zipfileReadData(
+ FILE *pFile, /* Read from this file */
+ u8 *aRead, /* Read into this buffer */
+ int nRead, /* Number of bytes to read */
+ i64 iOff, /* Offset to read from */
+ char **pzErrmsg /* OUT: Error message (from sqlite3_malloc) */
+){
+ size_t n;
+ fseek(pFile, (long)iOff, SEEK_SET);
+ n = fread(aRead, 1, nRead, pFile);
+ if( (int)n!=nRead ){
+ *pzErrmsg = sqlite3_mprintf("error in fread()");
+ return SQLITE_ERROR;
+ }
+ return SQLITE_OK;
+}
+
+static int zipfileAppendData(
+ ZipfileTab *pTab,
+ const u8 *aWrite,
+ int nWrite
+){
+ size_t n;
+ fseek(pTab->pWriteFd, (long)pTab->szCurrent, SEEK_SET);
+ n = fwrite(aWrite, 1, nWrite, pTab->pWriteFd);
+ if( (int)n!=nWrite ){
+ pTab->base.zErrMsg = sqlite3_mprintf("error in fwrite()");
+ return SQLITE_ERROR;
+ }
+ pTab->szCurrent += nWrite;
+ return SQLITE_OK;
+}
+
+/*
+** Read and return a 16-bit little-endian unsigned integer from buffer aBuf.
+*/
+static u16 zipfileGetU16(const u8 *aBuf){
+ return (aBuf[1] << 8) + aBuf[0];
+}
+
+/*
+** Read and return a 32-bit little-endian unsigned integer from buffer aBuf.
+*/
+static u32 zipfileGetU32(const u8 *aBuf){
+ return ((u32)(aBuf[3]) << 24)
+ + ((u32)(aBuf[2]) << 16)
+ + ((u32)(aBuf[1]) << 8)
+ + ((u32)(aBuf[0]) << 0);
+}
+
+/*
+** Write a 16-bit little endiate integer into buffer aBuf.
+*/
+static void zipfilePutU16(u8 *aBuf, u16 val){
+ aBuf[0] = val & 0xFF;
+ aBuf[1] = (val>>8) & 0xFF;
+}
+
+/*
+** Write a 32-bit little endiate integer into buffer aBuf.
+*/
+static void zipfilePutU32(u8 *aBuf, u32 val){
+ aBuf[0] = val & 0xFF;
+ aBuf[1] = (val>>8) & 0xFF;
+ aBuf[2] = (val>>16) & 0xFF;
+ aBuf[3] = (val>>24) & 0xFF;
+}
+
+#define zipfileRead32(aBuf) ( aBuf+=4, zipfileGetU32(aBuf-4) )
+#define zipfileRead16(aBuf) ( aBuf+=2, zipfileGetU16(aBuf-2) )
+
+#define zipfileWrite32(aBuf,val) { zipfilePutU32(aBuf,val); aBuf+=4; }
+#define zipfileWrite16(aBuf,val) { zipfilePutU16(aBuf,val); aBuf+=2; }
+
+/*
+** Magic numbers used to read CDS records.
+*/
+#define ZIPFILE_CDS_NFILE_OFF 28
+#define ZIPFILE_CDS_SZCOMPRESSED_OFF 20
+
+/*
+** Decode the CDS record in buffer aBuf into (*pCDS). Return SQLITE_ERROR
+** if the record is not well-formed, or SQLITE_OK otherwise.
+*/
+static int zipfileReadCDS(u8 *aBuf, ZipfileCDS *pCDS){
+ u8 *aRead = aBuf;
+ u32 sig = zipfileRead32(aRead);
+ int rc = SQLITE_OK;
+ if( sig!=ZIPFILE_SIGNATURE_CDS ){
+ rc = SQLITE_ERROR;
+ }else{
+ pCDS->iVersionMadeBy = zipfileRead16(aRead);
+ pCDS->iVersionExtract = zipfileRead16(aRead);
+ pCDS->flags = zipfileRead16(aRead);
+ pCDS->iCompression = zipfileRead16(aRead);
+ pCDS->mTime = zipfileRead16(aRead);
+ pCDS->mDate = zipfileRead16(aRead);
+ pCDS->crc32 = zipfileRead32(aRead);
+ pCDS->szCompressed = zipfileRead32(aRead);
+ pCDS->szUncompressed = zipfileRead32(aRead);
+ assert( aRead==&aBuf[ZIPFILE_CDS_NFILE_OFF] );
+ pCDS->nFile = zipfileRead16(aRead);
+ pCDS->nExtra = zipfileRead16(aRead);
+ pCDS->nComment = zipfileRead16(aRead);
+ pCDS->iDiskStart = zipfileRead16(aRead);
+ pCDS->iInternalAttr = zipfileRead16(aRead);
+ pCDS->iExternalAttr = zipfileRead32(aRead);
+ pCDS->iOffset = zipfileRead32(aRead);
+ assert( aRead==&aBuf[ZIPFILE_CDS_FIXED_SZ] );
+ }
+
+ return rc;
+}
+
+/*
+** Decode the LFH record in buffer aBuf into (*pLFH). Return SQLITE_ERROR
+** if the record is not well-formed, or SQLITE_OK otherwise.
+*/
+static int zipfileReadLFH(
+ u8 *aBuffer,
+ ZipfileLFH *pLFH
+){
+ u8 *aRead = aBuffer;
+ int rc = SQLITE_OK;
+
+ u32 sig = zipfileRead32(aRead);
+ if( sig!=ZIPFILE_SIGNATURE_LFH ){
+ rc = SQLITE_ERROR;
+ }else{
+ pLFH->iVersionExtract = zipfileRead16(aRead);
+ pLFH->flags = zipfileRead16(aRead);
+ pLFH->iCompression = zipfileRead16(aRead);
+ pLFH->mTime = zipfileRead16(aRead);
+ pLFH->mDate = zipfileRead16(aRead);
+ pLFH->crc32 = zipfileRead32(aRead);
+ pLFH->szCompressed = zipfileRead32(aRead);
+ pLFH->szUncompressed = zipfileRead32(aRead);
+ pLFH->nFile = zipfileRead16(aRead);
+ pLFH->nExtra = zipfileRead16(aRead);
+ }
+ return rc;
+}
+
+
+/*
+** Buffer aExtra (size nExtra bytes) contains zip archive "extra" fields.
+** Scan through this buffer to find an "extra-timestamp" field. If one
+** exists, extract the 32-bit modification-timestamp from it and store
+** the value in output parameter *pmTime.
+**
+** Zero is returned if no extra-timestamp record could be found (and so
+** *pmTime is left unchanged), or non-zero otherwise.
+**
+** The general format of an extra field is:
+**
+** Header ID 2 bytes
+** Data Size 2 bytes
+** Data N bytes
+*/
+static int zipfileScanExtra(u8 *aExtra, int nExtra, u32 *pmTime){
+ int ret = 0;
+ u8 *p = aExtra;
+ u8 *pEnd = &aExtra[nExtra];
+
+ while( p<pEnd ){
+ u16 id = zipfileRead16(p);
+ u16 nByte = zipfileRead16(p);
+
+ switch( id ){
+ case ZIPFILE_EXTRA_TIMESTAMP: {
+ u8 b = p[0];
+ if( b & 0x01 ){ /* 0x01 -> modtime is present */
+ *pmTime = zipfileGetU32(&p[1]);
+ ret = 1;
+ }
+ break;
+ }
+ }
+
+ p += nByte;
+ }
+ return ret;
+}
+
+/*
+** Convert the standard MS-DOS timestamp stored in the mTime and mDate
+** fields of the CDS structure passed as the only argument to a 32-bit
+** UNIX seconds-since-the-epoch timestamp. Return the result.
+**
+** "Standard" MS-DOS time format:
+**
+** File modification time:
+** Bits 00-04: seconds divided by 2
+** Bits 05-10: minute
+** Bits 11-15: hour
+** File modification date:
+** Bits 00-04: day
+** Bits 05-08: month (1-12)
+** Bits 09-15: years from 1980
+**
+** https://msdn.microsoft.com/en-us/library/9kkf9tah.aspx
+*/
+static u32 zipfileMtime(ZipfileCDS *pCDS){
+ int Y = (1980 + ((pCDS->mDate >> 9) & 0x7F));
+ int M = ((pCDS->mDate >> 5) & 0x0F);
+ int D = (pCDS->mDate & 0x1F);
+ int B = -13;
+
+ int sec = (pCDS->mTime & 0x1F)*2;
+ int min = (pCDS->mTime >> 5) & 0x3F;
+ int hr = (pCDS->mTime >> 11) & 0x1F;
+ i64 JD;
+
+ /* JD = INT(365.25 * (Y+4716)) + INT(30.6001 * (M+1)) + D + B - 1524.5 */
+
+ /* Calculate the JD in seconds for noon on the day in question */
+ if( M<3 ){
+ Y = Y-1;
+ M = M+12;
+ }
+ JD = (i64)(24*60*60) * (
+ (int)(365.25 * (Y + 4716))
+ + (int)(30.6001 * (M + 1))
+ + D + B - 1524
+ );
+
+ /* Correct the JD for the time within the day */
+ JD += (hr-12) * 3600 + min * 60 + sec;
+
+ /* Convert JD to unix timestamp (the JD epoch is 2440587.5) */
+ return (u32)(JD - (i64)(24405875) * 24*60*6);
+}
+
+/*
+** The opposite of zipfileMtime(). This function populates the mTime and
+** mDate fields of the CDS structure passed as the first argument according
+** to the UNIX timestamp value passed as the second.
+*/
+static void zipfileMtimeToDos(ZipfileCDS *pCds, u32 mUnixTime){
+ /* Convert unix timestamp to JD (2440588 is noon on 1/1/1970) */
+ i64 JD = (i64)2440588 + mUnixTime / (24*60*60);
+
+ int A, B, C, D, E;
+ int yr, mon, day;
+ int hr, min, sec;
+
+ A = (int)((JD - 1867216.25)/36524.25);
+ A = (int)(JD + 1 + A - (A/4));
+ B = A + 1524;
+ C = (int)((B - 122.1)/365.25);
+ D = (36525*(C&32767))/100;
+ E = (int)((B-D)/30.6001);
+
+ day = B - D - (int)(30.6001*E);
+ mon = (E<14 ? E-1 : E-13);
+ yr = mon>2 ? C-4716 : C-4715;
+
+ hr = (mUnixTime % (24*60*60)) / (60*60);
+ min = (mUnixTime % (60*60)) / 60;
+ sec = (mUnixTime % 60);
+
+ if( yr>=1980 ){
+ pCds->mDate = (u16)(day + (mon << 5) + ((yr-1980) << 9));
+ pCds->mTime = (u16)(sec/2 + (min<<5) + (hr<<11));
+ }else{
+ pCds->mDate = pCds->mTime = 0;
+ }
+
+ assert( mUnixTime<315507600
+ || mUnixTime==zipfileMtime(pCds)
+ || ((mUnixTime % 2) && mUnixTime-1==zipfileMtime(pCds))
+ /* || (mUnixTime % 2) */
+ );
+}
+
+/*
+** If aBlob is not NULL, then it is a pointer to a buffer (nBlob bytes in
+** size) containing an entire zip archive image. Or, if aBlob is NULL,
+** then pFile is a file-handle open on a zip file. In either case, this
+** function creates a ZipfileEntry object based on the zip archive entry
+** for which the CDS record is at offset iOff.
+**
+** If successful, SQLITE_OK is returned and (*ppEntry) set to point to
+** the new object. Otherwise, an SQLite error code is returned and the
+** final value of (*ppEntry) undefined.
+*/
+static int zipfileGetEntry(
+ ZipfileTab *pTab, /* Store any error message here */
+ const u8 *aBlob, /* Pointer to in-memory file image */
+ int nBlob, /* Size of aBlob[] in bytes */
+ FILE *pFile, /* If aBlob==0, read from this file */
+ i64 iOff, /* Offset of CDS record */
+ ZipfileEntry **ppEntry /* OUT: Pointer to new object */
+){
+ u8 *aRead;
+ char **pzErr = &pTab->base.zErrMsg;
+ int rc = SQLITE_OK;
+
+ if( aBlob==0 ){
+ aRead = pTab->aBuffer;
+ rc = zipfileReadData(pFile, aRead, ZIPFILE_CDS_FIXED_SZ, iOff, pzErr);
+ }else{
+ aRead = (u8*)&aBlob[iOff];
+ }
+
+ if( rc==SQLITE_OK ){
+ sqlite3_int64 nAlloc;
+ ZipfileEntry *pNew;
+
+ int nFile = zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF]);
+ int nExtra = zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF+2]);
+ nExtra += zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF+4]);
+
+ nAlloc = sizeof(ZipfileEntry) + nExtra;
+ if( aBlob ){
+ nAlloc += zipfileGetU32(&aRead[ZIPFILE_CDS_SZCOMPRESSED_OFF]);
+ }
+
+ pNew = (ZipfileEntry*)sqlite3_malloc64(nAlloc);
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(pNew, 0, sizeof(ZipfileEntry));
+ rc = zipfileReadCDS(aRead, &pNew->cds);
+ if( rc!=SQLITE_OK ){
+ *pzErr = sqlite3_mprintf("failed to read CDS at offset %lld", iOff);
+ }else if( aBlob==0 ){
+ rc = zipfileReadData(
+ pFile, aRead, nExtra+nFile, iOff+ZIPFILE_CDS_FIXED_SZ, pzErr
+ );
+ }else{
+ aRead = (u8*)&aBlob[iOff + ZIPFILE_CDS_FIXED_SZ];
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ u32 *pt = &pNew->mUnixTime;
+ pNew->cds.zFile = sqlite3_mprintf("%.*s", nFile, aRead);
+ pNew->aExtra = (u8*)&pNew[1];
+ memcpy(pNew->aExtra, &aRead[nFile], nExtra);
+ if( pNew->cds.zFile==0 ){
+ rc = SQLITE_NOMEM;
+ }else if( 0==zipfileScanExtra(&aRead[nFile], pNew->cds.nExtra, pt) ){
+ pNew->mUnixTime = zipfileMtime(&pNew->cds);
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ static const int szFix = ZIPFILE_LFH_FIXED_SZ;
+ ZipfileLFH lfh;
+ if( pFile ){
+ rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr);
+ }else{
+ aRead = (u8*)&aBlob[pNew->cds.iOffset];
+ }
+
+ rc = zipfileReadLFH(aRead, &lfh);
+ if( rc==SQLITE_OK ){
+ pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ;
+ pNew->iDataOff += lfh.nFile + lfh.nExtra;
+ if( aBlob && pNew->cds.szCompressed ){
+ pNew->aData = &pNew->aExtra[nExtra];
+ memcpy(pNew->aData, &aBlob[pNew->iDataOff], pNew->cds.szCompressed);
+ }
+ }else{
+ *pzErr = sqlite3_mprintf("failed to read LFH at offset %d",
+ (int)pNew->cds.iOffset
+ );
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ zipfileEntryFree(pNew);
+ }else{
+ *ppEntry = pNew;
+ }
+ }
+
+ return rc;
+}
+
+/*
+** Advance an ZipfileCsr to its next row of output.
+*/
+static int zipfileNext(sqlite3_vtab_cursor *cur){
+ ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ int rc = SQLITE_OK;
+
+ if( pCsr->pFile ){
+ i64 iEof = pCsr->eocd.iOffset + pCsr->eocd.nSize;
+ zipfileEntryFree(pCsr->pCurrent);
+ pCsr->pCurrent = 0;
+ if( pCsr->iNextOff>=iEof ){
+ pCsr->bEof = 1;
+ }else{
+ ZipfileEntry *p = 0;
+ ZipfileTab *pTab = (ZipfileTab*)(cur->pVtab);
+ rc = zipfileGetEntry(pTab, 0, 0, pCsr->pFile, pCsr->iNextOff, &p);
+ if( rc==SQLITE_OK ){
+ pCsr->iNextOff += ZIPFILE_CDS_FIXED_SZ;
+ pCsr->iNextOff += (int)p->cds.nExtra + p->cds.nFile + p->cds.nComment;
+ }
+ pCsr->pCurrent = p;
+ }
+ }else{
+ if( !pCsr->bNoop ){
+ pCsr->pCurrent = pCsr->pCurrent->pNext;
+ }
+ if( pCsr->pCurrent==0 ){
+ pCsr->bEof = 1;
+ }
+ }
+
+ pCsr->bNoop = 0;
+ return rc;
+}
+
+static void zipfileFree(void *p) {
+ sqlite3_free(p);
+}
+
+/*
+** Buffer aIn (size nIn bytes) contains compressed data. Uncompressed, the
+** size is nOut bytes. This function uncompresses the data and sets the
+** return value in context pCtx to the result (a blob).
+**
+** If an error occurs, an error code is left in pCtx instead.
+*/
+static void zipfileInflate(
+ sqlite3_context *pCtx, /* Store result here */
+ const u8 *aIn, /* Compressed data */
+ int nIn, /* Size of buffer aIn[] in bytes */
+ int nOut /* Expected output size */
+){
+ u8 *aRes = sqlite3_malloc(nOut);
+ if( aRes==0 ){
+ sqlite3_result_error_nomem(pCtx);
+ }else{
+ int err;
+ z_stream str;
+ memset(&str, 0, sizeof(str));
+
+ str.next_in = (Byte*)aIn;
+ str.avail_in = nIn;
+ str.next_out = (Byte*)aRes;
+ str.avail_out = nOut;
+
+ err = inflateInit2(&str, -15);
+ if( err!=Z_OK ){
+ zipfileCtxErrorMsg(pCtx, "inflateInit2() failed (%d)", err);
+ }else{
+ err = inflate(&str, Z_NO_FLUSH);
+ if( err!=Z_STREAM_END ){
+ zipfileCtxErrorMsg(pCtx, "inflate() failed (%d)", err);
+ }else{
+ sqlite3_result_blob(pCtx, aRes, nOut, zipfileFree);
+ aRes = 0;
+ }
+ }
+ sqlite3_free(aRes);
+ inflateEnd(&str);
+ }
+}
+
+/*
+** Buffer aIn (size nIn bytes) contains uncompressed data. This function
+** compresses it and sets (*ppOut) to point to a buffer containing the
+** compressed data. The caller is responsible for eventually calling
+** sqlite3_free() to release buffer (*ppOut). Before returning, (*pnOut)
+** is set to the size of buffer (*ppOut) in bytes.
+**
+** If no error occurs, SQLITE_OK is returned. Otherwise, an SQLite error
+** code is returned and an error message left in virtual-table handle
+** pTab. The values of (*ppOut) and (*pnOut) are left unchanged in this
+** case.
+*/
+static int zipfileDeflate(
+ const u8 *aIn, int nIn, /* Input */
+ u8 **ppOut, int *pnOut, /* Output */
+ char **pzErr /* OUT: Error message */
+){
+ sqlite3_int64 nAlloc = compressBound(nIn);
+ u8 *aOut;
+ int rc = SQLITE_OK;
+
+ aOut = (u8*)sqlite3_malloc64(nAlloc);
+ if( aOut==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ int res;
+ z_stream str;
+ memset(&str, 0, sizeof(str));
+ str.next_in = (Bytef*)aIn;
+ str.avail_in = nIn;
+ str.next_out = aOut;
+ str.avail_out = nAlloc;
+
+ deflateInit2(&str, 9, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
+ res = deflate(&str, Z_FINISH);
+
+ if( res==Z_STREAM_END ){
+ *ppOut = aOut;
+ *pnOut = (int)str.total_out;
+ }else{
+ sqlite3_free(aOut);
+ *pzErr = sqlite3_mprintf("zipfile: deflate() error");
+ rc = SQLITE_ERROR;
+ }
+ deflateEnd(&str);
+ }
+
+ return rc;
+}
+
+
+/*
+** Return values of columns for the row at which the series_cursor
+** is currently pointing.
+*/
+static int zipfileColumn(
+ sqlite3_vtab_cursor *cur, /* The cursor */
+ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
+ int i /* Which column to return */
+){
+ ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ ZipfileCDS *pCDS = &pCsr->pCurrent->cds;
+ int rc = SQLITE_OK;
+ switch( i ){
+ case 0: /* name */
+ sqlite3_result_text(ctx, pCDS->zFile, -1, SQLITE_TRANSIENT);
+ break;
+ case 1: /* mode */
+ /* TODO: Whether or not the following is correct surely depends on
+ ** the platform on which the archive was created. */
+ sqlite3_result_int(ctx, pCDS->iExternalAttr >> 16);
+ break;
+ case 2: { /* mtime */
+ sqlite3_result_int64(ctx, pCsr->pCurrent->mUnixTime);
+ break;
+ }
+ case 3: { /* sz */
+ if( sqlite3_vtab_nochange(ctx)==0 ){
+ sqlite3_result_int64(ctx, pCDS->szUncompressed);
+ }
+ break;
+ }
+ case 4: /* rawdata */
+ if( sqlite3_vtab_nochange(ctx) ) break;
+ case 5: { /* data */
+ if( i==4 || pCDS->iCompression==0 || pCDS->iCompression==8 ){
+ int sz = pCDS->szCompressed;
+ int szFinal = pCDS->szUncompressed;
+ if( szFinal>0 ){
+ u8 *aBuf;
+ u8 *aFree = 0;
+ if( pCsr->pCurrent->aData ){
+ aBuf = pCsr->pCurrent->aData;
+ }else{
+ aBuf = aFree = sqlite3_malloc64(sz);
+ if( aBuf==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ FILE *pFile = pCsr->pFile;
+ if( pFile==0 ){
+ pFile = ((ZipfileTab*)(pCsr->base.pVtab))->pWriteFd;
+ }
+ rc = zipfileReadData(pFile, aBuf, sz, pCsr->pCurrent->iDataOff,
+ &pCsr->base.pVtab->zErrMsg
+ );
+ }
+ }
+ if( rc==SQLITE_OK ){
+ if( i==5 && pCDS->iCompression ){
+ zipfileInflate(ctx, aBuf, sz, szFinal);
+ }else{
+ sqlite3_result_blob(ctx, aBuf, sz, SQLITE_TRANSIENT);
+ }
+ }
+ sqlite3_free(aFree);
+ }else{
+ /* Figure out if this is a directory or a zero-sized file. Consider
+ ** it to be a directory either if the mode suggests so, or if
+ ** the final character in the name is '/'. */
+ u32 mode = pCDS->iExternalAttr >> 16;
+ if( !(mode & S_IFDIR) && pCDS->zFile[pCDS->nFile-1]!='/' ){
+ sqlite3_result_blob(ctx, "", 0, SQLITE_STATIC);
+ }
+ }
+ }
+ break;
+ }
+ case 6: /* method */
+ sqlite3_result_int(ctx, pCDS->iCompression);
+ break;
+ default: /* z */
+ assert( i==7 );
+ sqlite3_result_int64(ctx, pCsr->iId);
+ break;
+ }
+
+ return rc;
+}
+
+/*
+** Return TRUE if the cursor is at EOF.
+*/
+static int zipfileEof(sqlite3_vtab_cursor *cur){
+ ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ return pCsr->bEof;
+}
+
+/*
+** If aBlob is not NULL, then it points to a buffer nBlob bytes in size
+** containing an entire zip archive image. Or, if aBlob is NULL, then pFile
+** is guaranteed to be a file-handle open on a zip file.
+**
+** This function attempts to locate the EOCD record within the zip archive
+** and populate *pEOCD with the results of decoding it. SQLITE_OK is
+** returned if successful. Otherwise, an SQLite error code is returned and
+** an English language error message may be left in virtual-table pTab.
+*/
+static int zipfileReadEOCD(
+ ZipfileTab *pTab, /* Return errors here */
+ const u8 *aBlob, /* Pointer to in-memory file image */
+ int nBlob, /* Size of aBlob[] in bytes */
+ FILE *pFile, /* Read from this file if aBlob==0 */
+ ZipfileEOCD *pEOCD /* Object to populate */
+){
+ u8 *aRead = pTab->aBuffer; /* Temporary buffer */
+ int nRead; /* Bytes to read from file */
+ int rc = SQLITE_OK;
+
+ if( aBlob==0 ){
+ i64 iOff; /* Offset to read from */
+ i64 szFile; /* Total size of file in bytes */
+ fseek(pFile, 0, SEEK_END);
+ szFile = (i64)ftell(pFile);
+ if( szFile==0 ){
+ memset(pEOCD, 0, sizeof(ZipfileEOCD));
+ return SQLITE_OK;
+ }
+ nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE));
+ iOff = szFile - nRead;
+ rc = zipfileReadData(pFile, aRead, nRead, iOff, &pTab->base.zErrMsg);
+ }else{
+ nRead = (int)(MIN(nBlob, ZIPFILE_BUFFER_SIZE));
+ aRead = (u8*)&aBlob[nBlob-nRead];
+ }
+
+ if( rc==SQLITE_OK ){
+ int i;
+
+ /* Scan backwards looking for the signature bytes */
+ for(i=nRead-20; i>=0; i--){
+ if( aRead[i]==0x50 && aRead[i+1]==0x4b
+ && aRead[i+2]==0x05 && aRead[i+3]==0x06
+ ){
+ break;
+ }
+ }
+ if( i<0 ){
+ pTab->base.zErrMsg = sqlite3_mprintf(
+ "cannot find end of central directory record"
+ );
+ return SQLITE_ERROR;
+ }
+
+ aRead += i+4;
+ pEOCD->iDisk = zipfileRead16(aRead);
+ pEOCD->iFirstDisk = zipfileRead16(aRead);
+ pEOCD->nEntry = zipfileRead16(aRead);
+ pEOCD->nEntryTotal = zipfileRead16(aRead);
+ pEOCD->nSize = zipfileRead32(aRead);
+ pEOCD->iOffset = zipfileRead32(aRead);
+ }
+
+ return rc;
+}
+
+/*
+** Add object pNew to the linked list that begins at ZipfileTab.pFirstEntry
+** and ends with pLastEntry. If argument pBefore is NULL, then pNew is added
+** to the end of the list. Otherwise, it is added to the list immediately
+** before pBefore (which is guaranteed to be a part of said list).
+*/
+static void zipfileAddEntry(
+ ZipfileTab *pTab,
+ ZipfileEntry *pBefore,
+ ZipfileEntry *pNew
+){
+ assert( (pTab->pFirstEntry==0)==(pTab->pLastEntry==0) );
+ assert( pNew->pNext==0 );
+ if( pBefore==0 ){
+ if( pTab->pFirstEntry==0 ){
+ pTab->pFirstEntry = pTab->pLastEntry = pNew;
+ }else{
+ assert( pTab->pLastEntry->pNext==0 );
+ pTab->pLastEntry->pNext = pNew;
+ pTab->pLastEntry = pNew;
+ }
+ }else{
+ ZipfileEntry **pp;
+ for(pp=&pTab->pFirstEntry; *pp!=pBefore; pp=&((*pp)->pNext));
+ pNew->pNext = pBefore;
+ *pp = pNew;
+ }
+}
+
+static int zipfileLoadDirectory(ZipfileTab *pTab, const u8 *aBlob, int nBlob){
+ ZipfileEOCD eocd;
+ int rc;
+ int i;
+ i64 iOff;
+
+ rc = zipfileReadEOCD(pTab, aBlob, nBlob, pTab->pWriteFd, &eocd);
+ iOff = eocd.iOffset;
+ for(i=0; rc==SQLITE_OK && i<eocd.nEntry; i++){
+ ZipfileEntry *pNew = 0;
+ rc = zipfileGetEntry(pTab, aBlob, nBlob, pTab->pWriteFd, iOff, &pNew);
+
+ if( rc==SQLITE_OK ){
+ zipfileAddEntry(pTab, 0, pNew);
+ iOff += ZIPFILE_CDS_FIXED_SZ;
+ iOff += (int)pNew->cds.nExtra + pNew->cds.nFile + pNew->cds.nComment;
+ }
+ }
+ return rc;
+}
+
+/*
+** xFilter callback.
+*/
+static int zipfileFilter(
+ sqlite3_vtab_cursor *cur,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ ZipfileTab *pTab = (ZipfileTab*)cur->pVtab;
+ ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ const char *zFile = 0; /* Zip file to scan */
+ int rc = SQLITE_OK; /* Return Code */
+ int bInMemory = 0; /* True for an in-memory zipfile */
+
+ zipfileResetCursor(pCsr);
+
+ if( pTab->zFile ){
+ zFile = pTab->zFile;
+ }else if( idxNum==0 ){
+ zipfileCursorErr(pCsr, "zipfile() function requires an argument");
+ return SQLITE_ERROR;
+ }else if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){
+ const u8 *aBlob = (const u8*)sqlite3_value_blob(argv[0]);
+ int nBlob = sqlite3_value_bytes(argv[0]);
+ assert( pTab->pFirstEntry==0 );
+ rc = zipfileLoadDirectory(pTab, aBlob, nBlob);
+ pCsr->pFreeEntry = pTab->pFirstEntry;
+ pTab->pFirstEntry = pTab->pLastEntry = 0;
+ if( rc!=SQLITE_OK ) return rc;
+ bInMemory = 1;
+ }else{
+ zFile = (const char*)sqlite3_value_text(argv[0]);
+ }
+
+ if( 0==pTab->pWriteFd && 0==bInMemory ){
+ pCsr->pFile = fopen(zFile, "rb");
+ if( pCsr->pFile==0 ){
+ zipfileCursorErr(pCsr, "cannot open file: %s", zFile);
+ rc = SQLITE_ERROR;
+ }else{
+ rc = zipfileReadEOCD(pTab, 0, 0, pCsr->pFile, &pCsr->eocd);
+ if( rc==SQLITE_OK ){
+ if( pCsr->eocd.nEntry==0 ){
+ pCsr->bEof = 1;
+ }else{
+ pCsr->iNextOff = pCsr->eocd.iOffset;
+ rc = zipfileNext(cur);
+ }
+ }
+ }
+ }else{
+ pCsr->bNoop = 1;
+ pCsr->pCurrent = pCsr->pFreeEntry ? pCsr->pFreeEntry : pTab->pFirstEntry;
+ rc = zipfileNext(cur);
+ }
+
+ return rc;
+}
+
+/*
+** xBestIndex callback.
+*/
+static int zipfileBestIndex(
+ sqlite3_vtab *tab,
+ sqlite3_index_info *pIdxInfo
+){
+ int i;
+ int idx = -1;
+ int unusable = 0;
+
+ for(i=0; i<pIdxInfo->nConstraint; i++){
+ const struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i];
+ if( pCons->iColumn!=ZIPFILE_F_COLUMN_IDX ) continue;
+ if( pCons->usable==0 ){
+ unusable = 1;
+ }else if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+ idx = i;
+ }
+ }
+ if( idx>=0 ){
+ pIdxInfo->aConstraintUsage[idx].argvIndex = 1;
+ pIdxInfo->aConstraintUsage[idx].omit = 1;
+ pIdxInfo->estimatedCost = 1000.0;
+ pIdxInfo->idxNum = 1;
+ }else if( unusable ){
+ return SQLITE_CONSTRAINT;
+ }
+ return SQLITE_OK;
+}
+
+static ZipfileEntry *zipfileNewEntry(const char *zPath){
+ ZipfileEntry *pNew;
+ pNew = sqlite3_malloc(sizeof(ZipfileEntry));
+ if( pNew ){
+ memset(pNew, 0, sizeof(ZipfileEntry));
+ pNew->cds.zFile = sqlite3_mprintf("%s", zPath);
+ if( pNew->cds.zFile==0 ){
+ sqlite3_free(pNew);
+ pNew = 0;
+ }
+ }
+ return pNew;
+}
+
+static int zipfileSerializeLFH(ZipfileEntry *pEntry, u8 *aBuf){
+ ZipfileCDS *pCds = &pEntry->cds;
+ u8 *a = aBuf;
+
+ pCds->nExtra = 9;
+
+ /* Write the LFH itself */
+ zipfileWrite32(a, ZIPFILE_SIGNATURE_LFH);
+ zipfileWrite16(a, pCds->iVersionExtract);
+ zipfileWrite16(a, pCds->flags);
+ zipfileWrite16(a, pCds->iCompression);
+ zipfileWrite16(a, pCds->mTime);
+ zipfileWrite16(a, pCds->mDate);
+ zipfileWrite32(a, pCds->crc32);
+ zipfileWrite32(a, pCds->szCompressed);
+ zipfileWrite32(a, pCds->szUncompressed);
+ zipfileWrite16(a, (u16)pCds->nFile);
+ zipfileWrite16(a, pCds->nExtra);
+ assert( a==&aBuf[ZIPFILE_LFH_FIXED_SZ] );
+
+ /* Add the file name */
+ memcpy(a, pCds->zFile, (int)pCds->nFile);
+ a += (int)pCds->nFile;
+
+ /* The "extra" data */
+ zipfileWrite16(a, ZIPFILE_EXTRA_TIMESTAMP);
+ zipfileWrite16(a, 5);
+ *a++ = 0x01;
+ zipfileWrite32(a, pEntry->mUnixTime);
+
+ return a-aBuf;
+}
+
+static int zipfileAppendEntry(
+ ZipfileTab *pTab,
+ ZipfileEntry *pEntry,
+ const u8 *pData,
+ int nData
+){
+ u8 *aBuf = pTab->aBuffer;
+ int nBuf;
+ int rc;
+
+ nBuf = zipfileSerializeLFH(pEntry, aBuf);
+ rc = zipfileAppendData(pTab, aBuf, nBuf);
+ if( rc==SQLITE_OK ){
+ pEntry->iDataOff = pTab->szCurrent;
+ rc = zipfileAppendData(pTab, pData, nData);
+ }
+
+ return rc;
+}
+
+static int zipfileGetMode(
+ sqlite3_value *pVal,
+ int bIsDir, /* If true, default to directory */
+ u32 *pMode, /* OUT: Mode value */
+ char **pzErr /* OUT: Error message */
+){
+ const char *z = (const char*)sqlite3_value_text(pVal);
+ u32 mode = 0;
+ if( z==0 ){
+ mode = (bIsDir ? (S_IFDIR + 0755) : (S_IFREG + 0644));
+ }else if( z[0]>='0' && z[0]<='9' ){
+ mode = (unsigned int)sqlite3_value_int(pVal);
+ }else{
+ const char zTemplate[11] = "-rwxrwxrwx";
+ int i;
+ if( strlen(z)!=10 ) goto parse_error;
+ switch( z[0] ){
+ case '-': mode |= S_IFREG; break;
+ case 'd': mode |= S_IFDIR; break;
+ case 'l': mode |= S_IFLNK; break;
+ default: goto parse_error;
+ }
+ for(i=1; i<10; i++){
+ if( z[i]==zTemplate[i] ) mode |= 1 << (9-i);
+ else if( z[i]!='-' ) goto parse_error;
+ }
+ }
+ if( ((mode & S_IFDIR)==0)==bIsDir ){
+ /* The "mode" attribute is a directory, but data has been specified.
+ ** Or vice-versa - no data but "mode" is a file or symlink. */
+ *pzErr = sqlite3_mprintf("zipfile: mode does not match data");
+ return SQLITE_CONSTRAINT;
+ }
+ *pMode = mode;
+ return SQLITE_OK;
+
+ parse_error:
+ *pzErr = sqlite3_mprintf("zipfile: parse error in mode: %s", z);
+ return SQLITE_ERROR;
+}
+
+/*
+** Both (const char*) arguments point to nul-terminated strings. Argument
+** nB is the value of strlen(zB). This function returns 0 if the strings are
+** identical, ignoring any trailing '/' character in either path. */
+static int zipfileComparePath(const char *zA, const char *zB, int nB){
+ int nA = (int)strlen(zA);
+ if( zA[nA-1]=='/' ) nA--;
+ if( zB[nB-1]=='/' ) nB--;
+ if( nA==nB && memcmp(zA, zB, nA)==0 ) return 0;
+ return 1;
+}
+
+static int zipfileBegin(sqlite3_vtab *pVtab){
+ ZipfileTab *pTab = (ZipfileTab*)pVtab;
+ int rc = SQLITE_OK;
+
+ assert( pTab->pWriteFd==0 );
+
+ /* Open a write fd on the file. Also load the entire central directory
+ ** structure into memory. During the transaction any new file data is
+ ** appended to the archive file, but the central directory is accumulated
+ ** in main-memory until the transaction is committed. */
+ pTab->pWriteFd = fopen(pTab->zFile, "ab+");
+ if( pTab->pWriteFd==0 ){
+ pTab->base.zErrMsg = sqlite3_mprintf(
+ "zipfile: failed to open file %s for writing", pTab->zFile
+ );
+ rc = SQLITE_ERROR;
+ }else{
+ fseek(pTab->pWriteFd, 0, SEEK_END);
+ pTab->szCurrent = pTab->szOrig = (i64)ftell(pTab->pWriteFd);
+ rc = zipfileLoadDirectory(pTab, 0, 0);
+ }
+
+ if( rc!=SQLITE_OK ){
+ zipfileCleanupTransaction(pTab);
+ }
+
+ return rc;
+}
+
+/*
+** Return the current time as a 32-bit timestamp in UNIX epoch format (like
+** time(2)).
+*/
+static u32 zipfileTime(void){
+ sqlite3_vfs *pVfs = sqlite3_vfs_find(0);
+ u32 ret;
+ if( pVfs->iVersion>=2 && pVfs->xCurrentTimeInt64 ){
+ i64 ms;
+ pVfs->xCurrentTimeInt64(pVfs, &ms);
+ ret = (u32)((ms/1000) - ((i64)24405875 * 8640));
+ }else{
+ double day;
+ pVfs->xCurrentTime(pVfs, &day);
+ ret = (u32)((day - 2440587.5) * 86400);
+ }
+ return ret;
+}
+
+/*
+** Return a 32-bit timestamp in UNIX epoch format.
+**
+** If the value passed as the only argument is either NULL or an SQL NULL,
+** return the current time. Otherwise, return the value stored in (*pVal)
+** cast to a 32-bit unsigned integer.
+*/
+static u32 zipfileGetTime(sqlite3_value *pVal){
+ if( pVal==0 || sqlite3_value_type(pVal)==SQLITE_NULL ){
+ return zipfileTime();
+ }
+ return (u32)sqlite3_value_int64(pVal);
+}
+
+/*
+** Unless it is NULL, entry pOld is currently part of the pTab->pFirstEntry
+** linked list. Remove it from the list and free the object.
+*/
+static void zipfileRemoveEntryFromList(ZipfileTab *pTab, ZipfileEntry *pOld){
+ if( pOld ){
+ ZipfileEntry **pp;
+ for(pp=&pTab->pFirstEntry; (*pp)!=pOld; pp=&((*pp)->pNext));
+ *pp = (*pp)->pNext;
+ zipfileEntryFree(pOld);
+ }
+}
+
+/*
+** xUpdate method.
+*/
+static int zipfileUpdate(
+ sqlite3_vtab *pVtab,
+ int nVal,
+ sqlite3_value **apVal,
+ sqlite_int64 *pRowid
+){
+ ZipfileTab *pTab = (ZipfileTab*)pVtab;
+ int rc = SQLITE_OK; /* Return Code */
+ ZipfileEntry *pNew = 0; /* New in-memory CDS entry */
+
+ u32 mode = 0; /* Mode for new entry */
+ u32 mTime = 0; /* Modification time for new entry */
+ i64 sz = 0; /* Uncompressed size */
+ const char *zPath = 0; /* Path for new entry */
+ int nPath = 0; /* strlen(zPath) */
+ const u8 *pData = 0; /* Pointer to buffer containing content */
+ int nData = 0; /* Size of pData buffer in bytes */
+ int iMethod = 0; /* Compression method for new entry */
+ u8 *pFree = 0; /* Free this */
+ char *zFree = 0; /* Also free this */
+ ZipfileEntry *pOld = 0;
+ ZipfileEntry *pOld2 = 0;
+ int bUpdate = 0; /* True for an update that modifies "name" */
+ int bIsDir = 0;
+ u32 iCrc32 = 0;
+
+ if( pTab->pWriteFd==0 ){
+ rc = zipfileBegin(pVtab);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+
+ /* If this is a DELETE or UPDATE, find the archive entry to delete. */
+ if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
+ const char *zDelete = (const char*)sqlite3_value_text(apVal[0]);
+ int nDelete = (int)strlen(zDelete);
+ if( nVal>1 ){
+ const char *zUpdate = (const char*)sqlite3_value_text(apVal[1]);
+ if( zUpdate && zipfileComparePath(zUpdate, zDelete, nDelete)!=0 ){
+ bUpdate = 1;
+ }
+ }
+ for(pOld=pTab->pFirstEntry; 1; pOld=pOld->pNext){
+ if( zipfileComparePath(pOld->cds.zFile, zDelete, nDelete)==0 ){
+ break;
+ }
+ assert( pOld->pNext );
+ }
+ }
+
+ if( nVal>1 ){
+ /* Check that "sz" and "rawdata" are both NULL: */
+ if( sqlite3_value_type(apVal[5])!=SQLITE_NULL ){
+ zipfileTableErr(pTab, "sz must be NULL");
+ rc = SQLITE_CONSTRAINT;
+ }
+ if( sqlite3_value_type(apVal[6])!=SQLITE_NULL ){
+ zipfileTableErr(pTab, "rawdata must be NULL");
+ rc = SQLITE_CONSTRAINT;
+ }
+
+ if( rc==SQLITE_OK ){
+ if( sqlite3_value_type(apVal[7])==SQLITE_NULL ){
+ /* data=NULL. A directory */
+ bIsDir = 1;
+ }else{
+ /* Value specified for "data", and possibly "method". This must be
+ ** a regular file or a symlink. */
+ const u8 *aIn = sqlite3_value_blob(apVal[7]);
+ int nIn = sqlite3_value_bytes(apVal[7]);
+ int bAuto = sqlite3_value_type(apVal[8])==SQLITE_NULL;
+
+ iMethod = sqlite3_value_int(apVal[8]);
+ sz = nIn;
+ pData = aIn;
+ nData = nIn;
+ if( iMethod!=0 && iMethod!=8 ){
+ zipfileTableErr(pTab, "unknown compression method: %d", iMethod);
+ rc = SQLITE_CONSTRAINT;
+ }else{
+ if( bAuto || iMethod ){
+ int nCmp;
+ rc = zipfileDeflate(aIn, nIn, &pFree, &nCmp, &pTab->base.zErrMsg);
+ if( rc==SQLITE_OK ){
+ if( iMethod || nCmp<nIn ){
+ iMethod = 8;
+ pData = pFree;
+ nData = nCmp;
+ }
+ }
+ }
+ iCrc32 = crc32(0, aIn, nIn);
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = zipfileGetMode(apVal[3], bIsDir, &mode, &pTab->base.zErrMsg);
+ }
+
+ if( rc==SQLITE_OK ){
+ zPath = (const char*)sqlite3_value_text(apVal[2]);
+ nPath = (int)strlen(zPath);
+ mTime = zipfileGetTime(apVal[4]);
+ }
+
+ if( rc==SQLITE_OK && bIsDir ){
+ /* For a directory, check that the last character in the path is a
+ ** '/'. This appears to be required for compatibility with info-zip
+ ** (the unzip command on unix). It does not create directories
+ ** otherwise. */
+ if( zPath[nPath-1]!='/' ){
+ zFree = sqlite3_mprintf("%s/", zPath);
+ if( zFree==0 ){ rc = SQLITE_NOMEM; }
+ zPath = (const char*)zFree;
+ nPath++;
+ }
+ }
+
+ /* Check that we're not inserting a duplicate entry -OR- updating an
+ ** entry with a path, thereby making it into a duplicate. */
+ if( (pOld==0 || bUpdate) && rc==SQLITE_OK ){
+ ZipfileEntry *p;
+ for(p=pTab->pFirstEntry; p; p=p->pNext){
+ if( zipfileComparePath(p->cds.zFile, zPath, nPath)==0 ){
+ switch( sqlite3_vtab_on_conflict(pTab->db) ){
+ case SQLITE_IGNORE: {
+ goto zipfile_update_done;
+ }
+ case SQLITE_REPLACE: {
+ pOld2 = p;
+ break;
+ }
+ default: {
+ zipfileTableErr(pTab, "duplicate name: \"%s\"", zPath);
+ rc = SQLITE_CONSTRAINT;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ /* Create the new CDS record. */
+ pNew = zipfileNewEntry(zPath);
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ pNew->cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY;
+ pNew->cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED;
+ pNew->cds.flags = ZIPFILE_NEWENTRY_FLAGS;
+ pNew->cds.iCompression = (u16)iMethod;
+ zipfileMtimeToDos(&pNew->cds, mTime);
+ pNew->cds.crc32 = iCrc32;
+ pNew->cds.szCompressed = nData;
+ pNew->cds.szUncompressed = (u32)sz;
+ pNew->cds.iExternalAttr = (mode<<16);
+ pNew->cds.iOffset = (u32)pTab->szCurrent;
+ pNew->cds.nFile = (u16)nPath;
+ pNew->mUnixTime = (u32)mTime;
+ rc = zipfileAppendEntry(pTab, pNew, pData, nData);
+ zipfileAddEntry(pTab, pOld, pNew);
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK && (pOld || pOld2) ){
+ ZipfileCsr *pCsr;
+ for(pCsr=pTab->pCsrList; pCsr; pCsr=pCsr->pCsrNext){
+ if( pCsr->pCurrent && (pCsr->pCurrent==pOld || pCsr->pCurrent==pOld2) ){
+ pCsr->pCurrent = pCsr->pCurrent->pNext;
+ pCsr->bNoop = 1;
+ }
+ }
+
+ zipfileRemoveEntryFromList(pTab, pOld);
+ zipfileRemoveEntryFromList(pTab, pOld2);
+ }
+
+zipfile_update_done:
+ sqlite3_free(pFree);
+ sqlite3_free(zFree);
+ return rc;
+}
+
+static int zipfileSerializeEOCD(ZipfileEOCD *p, u8 *aBuf){
+ u8 *a = aBuf;
+ zipfileWrite32(a, ZIPFILE_SIGNATURE_EOCD);
+ zipfileWrite16(a, p->iDisk);
+ zipfileWrite16(a, p->iFirstDisk);
+ zipfileWrite16(a, p->nEntry);
+ zipfileWrite16(a, p->nEntryTotal);
+ zipfileWrite32(a, p->nSize);
+ zipfileWrite32(a, p->iOffset);
+ zipfileWrite16(a, 0); /* Size of trailing comment in bytes*/
+
+ return a-aBuf;
+}
+
+static int zipfileAppendEOCD(ZipfileTab *pTab, ZipfileEOCD *p){
+ int nBuf = zipfileSerializeEOCD(p, pTab->aBuffer);
+ assert( nBuf==ZIPFILE_EOCD_FIXED_SZ );
+ return zipfileAppendData(pTab, pTab->aBuffer, nBuf);
+}
+
+/*
+** Serialize the CDS structure into buffer aBuf[]. Return the number
+** of bytes written.
+*/
+static int zipfileSerializeCDS(ZipfileEntry *pEntry, u8 *aBuf){
+ u8 *a = aBuf;
+ ZipfileCDS *pCDS = &pEntry->cds;
+
+ if( pEntry->aExtra==0 ){
+ pCDS->nExtra = 9;
+ }
+
+ zipfileWrite32(a, ZIPFILE_SIGNATURE_CDS);
+ zipfileWrite16(a, pCDS->iVersionMadeBy);
+ zipfileWrite16(a, pCDS->iVersionExtract);
+ zipfileWrite16(a, pCDS->flags);
+ zipfileWrite16(a, pCDS->iCompression);
+ zipfileWrite16(a, pCDS->mTime);
+ zipfileWrite16(a, pCDS->mDate);
+ zipfileWrite32(a, pCDS->crc32);
+ zipfileWrite32(a, pCDS->szCompressed);
+ zipfileWrite32(a, pCDS->szUncompressed);
+ assert( a==&aBuf[ZIPFILE_CDS_NFILE_OFF] );
+ zipfileWrite16(a, pCDS->nFile);
+ zipfileWrite16(a, pCDS->nExtra);
+ zipfileWrite16(a, pCDS->nComment);
+ zipfileWrite16(a, pCDS->iDiskStart);
+ zipfileWrite16(a, pCDS->iInternalAttr);
+ zipfileWrite32(a, pCDS->iExternalAttr);
+ zipfileWrite32(a, pCDS->iOffset);
+
+ memcpy(a, pCDS->zFile, pCDS->nFile);
+ a += pCDS->nFile;
+
+ if( pEntry->aExtra ){
+ int n = (int)pCDS->nExtra + (int)pCDS->nComment;
+ memcpy(a, pEntry->aExtra, n);
+ a += n;
+ }else{
+ assert( pCDS->nExtra==9 );
+ zipfileWrite16(a, ZIPFILE_EXTRA_TIMESTAMP);
+ zipfileWrite16(a, 5);
+ *a++ = 0x01;
+ zipfileWrite32(a, pEntry->mUnixTime);
+ }
+
+ return a-aBuf;
+}
+
+static int zipfileCommit(sqlite3_vtab *pVtab){
+ ZipfileTab *pTab = (ZipfileTab*)pVtab;
+ int rc = SQLITE_OK;
+ if( pTab->pWriteFd ){
+ i64 iOffset = pTab->szCurrent;
+ ZipfileEntry *p;
+ ZipfileEOCD eocd;
+ int nEntry = 0;
+
+ /* Write out all entries */
+ for(p=pTab->pFirstEntry; rc==SQLITE_OK && p; p=p->pNext){
+ int n = zipfileSerializeCDS(p, pTab->aBuffer);
+ rc = zipfileAppendData(pTab, pTab->aBuffer, n);
+ nEntry++;
+ }
+
+ /* Write out the EOCD record */
+ eocd.iDisk = 0;
+ eocd.iFirstDisk = 0;
+ eocd.nEntry = (u16)nEntry;
+ eocd.nEntryTotal = (u16)nEntry;
+ eocd.nSize = (u32)(pTab->szCurrent - iOffset);
+ eocd.iOffset = (u32)iOffset;
+ rc = zipfileAppendEOCD(pTab, &eocd);
+
+ zipfileCleanupTransaction(pTab);
+ }
+ return rc;
+}
+
+static int zipfileRollback(sqlite3_vtab *pVtab){
+ return zipfileCommit(pVtab);
+}
+
+static ZipfileCsr *zipfileFindCursor(ZipfileTab *pTab, i64 iId){
+ ZipfileCsr *pCsr;
+ for(pCsr=pTab->pCsrList; pCsr; pCsr=pCsr->pCsrNext){
+ if( iId==pCsr->iId ) break;
+ }
+ return pCsr;
+}
+
+static void zipfileFunctionCds(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ ZipfileCsr *pCsr;
+ ZipfileTab *pTab = (ZipfileTab*)sqlite3_user_data(context);
+ assert( argc>0 );
+
+ pCsr = zipfileFindCursor(pTab, sqlite3_value_int64(argv[0]));
+ if( pCsr ){
+ ZipfileCDS *p = &pCsr->pCurrent->cds;
+ char *zRes = sqlite3_mprintf("{"
+ "\"version-made-by\" : %u, "
+ "\"version-to-extract\" : %u, "
+ "\"flags\" : %u, "
+ "\"compression\" : %u, "
+ "\"time\" : %u, "
+ "\"date\" : %u, "
+ "\"crc32\" : %u, "
+ "\"compressed-size\" : %u, "
+ "\"uncompressed-size\" : %u, "
+ "\"file-name-length\" : %u, "
+ "\"extra-field-length\" : %u, "
+ "\"file-comment-length\" : %u, "
+ "\"disk-number-start\" : %u, "
+ "\"internal-attr\" : %u, "
+ "\"external-attr\" : %u, "
+ "\"offset\" : %u }",
+ (u32)p->iVersionMadeBy, (u32)p->iVersionExtract,
+ (u32)p->flags, (u32)p->iCompression,
+ (u32)p->mTime, (u32)p->mDate,
+ (u32)p->crc32, (u32)p->szCompressed,
+ (u32)p->szUncompressed, (u32)p->nFile,
+ (u32)p->nExtra, (u32)p->nComment,
+ (u32)p->iDiskStart, (u32)p->iInternalAttr,
+ (u32)p->iExternalAttr, (u32)p->iOffset
+ );
+
+ if( zRes==0 ){
+ sqlite3_result_error_nomem(context);
+ }else{
+ sqlite3_result_text(context, zRes, -1, SQLITE_TRANSIENT);
+ sqlite3_free(zRes);
+ }
+ }
+}
+
+/*
+** xFindFunction method.
+*/
+static int zipfileFindFunction(
+ sqlite3_vtab *pVtab, /* Virtual table handle */
+ int nArg, /* Number of SQL function arguments */
+ const char *zName, /* Name of SQL function */
+ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */
+ void **ppArg /* OUT: User data for *pxFunc */
+){
+ if( sqlite3_stricmp("zipfile_cds", zName)==0 ){
+ *pxFunc = zipfileFunctionCds;
+ *ppArg = (void*)pVtab;
+ return 1;
+ }
+ return 0;
+}
+
+typedef struct ZipfileBuffer ZipfileBuffer;
+struct ZipfileBuffer {
+ u8 *a; /* Pointer to buffer */
+ int n; /* Size of buffer in bytes */
+ int nAlloc; /* Byte allocated at a[] */
+};
+
+typedef struct ZipfileCtx ZipfileCtx;
+struct ZipfileCtx {
+ int nEntry;
+ ZipfileBuffer body;
+ ZipfileBuffer cds;
+};
+
+static int zipfileBufferGrow(ZipfileBuffer *pBuf, int nByte){
+ if( pBuf->n+nByte>pBuf->nAlloc ){
+ u8 *aNew;
+ sqlite3_int64 nNew = pBuf->n ? pBuf->n*2 : 512;
+ int nReq = pBuf->n + nByte;
+
+ while( nNew<nReq ) nNew = nNew*2;
+ aNew = sqlite3_realloc64(pBuf->a, nNew);
+ if( aNew==0 ) return SQLITE_NOMEM;
+ pBuf->a = aNew;
+ pBuf->nAlloc = (int)nNew;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** xStep() callback for the zipfile() aggregate. This can be called in
+** any of the following ways:
+**
+** SELECT zipfile(name,data) ...
+** SELECT zipfile(name,mode,mtime,data) ...
+** SELECT zipfile(name,mode,mtime,data,method) ...
+*/
+void zipfileStep(sqlite3_context *pCtx, int nVal, sqlite3_value **apVal){
+ ZipfileCtx *p; /* Aggregate function context */
+ ZipfileEntry e; /* New entry to add to zip archive */
+
+ sqlite3_value *pName = 0;
+ sqlite3_value *pMode = 0;
+ sqlite3_value *pMtime = 0;
+ sqlite3_value *pData = 0;
+ sqlite3_value *pMethod = 0;
+
+ int bIsDir = 0;
+ u32 mode;
+ int rc = SQLITE_OK;
+ char *zErr = 0;
+
+ int iMethod = -1; /* Compression method to use (0 or 8) */
+
+ const u8 *aData = 0; /* Possibly compressed data for new entry */
+ int nData = 0; /* Size of aData[] in bytes */
+ int szUncompressed = 0; /* Size of data before compression */
+ u8 *aFree = 0; /* Free this before returning */
+ u32 iCrc32 = 0; /* crc32 of uncompressed data */
+
+ char *zName = 0; /* Path (name) of new entry */
+ int nName = 0; /* Size of zName in bytes */
+ char *zFree = 0; /* Free this before returning */
+ int nByte;
+
+ memset(&e, 0, sizeof(e));
+ p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx));
+ if( p==0 ) return;
+
+ /* Martial the arguments into stack variables */
+ if( nVal!=2 && nVal!=4 && nVal!=5 ){
+ zErr = sqlite3_mprintf("wrong number of arguments to function zipfile()");
+ rc = SQLITE_ERROR;
+ goto zipfile_step_out;
+ }
+ pName = apVal[0];
+ if( nVal==2 ){
+ pData = apVal[1];
+ }else{
+ pMode = apVal[1];
+ pMtime = apVal[2];
+ pData = apVal[3];
+ if( nVal==5 ){
+ pMethod = apVal[4];
+ }
+ }
+
+ /* Check that the 'name' parameter looks ok. */
+ zName = (char*)sqlite3_value_text(pName);
+ nName = sqlite3_value_bytes(pName);
+ if( zName==0 ){
+ zErr = sqlite3_mprintf("first argument to zipfile() must be non-NULL");
+ rc = SQLITE_ERROR;
+ goto zipfile_step_out;
+ }
+
+ /* Inspect the 'method' parameter. This must be either 0 (store), 8 (use
+ ** deflate compression) or NULL (choose automatically). */
+ if( pMethod && SQLITE_NULL!=sqlite3_value_type(pMethod) ){
+ iMethod = (int)sqlite3_value_int64(pMethod);
+ if( iMethod!=0 && iMethod!=8 ){
+ zErr = sqlite3_mprintf("illegal method value: %d", iMethod);
+ rc = SQLITE_ERROR;
+ goto zipfile_step_out;
+ }
+ }
+
+ /* Now inspect the data. If this is NULL, then the new entry must be a
+ ** directory. Otherwise, figure out whether or not the data should
+ ** be deflated or simply stored in the zip archive. */
+ if( sqlite3_value_type(pData)==SQLITE_NULL ){
+ bIsDir = 1;
+ iMethod = 0;
+ }else{
+ aData = sqlite3_value_blob(pData);
+ szUncompressed = nData = sqlite3_value_bytes(pData);
+ iCrc32 = crc32(0, aData, nData);
+ if( iMethod<0 || iMethod==8 ){
+ int nOut = 0;
+ rc = zipfileDeflate(aData, nData, &aFree, &nOut, &zErr);
+ if( rc!=SQLITE_OK ){
+ goto zipfile_step_out;
+ }
+ if( iMethod==8 || nOut<nData ){
+ aData = aFree;
+ nData = nOut;
+ iMethod = 8;
+ }else{
+ iMethod = 0;
+ }
+ }
+ }
+
+ /* Decode the "mode" argument. */
+ rc = zipfileGetMode(pMode, bIsDir, &mode, &zErr);
+ if( rc ) goto zipfile_step_out;
+
+ /* Decode the "mtime" argument. */
+ e.mUnixTime = zipfileGetTime(pMtime);
+
+ /* If this is a directory entry, ensure that there is exactly one '/'
+ ** at the end of the path. Or, if this is not a directory and the path
+ ** ends in '/' it is an error. */
+ if( bIsDir==0 ){
+ if( zName[nName-1]=='/' ){
+ zErr = sqlite3_mprintf("non-directory name must not end with /");
+ rc = SQLITE_ERROR;
+ goto zipfile_step_out;
+ }
+ }else{
+ if( zName[nName-1]!='/' ){
+ zName = zFree = sqlite3_mprintf("%s/", zName);
+ nName++;
+ if( zName==0 ){
+ rc = SQLITE_NOMEM;
+ goto zipfile_step_out;
+ }
+ }else{
+ while( nName>1 && zName[nName-2]=='/' ) nName--;
+ }
+ }
+
+ /* Assemble the ZipfileEntry object for the new zip archive entry */
+ e.cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY;
+ e.cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED;
+ e.cds.flags = ZIPFILE_NEWENTRY_FLAGS;
+ e.cds.iCompression = (u16)iMethod;
+ zipfileMtimeToDos(&e.cds, (u32)e.mUnixTime);
+ e.cds.crc32 = iCrc32;
+ e.cds.szCompressed = nData;
+ e.cds.szUncompressed = szUncompressed;
+ e.cds.iExternalAttr = (mode<<16);
+ e.cds.iOffset = p->body.n;
+ e.cds.nFile = (u16)nName;
+ e.cds.zFile = zName;
+
+ /* Append the LFH to the body of the new archive */
+ nByte = ZIPFILE_LFH_FIXED_SZ + e.cds.nFile + 9;
+ if( (rc = zipfileBufferGrow(&p->body, nByte)) ) goto zipfile_step_out;
+ p->body.n += zipfileSerializeLFH(&e, &p->body.a[p->body.n]);
+
+ /* Append the data to the body of the new archive */
+ if( nData>0 ){
+ if( (rc = zipfileBufferGrow(&p->body, nData)) ) goto zipfile_step_out;
+ memcpy(&p->body.a[p->body.n], aData, nData);
+ p->body.n += nData;
+ }
+
+ /* Append the CDS record to the directory of the new archive */
+ nByte = ZIPFILE_CDS_FIXED_SZ + e.cds.nFile + 9;
+ if( (rc = zipfileBufferGrow(&p->cds, nByte)) ) goto zipfile_step_out;
+ p->cds.n += zipfileSerializeCDS(&e, &p->cds.a[p->cds.n]);
+
+ /* Increment the count of entries in the archive */
+ p->nEntry++;
+
+ zipfile_step_out:
+ sqlite3_free(aFree);
+ sqlite3_free(zFree);
+ if( rc ){
+ if( zErr ){
+ sqlite3_result_error(pCtx, zErr, -1);
+ }else{
+ sqlite3_result_error_code(pCtx, rc);
+ }
+ }
+ sqlite3_free(zErr);
+}
+
+/*
+** xFinalize() callback for zipfile aggregate function.
+*/
+void zipfileFinal(sqlite3_context *pCtx){
+ ZipfileCtx *p;
+ ZipfileEOCD eocd;
+ sqlite3_int64 nZip;
+ u8 *aZip;
+
+ p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx));
+ if( p==0 ) return;
+ if( p->nEntry>0 ){
+ memset(&eocd, 0, sizeof(eocd));
+ eocd.nEntry = (u16)p->nEntry;
+ eocd.nEntryTotal = (u16)p->nEntry;
+ eocd.nSize = p->cds.n;
+ eocd.iOffset = p->body.n;
+
+ nZip = p->body.n + p->cds.n + ZIPFILE_EOCD_FIXED_SZ;
+ aZip = (u8*)sqlite3_malloc64(nZip);
+ if( aZip==0 ){
+ sqlite3_result_error_nomem(pCtx);
+ }else{
+ memcpy(aZip, p->body.a, p->body.n);
+ memcpy(&aZip[p->body.n], p->cds.a, p->cds.n);
+ zipfileSerializeEOCD(&eocd, &aZip[p->body.n + p->cds.n]);
+ sqlite3_result_blob(pCtx, aZip, (int)nZip, zipfileFree);
+ }
+ }
+
+ sqlite3_free(p->body.a);
+ sqlite3_free(p->cds.a);
+}
+
+
+/*
+** Register the "zipfile" virtual table.
+*/
+static int zipfileRegister(sqlite3 *db){
+ static sqlite3_module zipfileModule = {
+ 1, /* iVersion */
+ zipfileConnect, /* xCreate */
+ zipfileConnect, /* xConnect */
+ zipfileBestIndex, /* xBestIndex */
+ zipfileDisconnect, /* xDisconnect */
+ zipfileDisconnect, /* xDestroy */
+ zipfileOpen, /* xOpen - open a cursor */
+ zipfileClose, /* xClose - close a cursor */
+ zipfileFilter, /* xFilter - configure scan constraints */
+ zipfileNext, /* xNext - advance a cursor */
+ zipfileEof, /* xEof - check for end of scan */
+ zipfileColumn, /* xColumn - read data */
+ 0, /* xRowid - read data */
+ zipfileUpdate, /* xUpdate */
+ zipfileBegin, /* xBegin */
+ 0, /* xSync */
+ zipfileCommit, /* xCommit */
+ zipfileRollback, /* xRollback */
+ zipfileFindFunction, /* xFindMethod */
+ 0, /* xRename */
+ };
+
+ int rc = sqlite3_create_module(db, "zipfile" , &zipfileModule, 0);
+ if( rc==SQLITE_OK ) rc = sqlite3_overload_function(db, "zipfile_cds", -1);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(db, "zipfile", -1, SQLITE_UTF8, 0, 0,
+ zipfileStep, zipfileFinal
+ );
+ }
+ return rc;
+}
+#else /* SQLITE_OMIT_VIRTUALTABLE */
+# define zipfileRegister(x) SQLITE_OK
+#endif
+
+#ifdef _WIN32
+
+#endif
+int sqlite3_zipfile_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ SQLITE_EXTENSION_INIT2(pApi);
+ (void)pzErrMsg; /* Unused parameter */
+ return zipfileRegister(db);
+}
+
+/************************* End ../ext/misc/zipfile.c ********************/
+/************************* Begin ../ext/misc/sqlar.c ******************/
+/*
+** 2017-12-17
+**
+** 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.
+**
+******************************************************************************
+**
+** Utility functions sqlar_compress() and sqlar_uncompress(). Useful
+** for working with sqlar archives and used by the shell tool's built-in
+** sqlar support.
+*/
+SQLITE_EXTENSION_INIT1
+#include <zlib.h>
+
+/*
+** Implementation of the "sqlar_compress(X)" SQL function.
+**
+** If the type of X is SQLITE_BLOB, and compressing that blob using
+** zlib utility function compress() yields a smaller blob, return the
+** compressed blob. Otherwise, return a copy of X.
+**
+** SQLar uses the "zlib format" for compressed content. The zlib format
+** contains a two-byte identification header and a four-byte checksum at
+** the end. This is different from ZIP which uses the raw deflate format.
+**
+** Future enhancements to SQLar might add support for new compression formats.
+** If so, those new formats will be identified by alternative headers in the
+** compressed data.
+*/
+static void sqlarCompressFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ assert( argc==1 );
+ if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){
+ const Bytef *pData = sqlite3_value_blob(argv[0]);
+ uLong nData = sqlite3_value_bytes(argv[0]);
+ uLongf nOut = compressBound(nData);
+ Bytef *pOut;
+
+ pOut = (Bytef*)sqlite3_malloc(nOut);
+ if( pOut==0 ){
+ sqlite3_result_error_nomem(context);
+ return;
+ }else{
+ if( Z_OK!=compress(pOut, &nOut, pData, nData) ){
+ sqlite3_result_error(context, "error in compress()", -1);
+ }else if( nOut<nData ){
+ sqlite3_result_blob(context, pOut, nOut, SQLITE_TRANSIENT);
+ }else{
+ sqlite3_result_value(context, argv[0]);
+ }
+ sqlite3_free(pOut);
+ }
+ }else{
+ sqlite3_result_value(context, argv[0]);
+ }
+}
+
+/*
+** Implementation of the "sqlar_uncompress(X,SZ)" SQL function
+**
+** Parameter SZ is interpreted as an integer. If it is less than or
+** equal to zero, then this function returns a copy of X. Or, if
+** SZ is equal to the size of X when interpreted as a blob, also
+** return a copy of X. Otherwise, decompress blob X using zlib
+** utility function uncompress() and return the results (another
+** blob).
+*/
+static void sqlarUncompressFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ uLong nData;
+ uLongf sz;
+
+ assert( argc==2 );
+ sz = sqlite3_value_int(argv[1]);
+
+ if( sz<=0 || sz==(nData = sqlite3_value_bytes(argv[0])) ){
+ sqlite3_result_value(context, argv[0]);
+ }else{
+ const Bytef *pData= sqlite3_value_blob(argv[0]);
+ Bytef *pOut = sqlite3_malloc(sz);
+ if( Z_OK!=uncompress(pOut, &sz, pData, nData) ){
+ sqlite3_result_error(context, "error in uncompress()", -1);
+ }else{
+ sqlite3_result_blob(context, pOut, sz, SQLITE_TRANSIENT);
+ }
+ sqlite3_free(pOut);
+ }
+}
+
+
+#ifdef _WIN32
+
+#endif
+int sqlite3_sqlar_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ int rc = SQLITE_OK;
+ SQLITE_EXTENSION_INIT2(pApi);
+ (void)pzErrMsg; /* Unused parameter */
+ rc = sqlite3_create_function(db, "sqlar_compress", 1, SQLITE_UTF8, 0,
+ sqlarCompressFunc, 0, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(db, "sqlar_uncompress", 2, SQLITE_UTF8, 0,
+ sqlarUncompressFunc, 0, 0);
+ }
+ return rc;
+}
+
+/************************* End ../ext/misc/sqlar.c ********************/
+#endif
+/************************* Begin ../ext/expert/sqlite3expert.h ******************/
+/*
+** 2017 April 07
+**
+** 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.
+**
+*************************************************************************
+*/
+
+
+
+typedef struct sqlite3expert sqlite3expert;
+
+/*
+** Create a new sqlite3expert object.
+**
+** If successful, a pointer to the new object is returned and (*pzErr) set
+** to NULL. Or, if an error occurs, NULL is returned and (*pzErr) set to
+** an English-language error message. In this case it is the responsibility
+** of the caller to eventually free the error message buffer using
+** sqlite3_free().
+*/
+sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErr);
+
+/*
+** Configure an sqlite3expert object.
+**
+** EXPERT_CONFIG_SAMPLE:
+** By default, sqlite3_expert_analyze() generates sqlite_stat1 data for
+** each candidate index. This involves scanning and sorting the entire
+** contents of each user database table once for each candidate index
+** associated with the table. For large databases, this can be
+** prohibitively slow. This option allows the sqlite3expert object to
+** be configured so that sqlite_stat1 data is instead generated based on a
+** subset of each table, or so that no sqlite_stat1 data is used at all.
+**
+** A single integer argument is passed to this option. If the value is less
+** than or equal to zero, then no sqlite_stat1 data is generated or used by
+** the analysis - indexes are recommended based on the database schema only.
+** Or, if the value is 100 or greater, complete sqlite_stat1 data is
+** generated for each candidate index (this is the default). Finally, if the
+** value falls between 0 and 100, then it represents the percentage of user
+** table rows that should be considered when generating sqlite_stat1 data.
+**
+** Examples:
+**
+** // Do not generate any sqlite_stat1 data
+** sqlite3_expert_config(pExpert, EXPERT_CONFIG_SAMPLE, 0);
+**
+** // Generate sqlite_stat1 data based on 10% of the rows in each table.
+** sqlite3_expert_config(pExpert, EXPERT_CONFIG_SAMPLE, 10);
+*/
+int sqlite3_expert_config(sqlite3expert *p, int op, ...);
+
+#define EXPERT_CONFIG_SAMPLE 1 /* int */
+
+/*
+** Specify zero or more SQL statements to be included in the analysis.
+**
+** Buffer zSql must contain zero or more complete SQL statements. This
+** function parses all statements contained in the buffer and adds them
+** to the internal list of statements to analyze. If successful, SQLITE_OK
+** is returned and (*pzErr) set to NULL. Or, if an error occurs - for example
+** due to a error in the SQL - an SQLite error code is returned and (*pzErr)
+** may be set to point to an English language error message. In this case
+** the caller is responsible for eventually freeing the error message buffer
+** using sqlite3_free().
+**
+** If an error does occur while processing one of the statements in the
+** buffer passed as the second argument, none of the statements in the
+** buffer are added to the analysis.
+**
+** This function must be called before sqlite3_expert_analyze(). If a call
+** to this function is made on an sqlite3expert object that has already
+** been passed to sqlite3_expert_analyze() SQLITE_MISUSE is returned
+** immediately and no statements are added to the analysis.
+*/
+int sqlite3_expert_sql(
+ sqlite3expert *p, /* From a successful sqlite3_expert_new() */
+ const char *zSql, /* SQL statement(s) to add */
+ char **pzErr /* OUT: Error message (if any) */
+);
+
+
+/*
+** This function is called after the sqlite3expert object has been configured
+** with all SQL statements using sqlite3_expert_sql() to actually perform
+** the analysis. Once this function has been called, it is not possible to
+** add further SQL statements to the analysis.
+**
+** If successful, SQLITE_OK is returned and (*pzErr) is set to NULL. Or, if
+** an error occurs, an SQLite error code is returned and (*pzErr) set to
+** point to a buffer containing an English language error message. In this
+** case it is the responsibility of the caller to eventually free the buffer
+** using sqlite3_free().
+**
+** If an error does occur within this function, the sqlite3expert object
+** is no longer useful for any purpose. At that point it is no longer
+** possible to add further SQL statements to the object or to re-attempt
+** the analysis. The sqlite3expert object must still be freed using a call
+** sqlite3_expert_destroy().
+*/
+int sqlite3_expert_analyze(sqlite3expert *p, char **pzErr);
+
+/*
+** Return the total number of statements loaded using sqlite3_expert_sql().
+** The total number of SQL statements may be different from the total number
+** to calls to sqlite3_expert_sql().
+*/
+int sqlite3_expert_count(sqlite3expert*);
+
+/*
+** Return a component of the report.
+**
+** This function is called after sqlite3_expert_analyze() to extract the
+** results of the analysis. Each call to this function returns either a
+** NULL pointer or a pointer to a buffer containing a nul-terminated string.
+** The value passed as the third argument must be one of the EXPERT_REPORT_*
+** #define constants defined below.
+**
+** For some EXPERT_REPORT_* parameters, the buffer returned contains
+** information relating to a specific SQL statement. In these cases that
+** SQL statement is identified by the value passed as the second argument.
+** SQL statements are numbered from 0 in the order in which they are parsed.
+** If an out-of-range value (less than zero or equal to or greater than the
+** value returned by sqlite3_expert_count()) is passed as the second argument
+** along with such an EXPERT_REPORT_* parameter, NULL is always returned.
+**
+** EXPERT_REPORT_SQL:
+** Return the text of SQL statement iStmt.
+**
+** EXPERT_REPORT_INDEXES:
+** Return a buffer containing the CREATE INDEX statements for all recommended
+** indexes for statement iStmt. If there are no new recommeded indexes, NULL
+** is returned.
+**
+** EXPERT_REPORT_PLAN:
+** Return a buffer containing the EXPLAIN QUERY PLAN output for SQL query
+** iStmt after the proposed indexes have been added to the database schema.
+**
+** EXPERT_REPORT_CANDIDATES:
+** Return a pointer to a buffer containing the CREATE INDEX statements
+** for all indexes that were tested (for all SQL statements). The iStmt
+** parameter is ignored for EXPERT_REPORT_CANDIDATES calls.
+*/
+const char *sqlite3_expert_report(sqlite3expert*, int iStmt, int eReport);
+
+/*
+** Values for the third argument passed to sqlite3_expert_report().
+*/
+#define EXPERT_REPORT_SQL 1
+#define EXPERT_REPORT_INDEXES 2
+#define EXPERT_REPORT_PLAN 3
+#define EXPERT_REPORT_CANDIDATES 4
+
+/*
+** Free an (sqlite3expert*) handle and all associated resources. There
+** should be one call to this function for each successful call to
+** sqlite3-expert_new().
+*/
+void sqlite3_expert_destroy(sqlite3expert*);
+
+
+
+/************************* End ../ext/expert/sqlite3expert.h ********************/
+/************************* Begin ../ext/expert/sqlite3expert.c ******************/
+/*
+** 2017 April 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.
+**
+*************************************************************************
+*/
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+/* typedef sqlite3_int64 i64; */
+/* typedef sqlite3_uint64 u64; */
+
+typedef struct IdxColumn IdxColumn;
+typedef struct IdxConstraint IdxConstraint;
+typedef struct IdxScan IdxScan;
+typedef struct IdxStatement IdxStatement;
+typedef struct IdxTable IdxTable;
+typedef struct IdxWrite IdxWrite;
+
+#define STRLEN (int)strlen
+
+/*
+** A temp table name that we assume no user database will actually use.
+** If this assumption proves incorrect triggers on the table with the
+** conflicting name will be ignored.
+*/
+#define UNIQUE_TABLE_NAME "t592690916721053953805701627921227776"
+
+/*
+** A single constraint. Equivalent to either "col = ?" or "col < ?" (or
+** any other type of single-ended range constraint on a column).
+**
+** pLink:
+** Used to temporarily link IdxConstraint objects into lists while
+** creating candidate indexes.
+*/
+struct IdxConstraint {
+ char *zColl; /* Collation sequence */
+ int bRange; /* True for range, false for eq */
+ int iCol; /* Constrained table column */
+ int bFlag; /* Used by idxFindCompatible() */
+ int bDesc; /* True if ORDER BY <expr> DESC */
+ IdxConstraint *pNext; /* Next constraint in pEq or pRange list */
+ IdxConstraint *pLink; /* See above */
+};
+
+/*
+** A single scan of a single table.
+*/
+struct IdxScan {
+ IdxTable *pTab; /* Associated table object */
+ int iDb; /* Database containing table zTable */
+ i64 covering; /* Mask of columns required for cov. index */
+ IdxConstraint *pOrder; /* ORDER BY columns */
+ IdxConstraint *pEq; /* List of == constraints */
+ IdxConstraint *pRange; /* List of < constraints */
+ IdxScan *pNextScan; /* Next IdxScan object for same analysis */
+};
+
+/*
+** Information regarding a single database table. Extracted from
+** "PRAGMA table_info" by function idxGetTableInfo().
+*/
+struct IdxColumn {
+ char *zName;
+ char *zColl;
+ int iPk;
+};
+struct IdxTable {
+ int nCol;
+ char *zName; /* Table name */
+ IdxColumn *aCol;
+ IdxTable *pNext; /* Next table in linked list of all tables */
+};
+
+/*
+** An object of the following type is created for each unique table/write-op
+** seen. The objects are stored in a singly-linked list beginning at
+** sqlite3expert.pWrite.
+*/
+struct IdxWrite {
+ IdxTable *pTab;
+ int eOp; /* SQLITE_UPDATE, DELETE or INSERT */
+ IdxWrite *pNext;
+};
+
+/*
+** Each statement being analyzed is represented by an instance of this
+** structure.
+*/
+struct IdxStatement {
+ int iId; /* Statement number */
+ char *zSql; /* SQL statement */
+ char *zIdx; /* Indexes */
+ char *zEQP; /* Plan */
+ IdxStatement *pNext;
+};
+
+
+/*
+** A hash table for storing strings. With space for a payload string
+** with each entry. Methods are:
+**
+** idxHashInit()
+** idxHashClear()
+** idxHashAdd()
+** idxHashSearch()
+*/
+#define IDX_HASH_SIZE 1023
+typedef struct IdxHashEntry IdxHashEntry;
+typedef struct IdxHash IdxHash;
+struct IdxHashEntry {
+ char *zKey; /* nul-terminated key */
+ char *zVal; /* nul-terminated value string */
+ char *zVal2; /* nul-terminated value string 2 */
+ IdxHashEntry *pHashNext; /* Next entry in same hash bucket */
+ IdxHashEntry *pNext; /* Next entry in hash */
+};
+struct IdxHash {
+ IdxHashEntry *pFirst;
+ IdxHashEntry *aHash[IDX_HASH_SIZE];
+};
+
+/*
+** sqlite3expert object.
+*/
+struct sqlite3expert {
+ int iSample; /* Percentage of tables to sample for stat1 */
+ sqlite3 *db; /* User database */
+ sqlite3 *dbm; /* In-memory db for this analysis */
+ sqlite3 *dbv; /* Vtab schema for this analysis */
+ IdxTable *pTable; /* List of all IdxTable objects */
+ IdxScan *pScan; /* List of scan objects */
+ IdxWrite *pWrite; /* List of write objects */
+ IdxStatement *pStatement; /* List of IdxStatement objects */
+ int bRun; /* True once analysis has run */
+ char **pzErrmsg;
+ int rc; /* Error code from whereinfo hook */
+ IdxHash hIdx; /* Hash containing all candidate indexes */
+ char *zCandidates; /* For EXPERT_REPORT_CANDIDATES */
+};
+
+
+/*
+** Allocate and return nByte bytes of zeroed memory using sqlite3_malloc().
+** If the allocation fails, set *pRc to SQLITE_NOMEM and return NULL.
+*/
+static void *idxMalloc(int *pRc, int nByte){
+ void *pRet;
+ assert( *pRc==SQLITE_OK );
+ assert( nByte>0 );
+ pRet = sqlite3_malloc(nByte);
+ if( pRet ){
+ memset(pRet, 0, nByte);
+ }else{
+ *pRc = SQLITE_NOMEM;
+ }
+ return pRet;
+}
+
+/*
+** Initialize an IdxHash hash table.
+*/
+static void idxHashInit(IdxHash *pHash){
+ memset(pHash, 0, sizeof(IdxHash));
+}
+
+/*
+** Reset an IdxHash hash table.
+*/
+static void idxHashClear(IdxHash *pHash){
+ int i;
+ for(i=0; i<IDX_HASH_SIZE; i++){
+ IdxHashEntry *pEntry;
+ IdxHashEntry *pNext;
+ for(pEntry=pHash->aHash[i]; pEntry; pEntry=pNext){
+ pNext = pEntry->pHashNext;
+ sqlite3_free(pEntry->zVal2);
+ sqlite3_free(pEntry);
+ }
+ }
+ memset(pHash, 0, sizeof(IdxHash));
+}
+
+/*
+** Return the index of the hash bucket that the string specified by the
+** arguments to this function belongs.
+*/
+static int idxHashString(const char *z, int n){
+ unsigned int ret = 0;
+ int i;
+ for(i=0; i<n; i++){
+ ret += (ret<<3) + (unsigned char)(z[i]);
+ }
+ return (int)(ret % IDX_HASH_SIZE);
+}
+
+/*
+** If zKey is already present in the hash table, return non-zero and do
+** nothing. Otherwise, add an entry with key zKey and payload string zVal to
+** the hash table passed as the second argument.
+*/
+static int idxHashAdd(
+ int *pRc,
+ IdxHash *pHash,
+ const char *zKey,
+ const char *zVal
+){
+ int nKey = STRLEN(zKey);
+ int iHash = idxHashString(zKey, nKey);
+ int nVal = (zVal ? STRLEN(zVal) : 0);
+ IdxHashEntry *pEntry;
+ assert( iHash>=0 );
+ for(pEntry=pHash->aHash[iHash]; pEntry; pEntry=pEntry->pHashNext){
+ if( STRLEN(pEntry->zKey)==nKey && 0==memcmp(pEntry->zKey, zKey, nKey) ){
+ return 1;
+ }
+ }
+ pEntry = idxMalloc(pRc, sizeof(IdxHashEntry) + nKey+1 + nVal+1);
+ if( pEntry ){
+ pEntry->zKey = (char*)&pEntry[1];
+ memcpy(pEntry->zKey, zKey, nKey);
+ if( zVal ){
+ pEntry->zVal = &pEntry->zKey[nKey+1];
+ memcpy(pEntry->zVal, zVal, nVal);
+ }
+ pEntry->pHashNext = pHash->aHash[iHash];
+ pHash->aHash[iHash] = pEntry;
+
+ pEntry->pNext = pHash->pFirst;
+ pHash->pFirst = pEntry;
+ }
+ return 0;
+}
+
+/*
+** If zKey/nKey is present in the hash table, return a pointer to the
+** hash-entry object.
+*/
+static IdxHashEntry *idxHashFind(IdxHash *pHash, const char *zKey, int nKey){
+ int iHash;
+ IdxHashEntry *pEntry;
+ if( nKey<0 ) nKey = STRLEN(zKey);
+ iHash = idxHashString(zKey, nKey);
+ assert( iHash>=0 );
+ for(pEntry=pHash->aHash[iHash]; pEntry; pEntry=pEntry->pHashNext){
+ if( STRLEN(pEntry->zKey)==nKey && 0==memcmp(pEntry->zKey, zKey, nKey) ){
+ return pEntry;
+ }
+ }
+ return 0;
+}
+
+/*
+** If the hash table contains an entry with a key equal to the string
+** passed as the final two arguments to this function, return a pointer
+** to the payload string. Otherwise, if zKey/nKey is not present in the
+** hash table, return NULL.
+*/
+static const char *idxHashSearch(IdxHash *pHash, const char *zKey, int nKey){
+ IdxHashEntry *pEntry = idxHashFind(pHash, zKey, nKey);
+ if( pEntry ) return pEntry->zVal;
+ return 0;
+}
+
+/*
+** Allocate and return a new IdxConstraint object. Set the IdxConstraint.zColl
+** variable to point to a copy of nul-terminated string zColl.
+*/
+static IdxConstraint *idxNewConstraint(int *pRc, const char *zColl){
+ IdxConstraint *pNew;
+ int nColl = STRLEN(zColl);
+
+ assert( *pRc==SQLITE_OK );
+ pNew = (IdxConstraint*)idxMalloc(pRc, sizeof(IdxConstraint) * nColl + 1);
+ if( pNew ){
+ pNew->zColl = (char*)&pNew[1];
+ memcpy(pNew->zColl, zColl, nColl+1);
+ }
+ return pNew;
+}
+
+/*
+** An error associated with database handle db has just occurred. Pass
+** the error message to callback function xOut.
+*/
+static void idxDatabaseError(
+ sqlite3 *db, /* Database handle */
+ char **pzErrmsg /* Write error here */
+){
+ *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+}
+
+/*
+** Prepare an SQL statement.
+*/
+static int idxPrepareStmt(
+ sqlite3 *db, /* Database handle to compile against */
+ sqlite3_stmt **ppStmt, /* OUT: Compiled SQL statement */
+ char **pzErrmsg, /* OUT: sqlite3_malloc()ed error message */
+ const char *zSql /* SQL statement to compile */
+){
+ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
+ if( rc!=SQLITE_OK ){
+ *ppStmt = 0;
+ idxDatabaseError(db, pzErrmsg);
+ }
+ return rc;
+}
+
+/*
+** Prepare an SQL statement using the results of a printf() formatting.
+*/
+static int idxPrintfPrepareStmt(
+ sqlite3 *db, /* Database handle to compile against */
+ sqlite3_stmt **ppStmt, /* OUT: Compiled SQL statement */
+ char **pzErrmsg, /* OUT: sqlite3_malloc()ed error message */
+ const char *zFmt, /* printf() format of SQL statement */
+ ... /* Trailing printf() arguments */
+){
+ va_list ap;
+ int rc;
+ char *zSql;
+ va_start(ap, zFmt);
+ zSql = sqlite3_vmprintf(zFmt, ap);
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = idxPrepareStmt(db, ppStmt, pzErrmsg, zSql);
+ sqlite3_free(zSql);
+ }
+ va_end(ap);
+ return rc;
+}
+
+
+/*************************************************************************
+** Beginning of virtual table implementation.
+*/
+typedef struct ExpertVtab ExpertVtab;
+struct ExpertVtab {
+ sqlite3_vtab base;
+ IdxTable *pTab;
+ sqlite3expert *pExpert;
+};
+
+typedef struct ExpertCsr ExpertCsr;
+struct ExpertCsr {
+ sqlite3_vtab_cursor base;
+ sqlite3_stmt *pData;
+};
+
+static char *expertDequote(const char *zIn){
+ int n = STRLEN(zIn);
+ char *zRet = sqlite3_malloc(n);
+
+ assert( zIn[0]=='\'' );
+ assert( zIn[n-1]=='\'' );
+
+ if( zRet ){
+ int iOut = 0;
+ int iIn = 0;
+ for(iIn=1; iIn<(n-1); iIn++){
+ if( zIn[iIn]=='\'' ){
+ assert( zIn[iIn+1]=='\'' );
+ iIn++;
+ }
+ zRet[iOut++] = zIn[iIn];
+ }
+ zRet[iOut] = '\0';
+ }
+
+ return zRet;
+}
+
+/*
+** This function is the implementation of both the xConnect and xCreate
+** methods of the r-tree virtual table.
+**
+** argv[0] -> module name
+** argv[1] -> database name
+** argv[2] -> table name
+** argv[...] -> column names...
+*/
+static int expertConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ sqlite3expert *pExpert = (sqlite3expert*)pAux;
+ ExpertVtab *p = 0;
+ int rc;
+
+ if( argc!=4 ){
+ *pzErr = sqlite3_mprintf("internal error!");
+ rc = SQLITE_ERROR;
+ }else{
+ char *zCreateTable = expertDequote(argv[3]);
+ if( zCreateTable ){
+ rc = sqlite3_declare_vtab(db, zCreateTable);
+ if( rc==SQLITE_OK ){
+ p = idxMalloc(&rc, sizeof(ExpertVtab));
+ }
+ if( rc==SQLITE_OK ){
+ p->pExpert = pExpert;
+ p->pTab = pExpert->pTable;
+ assert( sqlite3_stricmp(p->pTab->zName, argv[2])==0 );
+ }
+ sqlite3_free(zCreateTable);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+
+ *ppVtab = (sqlite3_vtab*)p;
+ return rc;
+}
+
+static int expertDisconnect(sqlite3_vtab *pVtab){
+ ExpertVtab *p = (ExpertVtab*)pVtab;
+ sqlite3_free(p);
+ return SQLITE_OK;
+}
+
+static int expertBestIndex(sqlite3_vtab *pVtab, sqlite3_index_info *pIdxInfo){
+ ExpertVtab *p = (ExpertVtab*)pVtab;
+ int rc = SQLITE_OK;
+ int n = 0;
+ IdxScan *pScan;
+ const int opmask =
+ SQLITE_INDEX_CONSTRAINT_EQ | SQLITE_INDEX_CONSTRAINT_GT |
+ SQLITE_INDEX_CONSTRAINT_LT | SQLITE_INDEX_CONSTRAINT_GE |
+ SQLITE_INDEX_CONSTRAINT_LE;
+
+ pScan = idxMalloc(&rc, sizeof(IdxScan));
+ if( pScan ){
+ int i;
+
+ /* Link the new scan object into the list */
+ pScan->pTab = p->pTab;
+ pScan->pNextScan = p->pExpert->pScan;
+ p->pExpert->pScan = pScan;
+
+ /* Add the constraints to the IdxScan object */
+ for(i=0; i<pIdxInfo->nConstraint; i++){
+ struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i];
+ if( pCons->usable
+ && pCons->iColumn>=0
+ && p->pTab->aCol[pCons->iColumn].iPk==0
+ && (pCons->op & opmask)
+ ){
+ IdxConstraint *pNew;
+ const char *zColl = sqlite3_vtab_collation(pIdxInfo, i);
+ pNew = idxNewConstraint(&rc, zColl);
+ if( pNew ){
+ pNew->iCol = pCons->iColumn;
+ if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+ pNew->pNext = pScan->pEq;
+ pScan->pEq = pNew;
+ }else{
+ pNew->bRange = 1;
+ pNew->pNext = pScan->pRange;
+ pScan->pRange = pNew;
+ }
+ }
+ n++;
+ pIdxInfo->aConstraintUsage[i].argvIndex = n;
+ }
+ }
+
+ /* Add the ORDER BY to the IdxScan object */
+ for(i=pIdxInfo->nOrderBy-1; i>=0; i--){
+ int iCol = pIdxInfo->aOrderBy[i].iColumn;
+ if( iCol>=0 ){
+ IdxConstraint *pNew = idxNewConstraint(&rc, p->pTab->aCol[iCol].zColl);
+ if( pNew ){
+ pNew->iCol = iCol;
+ pNew->bDesc = pIdxInfo->aOrderBy[i].desc;
+ pNew->pNext = pScan->pOrder;
+ pNew->pLink = pScan->pOrder;
+ pScan->pOrder = pNew;
+ n++;
+ }
+ }
+ }
+ }
+
+ pIdxInfo->estimatedCost = 1000000.0 / (n+1);
+ return rc;
+}
+
+static int expertUpdate(
+ sqlite3_vtab *pVtab,
+ int nData,
+ sqlite3_value **azData,
+ sqlite_int64 *pRowid
+){
+ (void)pVtab;
+ (void)nData;
+ (void)azData;
+ (void)pRowid;
+ return SQLITE_OK;
+}
+
+/*
+** Virtual table module xOpen method.
+*/
+static int expertOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+ int rc = SQLITE_OK;
+ ExpertCsr *pCsr;
+ (void)pVTab;
+ pCsr = idxMalloc(&rc, sizeof(ExpertCsr));
+ *ppCursor = (sqlite3_vtab_cursor*)pCsr;
+ return rc;
+}
+
+/*
+** Virtual table module xClose method.
+*/
+static int expertClose(sqlite3_vtab_cursor *cur){
+ ExpertCsr *pCsr = (ExpertCsr*)cur;
+ sqlite3_finalize(pCsr->pData);
+ sqlite3_free(pCsr);
+ return SQLITE_OK;
+}
+
+/*
+** Virtual table module xEof method.
+**
+** Return non-zero if the cursor does not currently point to a valid
+** record (i.e if the scan has finished), or zero otherwise.
+*/
+static int expertEof(sqlite3_vtab_cursor *cur){
+ ExpertCsr *pCsr = (ExpertCsr*)cur;
+ return pCsr->pData==0;
+}
+
+/*
+** Virtual table module xNext method.
+*/
+static int expertNext(sqlite3_vtab_cursor *cur){
+ ExpertCsr *pCsr = (ExpertCsr*)cur;
+ int rc = SQLITE_OK;
+
+ assert( pCsr->pData );
+ rc = sqlite3_step(pCsr->pData);
+ if( rc!=SQLITE_ROW ){
+ rc = sqlite3_finalize(pCsr->pData);
+ pCsr->pData = 0;
+ }else{
+ rc = SQLITE_OK;
+ }
+
+ return rc;
+}
+
+/*
+** Virtual table module xRowid method.
+*/
+static int expertRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+ (void)cur;
+ *pRowid = 0;
+ return SQLITE_OK;
+}
+
+/*
+** Virtual table module xColumn method.
+*/
+static int expertColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
+ ExpertCsr *pCsr = (ExpertCsr*)cur;
+ sqlite3_value *pVal;
+ pVal = sqlite3_column_value(pCsr->pData, i);
+ if( pVal ){
+ sqlite3_result_value(ctx, pVal);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Virtual table module xFilter method.
+*/
+static int expertFilter(
+ sqlite3_vtab_cursor *cur,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ ExpertCsr *pCsr = (ExpertCsr*)cur;
+ ExpertVtab *pVtab = (ExpertVtab*)(cur->pVtab);
+ sqlite3expert *pExpert = pVtab->pExpert;
+ int rc;
+
+ (void)idxNum;
+ (void)idxStr;
+ (void)argc;
+ (void)argv;
+ rc = sqlite3_finalize(pCsr->pData);
+ pCsr->pData = 0;
+ if( rc==SQLITE_OK ){
+ rc = idxPrintfPrepareStmt(pExpert->db, &pCsr->pData, &pVtab->base.zErrMsg,
+ "SELECT * FROM main.%Q WHERE sample()", pVtab->pTab->zName
+ );
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = expertNext(cur);
+ }
+ return rc;
+}
+
+static int idxRegisterVtab(sqlite3expert *p){
+ static sqlite3_module expertModule = {
+ 2, /* iVersion */
+ expertConnect, /* xCreate - create a table */
+ expertConnect, /* xConnect - connect to an existing table */
+ expertBestIndex, /* xBestIndex - Determine search strategy */
+ expertDisconnect, /* xDisconnect - Disconnect from a table */
+ expertDisconnect, /* xDestroy - Drop a table */
+ expertOpen, /* xOpen - open a cursor */
+ expertClose, /* xClose - close a cursor */
+ expertFilter, /* xFilter - configure scan constraints */
+ expertNext, /* xNext - advance a cursor */
+ expertEof, /* xEof */
+ expertColumn, /* xColumn - read data */
+ expertRowid, /* xRowid - read data */
+ expertUpdate, /* xUpdate - write data */
+ 0, /* xBegin - begin transaction */
+ 0, /* xSync - sync transaction */
+ 0, /* xCommit - commit transaction */
+ 0, /* xRollback - rollback transaction */
+ 0, /* xFindFunction - function overloading */
+ 0, /* xRename - rename the table */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ };
+
+ return sqlite3_create_module(p->dbv, "expert", &expertModule, (void*)p);
+}
+/*
+** End of virtual table implementation.
+*************************************************************************/
+/*
+** Finalize SQL statement pStmt. If (*pRc) is SQLITE_OK when this function
+** is called, set it to the return value of sqlite3_finalize() before
+** returning. Otherwise, discard the sqlite3_finalize() return value.
+*/
+static void idxFinalize(int *pRc, sqlite3_stmt *pStmt){
+ int rc = sqlite3_finalize(pStmt);
+ if( *pRc==SQLITE_OK ) *pRc = rc;
+}
+
+/*
+** Attempt to allocate an IdxTable structure corresponding to table zTab
+** in the main database of connection db. If successful, set (*ppOut) to
+** point to the new object and return SQLITE_OK. Otherwise, return an
+** SQLite error code and set (*ppOut) to NULL. In this case *pzErrmsg may be
+** set to point to an error string.
+**
+** It is the responsibility of the caller to eventually free either the
+** IdxTable object or error message using sqlite3_free().
+*/
+static int idxGetTableInfo(
+ sqlite3 *db, /* Database connection to read details from */
+ const char *zTab, /* Table name */
+ IdxTable **ppOut, /* OUT: New object (if successful) */
+ char **pzErrmsg /* OUT: Error message (if not) */
+){
+ sqlite3_stmt *p1 = 0;
+ int nCol = 0;
+ int nTab = STRLEN(zTab);
+ int nByte = sizeof(IdxTable) + nTab + 1;
+ IdxTable *pNew = 0;
+ int rc, rc2;
+ char *pCsr = 0;
+
+ rc = idxPrintfPrepareStmt(db, &p1, pzErrmsg, "PRAGMA table_info=%Q", zTab);
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(p1) ){
+ const char *zCol = (const char*)sqlite3_column_text(p1, 1);
+ nByte += 1 + STRLEN(zCol);
+ rc = sqlite3_table_column_metadata(
+ db, "main", zTab, zCol, 0, &zCol, 0, 0, 0
+ );
+ nByte += 1 + STRLEN(zCol);
+ nCol++;
+ }
+ rc2 = sqlite3_reset(p1);
+ if( rc==SQLITE_OK ) rc = rc2;
+
+ nByte += sizeof(IdxColumn) * nCol;
+ if( rc==SQLITE_OK ){
+ pNew = idxMalloc(&rc, nByte);
+ }
+ if( rc==SQLITE_OK ){
+ pNew->aCol = (IdxColumn*)&pNew[1];
+ pNew->nCol = nCol;
+ pCsr = (char*)&pNew->aCol[nCol];
+ }
+
+ nCol = 0;
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(p1) ){
+ const char *zCol = (const char*)sqlite3_column_text(p1, 1);
+ int nCopy = STRLEN(zCol) + 1;
+ pNew->aCol[nCol].zName = pCsr;
+ pNew->aCol[nCol].iPk = sqlite3_column_int(p1, 5);
+ memcpy(pCsr, zCol, nCopy);
+ pCsr += nCopy;
+
+ rc = sqlite3_table_column_metadata(
+ db, "main", zTab, zCol, 0, &zCol, 0, 0, 0
+ );
+ if( rc==SQLITE_OK ){
+ nCopy = STRLEN(zCol) + 1;
+ pNew->aCol[nCol].zColl = pCsr;
+ memcpy(pCsr, zCol, nCopy);
+ pCsr += nCopy;
+ }
+
+ nCol++;
+ }
+ idxFinalize(&rc, p1);
+
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(pNew);
+ pNew = 0;
+ }else{
+ pNew->zName = pCsr;
+ memcpy(pNew->zName, zTab, nTab+1);
+ }
+
+ *ppOut = pNew;
+ return rc;
+}
+
+/*
+** This function is a no-op if *pRc is set to anything other than
+** SQLITE_OK when it is called.
+**
+** If *pRc is initially set to SQLITE_OK, then the text specified by
+** the printf() style arguments is appended to zIn and the result returned
+** in a buffer allocated by sqlite3_malloc(). sqlite3_free() is called on
+** zIn before returning.
+*/
+static char *idxAppendText(int *pRc, char *zIn, const char *zFmt, ...){
+ va_list ap;
+ char *zAppend = 0;
+ char *zRet = 0;
+ int nIn = zIn ? STRLEN(zIn) : 0;
+ int nAppend = 0;
+ va_start(ap, zFmt);
+ if( *pRc==SQLITE_OK ){
+ zAppend = sqlite3_vmprintf(zFmt, ap);
+ if( zAppend ){
+ nAppend = STRLEN(zAppend);
+ zRet = (char*)sqlite3_malloc(nIn + nAppend + 1);
+ }
+ if( zAppend && zRet ){
+ if( nIn ) memcpy(zRet, zIn, nIn);
+ memcpy(&zRet[nIn], zAppend, nAppend+1);
+ }else{
+ sqlite3_free(zRet);
+ zRet = 0;
+ *pRc = SQLITE_NOMEM;
+ }
+ sqlite3_free(zAppend);
+ sqlite3_free(zIn);
+ }
+ va_end(ap);
+ return zRet;
+}
+
+/*
+** Return true if zId must be quoted in order to use it as an SQL
+** identifier, or false otherwise.
+*/
+static int idxIdentifierRequiresQuotes(const char *zId){
+ int i;
+ for(i=0; zId[i]; i++){
+ if( !(zId[i]=='_')
+ && !(zId[i]>='0' && zId[i]<='9')
+ && !(zId[i]>='a' && zId[i]<='z')
+ && !(zId[i]>='A' && zId[i]<='Z')
+ ){
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+** This function appends an index column definition suitable for constraint
+** pCons to the string passed as zIn and returns the result.
+*/
+static char *idxAppendColDefn(
+ int *pRc, /* IN/OUT: Error code */
+ char *zIn, /* Column defn accumulated so far */
+ IdxTable *pTab, /* Table index will be created on */
+ IdxConstraint *pCons
+){
+ char *zRet = zIn;
+ IdxColumn *p = &pTab->aCol[pCons->iCol];
+ if( zRet ) zRet = idxAppendText(pRc, zRet, ", ");
+
+ if( idxIdentifierRequiresQuotes(p->zName) ){
+ zRet = idxAppendText(pRc, zRet, "%Q", p->zName);
+ }else{
+ zRet = idxAppendText(pRc, zRet, "%s", p->zName);
+ }
+
+ if( sqlite3_stricmp(p->zColl, pCons->zColl) ){
+ if( idxIdentifierRequiresQuotes(pCons->zColl) ){
+ zRet = idxAppendText(pRc, zRet, " COLLATE %Q", pCons->zColl);
+ }else{
+ zRet = idxAppendText(pRc, zRet, " COLLATE %s", pCons->zColl);
+ }
+ }
+
+ if( pCons->bDesc ){
+ zRet = idxAppendText(pRc, zRet, " DESC");
+ }
+ return zRet;
+}
+
+/*
+** Search database dbm for an index compatible with the one idxCreateFromCons()
+** would create from arguments pScan, pEq and pTail. If no error occurs and
+** such an index is found, return non-zero. Or, if no such index is found,
+** return zero.
+**
+** If an error occurs, set *pRc to an SQLite error code and return zero.
+*/
+static int idxFindCompatible(
+ int *pRc, /* OUT: Error code */
+ sqlite3* dbm, /* Database to search */
+ IdxScan *pScan, /* Scan for table to search for index on */
+ IdxConstraint *pEq, /* List of == constraints */
+ IdxConstraint *pTail /* List of range constraints */
+){
+ const char *zTbl = pScan->pTab->zName;
+ sqlite3_stmt *pIdxList = 0;
+ IdxConstraint *pIter;
+ int nEq = 0; /* Number of elements in pEq */
+ int rc;
+
+ /* Count the elements in list pEq */
+ for(pIter=pEq; pIter; pIter=pIter->pLink) nEq++;
+
+ rc = idxPrintfPrepareStmt(dbm, &pIdxList, 0, "PRAGMA index_list=%Q", zTbl);
+ while( rc==SQLITE_OK && sqlite3_step(pIdxList)==SQLITE_ROW ){
+ int bMatch = 1;
+ IdxConstraint *pT = pTail;
+ sqlite3_stmt *pInfo = 0;
+ const char *zIdx = (const char*)sqlite3_column_text(pIdxList, 1);
+
+ /* Zero the IdxConstraint.bFlag values in the pEq list */
+ for(pIter=pEq; pIter; pIter=pIter->pLink) pIter->bFlag = 0;
+
+ rc = idxPrintfPrepareStmt(dbm, &pInfo, 0, "PRAGMA index_xInfo=%Q", zIdx);
+ while( rc==SQLITE_OK && sqlite3_step(pInfo)==SQLITE_ROW ){
+ int iIdx = sqlite3_column_int(pInfo, 0);
+ int iCol = sqlite3_column_int(pInfo, 1);
+ const char *zColl = (const char*)sqlite3_column_text(pInfo, 4);
+
+ if( iIdx<nEq ){
+ for(pIter=pEq; pIter; pIter=pIter->pLink){
+ if( pIter->bFlag ) continue;
+ if( pIter->iCol!=iCol ) continue;
+ if( sqlite3_stricmp(pIter->zColl, zColl) ) continue;
+ pIter->bFlag = 1;
+ break;
+ }
+ if( pIter==0 ){
+ bMatch = 0;
+ break;
+ }
+ }else{
+ if( pT ){
+ if( pT->iCol!=iCol || sqlite3_stricmp(pT->zColl, zColl) ){
+ bMatch = 0;
+ break;
+ }
+ pT = pT->pLink;
+ }
+ }
+ }
+ idxFinalize(&rc, pInfo);
+
+ if( rc==SQLITE_OK && bMatch ){
+ sqlite3_finalize(pIdxList);
+ return 1;
+ }
+ }
+ idxFinalize(&rc, pIdxList);
+
+ *pRc = rc;
+ return 0;
+}
+
+static int idxCreateFromCons(
+ sqlite3expert *p,
+ IdxScan *pScan,
+ IdxConstraint *pEq,
+ IdxConstraint *pTail
+){
+ sqlite3 *dbm = p->dbm;
+ int rc = SQLITE_OK;
+ if( (pEq || pTail) && 0==idxFindCompatible(&rc, dbm, pScan, pEq, pTail) ){
+ IdxTable *pTab = pScan->pTab;
+ char *zCols = 0;
+ char *zIdx = 0;
+ IdxConstraint *pCons;
+ unsigned int h = 0;
+ const char *zFmt;
+
+ for(pCons=pEq; pCons; pCons=pCons->pLink){
+ zCols = idxAppendColDefn(&rc, zCols, pTab, pCons);
+ }
+ for(pCons=pTail; pCons; pCons=pCons->pLink){
+ zCols = idxAppendColDefn(&rc, zCols, pTab, pCons);
+ }
+
+ if( rc==SQLITE_OK ){
+ /* Hash the list of columns to come up with a name for the index */
+ const char *zTable = pScan->pTab->zName;
+ char *zName; /* Index name */
+ int i;
+ for(i=0; zCols[i]; i++){
+ h += ((h<<3) + zCols[i]);
+ }
+ zName = sqlite3_mprintf("%s_idx_%08x", zTable, h);
+ if( zName==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ if( idxIdentifierRequiresQuotes(zTable) ){
+ zFmt = "CREATE INDEX '%q' ON %Q(%s)";
+ }else{
+ zFmt = "CREATE INDEX %s ON %s(%s)";
+ }
+ zIdx = sqlite3_mprintf(zFmt, zName, zTable, zCols);
+ if( !zIdx ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_exec(dbm, zIdx, 0, 0, p->pzErrmsg);
+ idxHashAdd(&rc, &p->hIdx, zName, zIdx);
+ }
+ sqlite3_free(zName);
+ sqlite3_free(zIdx);
+ }
+ }
+
+ sqlite3_free(zCols);
+ }
+ return rc;
+}
+
+/*
+** Return true if list pList (linked by IdxConstraint.pLink) contains
+** a constraint compatible with *p. Otherwise return false.
+*/
+static int idxFindConstraint(IdxConstraint *pList, IdxConstraint *p){
+ IdxConstraint *pCmp;
+ for(pCmp=pList; pCmp; pCmp=pCmp->pLink){
+ if( p->iCol==pCmp->iCol ) return 1;
+ }
+ return 0;
+}
+
+static int idxCreateFromWhere(
+ sqlite3expert *p,
+ IdxScan *pScan, /* Create indexes for this scan */
+ IdxConstraint *pTail /* range/ORDER BY constraints for inclusion */
+){
+ IdxConstraint *p1 = 0;
+ IdxConstraint *pCon;
+ int rc;
+
+ /* Gather up all the == constraints. */
+ for(pCon=pScan->pEq; pCon; pCon=pCon->pNext){
+ if( !idxFindConstraint(p1, pCon) && !idxFindConstraint(pTail, pCon) ){
+ pCon->pLink = p1;
+ p1 = pCon;
+ }
+ }
+
+ /* Create an index using the == constraints collected above. And the
+ ** range constraint/ORDER BY terms passed in by the caller, if any. */
+ rc = idxCreateFromCons(p, pScan, p1, pTail);
+
+ /* If no range/ORDER BY passed by the caller, create a version of the
+ ** index for each range constraint. */
+ if( pTail==0 ){
+ for(pCon=pScan->pRange; rc==SQLITE_OK && pCon; pCon=pCon->pNext){
+ assert( pCon->pLink==0 );
+ if( !idxFindConstraint(p1, pCon) && !idxFindConstraint(pTail, pCon) ){
+ rc = idxCreateFromCons(p, pScan, p1, pCon);
+ }
+ }
+ }
+
+ return rc;
+}
+
+/*
+** Create candidate indexes in database [dbm] based on the data in
+** linked-list pScan.
+*/
+static int idxCreateCandidates(sqlite3expert *p){
+ int rc = SQLITE_OK;
+ IdxScan *pIter;
+
+ for(pIter=p->pScan; pIter && rc==SQLITE_OK; pIter=pIter->pNextScan){
+ rc = idxCreateFromWhere(p, pIter, 0);
+ if( rc==SQLITE_OK && pIter->pOrder ){
+ rc = idxCreateFromWhere(p, pIter, pIter->pOrder);
+ }
+ }
+
+ return rc;
+}
+
+/*
+** Free all elements of the linked list starting at pConstraint.
+*/
+static void idxConstraintFree(IdxConstraint *pConstraint){
+ IdxConstraint *pNext;
+ IdxConstraint *p;
+
+ for(p=pConstraint; p; p=pNext){
+ pNext = p->pNext;
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Free all elements of the linked list starting from pScan up until pLast
+** (pLast is not freed).
+*/
+static void idxScanFree(IdxScan *pScan, IdxScan *pLast){
+ IdxScan *p;
+ IdxScan *pNext;
+ for(p=pScan; p!=pLast; p=pNext){
+ pNext = p->pNextScan;
+ idxConstraintFree(p->pOrder);
+ idxConstraintFree(p->pEq);
+ idxConstraintFree(p->pRange);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Free all elements of the linked list starting from pStatement up
+** until pLast (pLast is not freed).
+*/
+static void idxStatementFree(IdxStatement *pStatement, IdxStatement *pLast){
+ IdxStatement *p;
+ IdxStatement *pNext;
+ for(p=pStatement; p!=pLast; p=pNext){
+ pNext = p->pNext;
+ sqlite3_free(p->zEQP);
+ sqlite3_free(p->zIdx);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Free the linked list of IdxTable objects starting at pTab.
+*/
+static void idxTableFree(IdxTable *pTab){
+ IdxTable *pIter;
+ IdxTable *pNext;
+ for(pIter=pTab; pIter; pIter=pNext){
+ pNext = pIter->pNext;
+ sqlite3_free(pIter);
+ }
+}
+
+/*
+** Free the linked list of IdxWrite objects starting at pTab.
+*/
+static void idxWriteFree(IdxWrite *pTab){
+ IdxWrite *pIter;
+ IdxWrite *pNext;
+ for(pIter=pTab; pIter; pIter=pNext){
+ pNext = pIter->pNext;
+ sqlite3_free(pIter);
+ }
+}
+
+
+
+/*
+** This function is called after candidate indexes have been created. It
+** runs all the queries to see which indexes they prefer, and populates
+** IdxStatement.zIdx and IdxStatement.zEQP with the results.
+*/
+int idxFindIndexes(
+ sqlite3expert *p,
+ char **pzErr /* OUT: Error message (sqlite3_malloc) */
+){
+ IdxStatement *pStmt;
+ sqlite3 *dbm = p->dbm;
+ int rc = SQLITE_OK;
+
+ IdxHash hIdx;
+ idxHashInit(&hIdx);
+
+ for(pStmt=p->pStatement; rc==SQLITE_OK && pStmt; pStmt=pStmt->pNext){
+ IdxHashEntry *pEntry;
+ sqlite3_stmt *pExplain = 0;
+ idxHashClear(&hIdx);
+ rc = idxPrintfPrepareStmt(dbm, &pExplain, pzErr,
+ "EXPLAIN QUERY PLAN %s", pStmt->zSql
+ );
+ while( rc==SQLITE_OK && sqlite3_step(pExplain)==SQLITE_ROW ){
+ /* int iId = sqlite3_column_int(pExplain, 0); */
+ /* int iParent = sqlite3_column_int(pExplain, 1); */
+ /* int iNotUsed = sqlite3_column_int(pExplain, 2); */
+ const char *zDetail = (const char*)sqlite3_column_text(pExplain, 3);
+ int nDetail = STRLEN(zDetail);
+ int i;
+
+ for(i=0; i<nDetail; i++){
+ const char *zIdx = 0;
+ if( memcmp(&zDetail[i], " USING INDEX ", 13)==0 ){
+ zIdx = &zDetail[i+13];
+ }else if( memcmp(&zDetail[i], " USING COVERING INDEX ", 22)==0 ){
+ zIdx = &zDetail[i+22];
+ }
+ if( zIdx ){
+ const char *zSql;
+ int nIdx = 0;
+ while( zIdx[nIdx]!='\0' && (zIdx[nIdx]!=' ' || zIdx[nIdx+1]!='(') ){
+ nIdx++;
+ }
+ zSql = idxHashSearch(&p->hIdx, zIdx, nIdx);
+ if( zSql ){
+ idxHashAdd(&rc, &hIdx, zSql, 0);
+ if( rc ) goto find_indexes_out;
+ }
+ break;
+ }
+ }
+
+ if( zDetail[0]!='-' ){
+ pStmt->zEQP = idxAppendText(&rc, pStmt->zEQP, "%s\n", zDetail);
+ }
+ }
+
+ for(pEntry=hIdx.pFirst; pEntry; pEntry=pEntry->pNext){
+ pStmt->zIdx = idxAppendText(&rc, pStmt->zIdx, "%s;\n", pEntry->zKey);
+ }
+
+ idxFinalize(&rc, pExplain);
+ }
+
+ find_indexes_out:
+ idxHashClear(&hIdx);
+ return rc;
+}
+
+static int idxAuthCallback(
+ void *pCtx,
+ int eOp,
+ const char *z3,
+ const char *z4,
+ const char *zDb,
+ const char *zTrigger
+){
+ int rc = SQLITE_OK;
+ (void)z4;
+ (void)zTrigger;
+ if( eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE || eOp==SQLITE_DELETE ){
+ if( sqlite3_stricmp(zDb, "main")==0 ){
+ sqlite3expert *p = (sqlite3expert*)pCtx;
+ IdxTable *pTab;
+ for(pTab=p->pTable; pTab; pTab=pTab->pNext){
+ if( 0==sqlite3_stricmp(z3, pTab->zName) ) break;
+ }
+ if( pTab ){
+ IdxWrite *pWrite;
+ for(pWrite=p->pWrite; pWrite; pWrite=pWrite->pNext){
+ if( pWrite->pTab==pTab && pWrite->eOp==eOp ) break;
+ }
+ if( pWrite==0 ){
+ pWrite = idxMalloc(&rc, sizeof(IdxWrite));
+ if( rc==SQLITE_OK ){
+ pWrite->pTab = pTab;
+ pWrite->eOp = eOp;
+ pWrite->pNext = p->pWrite;
+ p->pWrite = pWrite;
+ }
+ }
+ }
+ }
+ }
+ return rc;
+}
+
+static int idxProcessOneTrigger(
+ sqlite3expert *p,
+ IdxWrite *pWrite,
+ char **pzErr
+){
+ static const char *zInt = UNIQUE_TABLE_NAME;
+ static const char *zDrop = "DROP TABLE " UNIQUE_TABLE_NAME;
+ IdxTable *pTab = pWrite->pTab;
+ const char *zTab = pTab->zName;
+ const char *zSql =
+ "SELECT 'CREATE TEMP' || substr(sql, 7) FROM sqlite_master "
+ "WHERE tbl_name = %Q AND type IN ('table', 'trigger') "
+ "ORDER BY type;";
+ sqlite3_stmt *pSelect = 0;
+ int rc = SQLITE_OK;
+ char *zWrite = 0;
+
+ /* Create the table and its triggers in the temp schema */
+ rc = idxPrintfPrepareStmt(p->db, &pSelect, pzErr, zSql, zTab, zTab);
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSelect) ){
+ const char *zCreate = (const char*)sqlite3_column_text(pSelect, 0);
+ rc = sqlite3_exec(p->dbv, zCreate, 0, 0, pzErr);
+ }
+ idxFinalize(&rc, pSelect);
+
+ /* Rename the table in the temp schema to zInt */
+ if( rc==SQLITE_OK ){
+ char *z = sqlite3_mprintf("ALTER TABLE temp.%Q RENAME TO %Q", zTab, zInt);
+ if( z==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_exec(p->dbv, z, 0, 0, pzErr);
+ sqlite3_free(z);
+ }
+ }
+
+ switch( pWrite->eOp ){
+ case SQLITE_INSERT: {
+ int i;
+ zWrite = idxAppendText(&rc, zWrite, "INSERT INTO %Q VALUES(", zInt);
+ for(i=0; i<pTab->nCol; i++){
+ zWrite = idxAppendText(&rc, zWrite, "%s?", i==0 ? "" : ", ");
+ }
+ zWrite = idxAppendText(&rc, zWrite, ")");
+ break;
+ }
+ case SQLITE_UPDATE: {
+ int i;
+ zWrite = idxAppendText(&rc, zWrite, "UPDATE %Q SET ", zInt);
+ for(i=0; i<pTab->nCol; i++){
+ zWrite = idxAppendText(&rc, zWrite, "%s%Q=?", i==0 ? "" : ", ",
+ pTab->aCol[i].zName
+ );
+ }
+ break;
+ }
+ default: {
+ assert( pWrite->eOp==SQLITE_DELETE );
+ if( rc==SQLITE_OK ){
+ zWrite = sqlite3_mprintf("DELETE FROM %Q", zInt);
+ if( zWrite==0 ) rc = SQLITE_NOMEM;
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ sqlite3_stmt *pX = 0;
+ rc = sqlite3_prepare_v2(p->dbv, zWrite, -1, &pX, 0);
+ idxFinalize(&rc, pX);
+ if( rc!=SQLITE_OK ){
+ idxDatabaseError(p->dbv, pzErr);
+ }
+ }
+ sqlite3_free(zWrite);
+
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_exec(p->dbv, zDrop, 0, 0, pzErr);
+ }
+
+ return rc;
+}
+
+static int idxProcessTriggers(sqlite3expert *p, char **pzErr){
+ int rc = SQLITE_OK;
+ IdxWrite *pEnd = 0;
+ IdxWrite *pFirst = p->pWrite;
+
+ while( rc==SQLITE_OK && pFirst!=pEnd ){
+ IdxWrite *pIter;
+ for(pIter=pFirst; rc==SQLITE_OK && pIter!=pEnd; pIter=pIter->pNext){
+ rc = idxProcessOneTrigger(p, pIter, pzErr);
+ }
+ pEnd = pFirst;
+ pFirst = p->pWrite;
+ }
+
+ return rc;
+}
+
+
+static int idxCreateVtabSchema(sqlite3expert *p, char **pzErrmsg){
+ int rc = idxRegisterVtab(p);
+ sqlite3_stmt *pSchema = 0;
+
+ /* For each table in the main db schema:
+ **
+ ** 1) Add an entry to the p->pTable list, and
+ ** 2) Create the equivalent virtual table in dbv.
+ */
+ rc = idxPrepareStmt(p->db, &pSchema, pzErrmsg,
+ "SELECT type, name, sql, 1 FROM sqlite_master "
+ "WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%%' "
+ " UNION ALL "
+ "SELECT type, name, sql, 2 FROM sqlite_master "
+ "WHERE type = 'trigger'"
+ " AND tbl_name IN(SELECT name FROM sqlite_master WHERE type = 'view') "
+ "ORDER BY 4, 1"
+ );
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSchema) ){
+ const char *zType = (const char*)sqlite3_column_text(pSchema, 0);
+ const char *zName = (const char*)sqlite3_column_text(pSchema, 1);
+ const char *zSql = (const char*)sqlite3_column_text(pSchema, 2);
+
+ if( zType[0]=='v' || zType[1]=='r' ){
+ rc = sqlite3_exec(p->dbv, zSql, 0, 0, pzErrmsg);
+ }else{
+ IdxTable *pTab;
+ rc = idxGetTableInfo(p->db, zName, &pTab, pzErrmsg);
+ if( rc==SQLITE_OK ){
+ int i;
+ char *zInner = 0;
+ char *zOuter = 0;
+ pTab->pNext = p->pTable;
+ p->pTable = pTab;
+
+ /* The statement the vtab will pass to sqlite3_declare_vtab() */
+ zInner = idxAppendText(&rc, 0, "CREATE TABLE x(");
+ for(i=0; i<pTab->nCol; i++){
+ zInner = idxAppendText(&rc, zInner, "%s%Q COLLATE %s",
+ (i==0 ? "" : ", "), pTab->aCol[i].zName, pTab->aCol[i].zColl
+ );
+ }
+ zInner = idxAppendText(&rc, zInner, ")");
+
+ /* The CVT statement to create the vtab */
+ zOuter = idxAppendText(&rc, 0,
+ "CREATE VIRTUAL TABLE %Q USING expert(%Q)", zName, zInner
+ );
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_exec(p->dbv, zOuter, 0, 0, pzErrmsg);
+ }
+ sqlite3_free(zInner);
+ sqlite3_free(zOuter);
+ }
+ }
+ }
+ idxFinalize(&rc, pSchema);
+ return rc;
+}
+
+struct IdxSampleCtx {
+ int iTarget;
+ double target; /* Target nRet/nRow value */
+ double nRow; /* Number of rows seen */
+ double nRet; /* Number of rows returned */
+};
+
+static void idxSampleFunc(
+ sqlite3_context *pCtx,
+ int argc,
+ sqlite3_value **argv
+){
+ struct IdxSampleCtx *p = (struct IdxSampleCtx*)sqlite3_user_data(pCtx);
+ int bRet;
+
+ (void)argv;
+ assert( argc==0 );
+ if( p->nRow==0.0 ){
+ bRet = 1;
+ }else{
+ bRet = (p->nRet / p->nRow) <= p->target;
+ if( bRet==0 ){
+ unsigned short rnd;
+ sqlite3_randomness(2, (void*)&rnd);
+ bRet = ((int)rnd % 100) <= p->iTarget;
+ }
+ }
+
+ sqlite3_result_int(pCtx, bRet);
+ p->nRow += 1.0;
+ p->nRet += (double)bRet;
+}
+
+struct IdxRemCtx {
+ int nSlot;
+ struct IdxRemSlot {
+ int eType; /* SQLITE_NULL, INTEGER, REAL, TEXT, BLOB */
+ i64 iVal; /* SQLITE_INTEGER value */
+ double rVal; /* SQLITE_FLOAT value */
+ int nByte; /* Bytes of space allocated at z */
+ int n; /* Size of buffer z */
+ char *z; /* SQLITE_TEXT/BLOB value */
+ } aSlot[1];
+};
+
+/*
+** Implementation of scalar function rem().
+*/
+static void idxRemFunc(
+ sqlite3_context *pCtx,
+ int argc,
+ sqlite3_value **argv
+){
+ struct IdxRemCtx *p = (struct IdxRemCtx*)sqlite3_user_data(pCtx);
+ struct IdxRemSlot *pSlot;
+ int iSlot;
+ assert( argc==2 );
+
+ iSlot = sqlite3_value_int(argv[0]);
+ assert( iSlot<=p->nSlot );
+ pSlot = &p->aSlot[iSlot];
+
+ switch( pSlot->eType ){
+ case SQLITE_NULL:
+ /* no-op */
+ break;
+
+ case SQLITE_INTEGER:
+ sqlite3_result_int64(pCtx, pSlot->iVal);
+ break;
+
+ case SQLITE_FLOAT:
+ sqlite3_result_double(pCtx, pSlot->rVal);
+ break;
+
+ case SQLITE_BLOB:
+ sqlite3_result_blob(pCtx, pSlot->z, pSlot->n, SQLITE_TRANSIENT);
+ break;
+
+ case SQLITE_TEXT:
+ sqlite3_result_text(pCtx, pSlot->z, pSlot->n, SQLITE_TRANSIENT);
+ break;
+ }
+
+ pSlot->eType = sqlite3_value_type(argv[1]);
+ switch( pSlot->eType ){
+ case SQLITE_NULL:
+ /* no-op */
+ break;
+
+ case SQLITE_INTEGER:
+ pSlot->iVal = sqlite3_value_int64(argv[1]);
+ break;
+
+ case SQLITE_FLOAT:
+ pSlot->rVal = sqlite3_value_double(argv[1]);
+ break;
+
+ case SQLITE_BLOB:
+ case SQLITE_TEXT: {
+ int nByte = sqlite3_value_bytes(argv[1]);
+ if( nByte>pSlot->nByte ){
+ char *zNew = (char*)sqlite3_realloc(pSlot->z, nByte*2);
+ if( zNew==0 ){
+ sqlite3_result_error_nomem(pCtx);
+ return;
+ }
+ pSlot->nByte = nByte*2;
+ pSlot->z = zNew;
+ }
+ pSlot->n = nByte;
+ if( pSlot->eType==SQLITE_BLOB ){
+ memcpy(pSlot->z, sqlite3_value_blob(argv[1]), nByte);
+ }else{
+ memcpy(pSlot->z, sqlite3_value_text(argv[1]), nByte);
+ }
+ break;
+ }
+ }
+}
+
+static int idxLargestIndex(sqlite3 *db, int *pnMax, char **pzErr){
+ int rc = SQLITE_OK;
+ const char *zMax =
+ "SELECT max(i.seqno) FROM "
+ " sqlite_master AS s, "
+ " pragma_index_list(s.name) AS l, "
+ " pragma_index_info(l.name) AS i "
+ "WHERE s.type = 'table'";
+ sqlite3_stmt *pMax = 0;
+
+ *pnMax = 0;
+ rc = idxPrepareStmt(db, &pMax, pzErr, zMax);
+ if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pMax) ){
+ *pnMax = sqlite3_column_int(pMax, 0) + 1;
+ }
+ idxFinalize(&rc, pMax);
+
+ return rc;
+}
+
+static int idxPopulateOneStat1(
+ sqlite3expert *p,
+ sqlite3_stmt *pIndexXInfo,
+ sqlite3_stmt *pWriteStat,
+ const char *zTab,
+ const char *zIdx,
+ char **pzErr
+){
+ char *zCols = 0;
+ char *zOrder = 0;
+ char *zQuery = 0;
+ int nCol = 0;
+ int i;
+ sqlite3_stmt *pQuery = 0;
+ int *aStat = 0;
+ int rc = SQLITE_OK;
+
+ assert( p->iSample>0 );
+
+ /* Formulate the query text */
+ sqlite3_bind_text(pIndexXInfo, 1, zIdx, -1, SQLITE_STATIC);
+ while( SQLITE_OK==rc && SQLITE_ROW==sqlite3_step(pIndexXInfo) ){
+ const char *zComma = zCols==0 ? "" : ", ";
+ const char *zName = (const char*)sqlite3_column_text(pIndexXInfo, 0);
+ const char *zColl = (const char*)sqlite3_column_text(pIndexXInfo, 1);
+ zCols = idxAppendText(&rc, zCols,
+ "%sx.%Q IS rem(%d, x.%Q) COLLATE %s", zComma, zName, nCol, zName, zColl
+ );
+ zOrder = idxAppendText(&rc, zOrder, "%s%d", zComma, ++nCol);
+ }
+ sqlite3_reset(pIndexXInfo);
+ if( rc==SQLITE_OK ){
+ if( p->iSample==100 ){
+ zQuery = sqlite3_mprintf(
+ "SELECT %s FROM %Q x ORDER BY %s", zCols, zTab, zOrder
+ );
+ }else{
+ zQuery = sqlite3_mprintf(
+ "SELECT %s FROM temp."UNIQUE_TABLE_NAME" x ORDER BY %s", zCols, zOrder
+ );
+ }
+ }
+ sqlite3_free(zCols);
+ sqlite3_free(zOrder);
+
+ /* Formulate the query text */
+ if( rc==SQLITE_OK ){
+ sqlite3 *dbrem = (p->iSample==100 ? p->db : p->dbv);
+ rc = idxPrepareStmt(dbrem, &pQuery, pzErr, zQuery);
+ }
+ sqlite3_free(zQuery);
+
+ if( rc==SQLITE_OK ){
+ aStat = (int*)idxMalloc(&rc, sizeof(int)*(nCol+1));
+ }
+ if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pQuery) ){
+ IdxHashEntry *pEntry;
+ char *zStat = 0;
+ for(i=0; i<=nCol; i++) aStat[i] = 1;
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pQuery) ){
+ aStat[0]++;
+ for(i=0; i<nCol; i++){
+ if( sqlite3_column_int(pQuery, i)==0 ) break;
+ }
+ for(/*no-op*/; i<nCol; i++){
+ aStat[i+1]++;
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ int s0 = aStat[0];
+ zStat = sqlite3_mprintf("%d", s0);
+ if( zStat==0 ) rc = SQLITE_NOMEM;
+ for(i=1; rc==SQLITE_OK && i<=nCol; i++){
+ zStat = idxAppendText(&rc, zStat, " %d", (s0+aStat[i]/2) / aStat[i]);
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_text(pWriteStat, 1, zTab, -1, SQLITE_STATIC);
+ sqlite3_bind_text(pWriteStat, 2, zIdx, -1, SQLITE_STATIC);
+ sqlite3_bind_text(pWriteStat, 3, zStat, -1, SQLITE_STATIC);
+ sqlite3_step(pWriteStat);
+ rc = sqlite3_reset(pWriteStat);
+ }
+
+ pEntry = idxHashFind(&p->hIdx, zIdx, STRLEN(zIdx));
+ if( pEntry ){
+ assert( pEntry->zVal2==0 );
+ pEntry->zVal2 = zStat;
+ }else{
+ sqlite3_free(zStat);
+ }
+ }
+ sqlite3_free(aStat);
+ idxFinalize(&rc, pQuery);
+
+ return rc;
+}
+
+static int idxBuildSampleTable(sqlite3expert *p, const char *zTab){
+ int rc;
+ char *zSql;
+
+ rc = sqlite3_exec(p->dbv,"DROP TABLE IF EXISTS temp."UNIQUE_TABLE_NAME,0,0,0);
+ if( rc!=SQLITE_OK ) return rc;
+
+ zSql = sqlite3_mprintf(
+ "CREATE TABLE temp." UNIQUE_TABLE_NAME " AS SELECT * FROM %Q", zTab
+ );
+ if( zSql==0 ) return SQLITE_NOMEM;
+ rc = sqlite3_exec(p->dbv, zSql, 0, 0, 0);
+ sqlite3_free(zSql);
+
+ return rc;
+}
+
+/*
+** This function is called as part of sqlite3_expert_analyze(). Candidate
+** indexes have already been created in database sqlite3expert.dbm, this
+** function populates sqlite_stat1 table in the same database.
+**
+** The stat1 data is generated by querying the
+*/
+static int idxPopulateStat1(sqlite3expert *p, char **pzErr){
+ int rc = SQLITE_OK;
+ int nMax =0;
+ struct IdxRemCtx *pCtx = 0;
+ struct IdxSampleCtx samplectx;
+ int i;
+ i64 iPrev = -100000;
+ sqlite3_stmt *pAllIndex = 0;
+ sqlite3_stmt *pIndexXInfo = 0;
+ sqlite3_stmt *pWrite = 0;
+
+ const char *zAllIndex =
+ "SELECT s.rowid, s.name, l.name FROM "
+ " sqlite_master AS s, "
+ " pragma_index_list(s.name) AS l "
+ "WHERE s.type = 'table'";
+ const char *zIndexXInfo =
+ "SELECT name, coll FROM pragma_index_xinfo(?) WHERE key";
+ const char *zWrite = "INSERT INTO sqlite_stat1 VALUES(?, ?, ?)";
+
+ /* If iSample==0, no sqlite_stat1 data is required. */
+ if( p->iSample==0 ) return SQLITE_OK;
+
+ rc = idxLargestIndex(p->dbm, &nMax, pzErr);
+ if( nMax<=0 || rc!=SQLITE_OK ) return rc;
+
+ rc = sqlite3_exec(p->dbm, "ANALYZE; PRAGMA writable_schema=1", 0, 0, 0);
+
+ if( rc==SQLITE_OK ){
+ int nByte = sizeof(struct IdxRemCtx) + (sizeof(struct IdxRemSlot) * nMax);
+ pCtx = (struct IdxRemCtx*)idxMalloc(&rc, nByte);
+ }
+
+ if( rc==SQLITE_OK ){
+ sqlite3 *dbrem = (p->iSample==100 ? p->db : p->dbv);
+ rc = sqlite3_create_function(
+ dbrem, "rem", 2, SQLITE_UTF8, (void*)pCtx, idxRemFunc, 0, 0
+ );
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(
+ p->db, "sample", 0, SQLITE_UTF8, (void*)&samplectx, idxSampleFunc, 0, 0
+ );
+ }
+
+ if( rc==SQLITE_OK ){
+ pCtx->nSlot = nMax+1;
+ rc = idxPrepareStmt(p->dbm, &pAllIndex, pzErr, zAllIndex);
+ }
+ if( rc==SQLITE_OK ){
+ rc = idxPrepareStmt(p->dbm, &pIndexXInfo, pzErr, zIndexXInfo);
+ }
+ if( rc==SQLITE_OK ){
+ rc = idxPrepareStmt(p->dbm, &pWrite, pzErr, zWrite);
+ }
+
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pAllIndex) ){
+ i64 iRowid = sqlite3_column_int64(pAllIndex, 0);
+ const char *zTab = (const char*)sqlite3_column_text(pAllIndex, 1);
+ const char *zIdx = (const char*)sqlite3_column_text(pAllIndex, 2);
+ if( p->iSample<100 && iPrev!=iRowid ){
+ samplectx.target = (double)p->iSample / 100.0;
+ samplectx.iTarget = p->iSample;
+ samplectx.nRow = 0.0;
+ samplectx.nRet = 0.0;
+ rc = idxBuildSampleTable(p, zTab);
+ if( rc!=SQLITE_OK ) break;
+ }
+ rc = idxPopulateOneStat1(p, pIndexXInfo, pWrite, zTab, zIdx, pzErr);
+ iPrev = iRowid;
+ }
+ if( rc==SQLITE_OK && p->iSample<100 ){
+ rc = sqlite3_exec(p->dbv,
+ "DROP TABLE IF EXISTS temp." UNIQUE_TABLE_NAME, 0,0,0
+ );
+ }
+
+ idxFinalize(&rc, pAllIndex);
+ idxFinalize(&rc, pIndexXInfo);
+ idxFinalize(&rc, pWrite);
+
+ for(i=0; i<pCtx->nSlot; i++){
+ sqlite3_free(pCtx->aSlot[i].z);
+ }
+ sqlite3_free(pCtx);
+
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_exec(p->dbm, "ANALYZE sqlite_master", 0, 0, 0);
+ }
+
+ sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp."UNIQUE_TABLE_NAME,0,0,0);
+ return rc;
+}
+
+/*
+** Allocate a new sqlite3expert object.
+*/
+sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){
+ int rc = SQLITE_OK;
+ sqlite3expert *pNew;
+
+ pNew = (sqlite3expert*)idxMalloc(&rc, sizeof(sqlite3expert));
+
+ /* Open two in-memory databases to work with. The "vtab database" (dbv)
+ ** will contain a virtual table corresponding to each real table in
+ ** the user database schema, and a copy of each view. It is used to
+ ** collect information regarding the WHERE, ORDER BY and other clauses
+ ** of the user's query.
+ */
+ if( rc==SQLITE_OK ){
+ pNew->db = db;
+ pNew->iSample = 100;
+ rc = sqlite3_open(":memory:", &pNew->dbv);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_open(":memory:", &pNew->dbm);
+ if( rc==SQLITE_OK ){
+ sqlite3_db_config(pNew->dbm, SQLITE_DBCONFIG_TRIGGER_EQP, 1, (int*)0);
+ }
+ }
+
+
+ /* Copy the entire schema of database [db] into [dbm]. */
+ if( rc==SQLITE_OK ){
+ sqlite3_stmt *pSql;
+ rc = idxPrintfPrepareStmt(pNew->db, &pSql, pzErrmsg,
+ "SELECT sql FROM sqlite_master WHERE name NOT LIKE 'sqlite_%%'"
+ " AND sql NOT LIKE 'CREATE VIRTUAL %%'"
+ );
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
+ const char *zSql = (const char*)sqlite3_column_text(pSql, 0);
+ rc = sqlite3_exec(pNew->dbm, zSql, 0, 0, pzErrmsg);
+ }
+ idxFinalize(&rc, pSql);
+ }
+
+ /* Create the vtab schema */
+ if( rc==SQLITE_OK ){
+ rc = idxCreateVtabSchema(pNew, pzErrmsg);
+ }
+
+ /* Register the auth callback with dbv */
+ if( rc==SQLITE_OK ){
+ sqlite3_set_authorizer(pNew->dbv, idxAuthCallback, (void*)pNew);
+ }
+
+ /* If an error has occurred, free the new object and reutrn NULL. Otherwise,
+ ** return the new sqlite3expert handle. */
+ if( rc!=SQLITE_OK ){
+ sqlite3_expert_destroy(pNew);
+ pNew = 0;
+ }
+ return pNew;
+}
+
+/*
+** Configure an sqlite3expert object.
+*/
+int sqlite3_expert_config(sqlite3expert *p, int op, ...){
+ int rc = SQLITE_OK;
+ va_list ap;
+ va_start(ap, op);
+ switch( op ){
+ case EXPERT_CONFIG_SAMPLE: {
+ int iVal = va_arg(ap, int);
+ if( iVal<0 ) iVal = 0;
+ if( iVal>100 ) iVal = 100;
+ p->iSample = iVal;
+ break;
+ }
+ default:
+ rc = SQLITE_NOTFOUND;
+ break;
+ }
+
+ va_end(ap);
+ return rc;
+}
+
+/*
+** Add an SQL statement to the analysis.
+*/
+int sqlite3_expert_sql(
+ sqlite3expert *p, /* From sqlite3_expert_new() */
+ const char *zSql, /* SQL statement to add */
+ char **pzErr /* OUT: Error message (if any) */
+){
+ IdxScan *pScanOrig = p->pScan;
+ IdxStatement *pStmtOrig = p->pStatement;
+ int rc = SQLITE_OK;
+ const char *zStmt = zSql;
+
+ if( p->bRun ) return SQLITE_MISUSE;
+
+ while( rc==SQLITE_OK && zStmt && zStmt[0] ){
+ sqlite3_stmt *pStmt = 0;
+ rc = sqlite3_prepare_v2(p->dbv, zStmt, -1, &pStmt, &zStmt);
+ if( rc==SQLITE_OK ){
+ if( pStmt ){
+ IdxStatement *pNew;
+ const char *z = sqlite3_sql(pStmt);
+ int n = STRLEN(z);
+ pNew = (IdxStatement*)idxMalloc(&rc, sizeof(IdxStatement) + n+1);
+ if( rc==SQLITE_OK ){
+ pNew->zSql = (char*)&pNew[1];
+ memcpy(pNew->zSql, z, n+1);
+ pNew->pNext = p->pStatement;
+ if( p->pStatement ) pNew->iId = p->pStatement->iId+1;
+ p->pStatement = pNew;
+ }
+ sqlite3_finalize(pStmt);
+ }
+ }else{
+ idxDatabaseError(p->dbv, pzErr);
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ idxScanFree(p->pScan, pScanOrig);
+ idxStatementFree(p->pStatement, pStmtOrig);
+ p->pScan = pScanOrig;
+ p->pStatement = pStmtOrig;
+ }
+
+ return rc;
+}
+
+int sqlite3_expert_analyze(sqlite3expert *p, char **pzErr){
+ int rc;
+ IdxHashEntry *pEntry;
+
+ /* Do trigger processing to collect any extra IdxScan structures */
+ rc = idxProcessTriggers(p, pzErr);
+
+ /* Create candidate indexes within the in-memory database file */
+ if( rc==SQLITE_OK ){
+ rc = idxCreateCandidates(p);
+ }
+
+ /* Generate the stat1 data */
+ if( rc==SQLITE_OK ){
+ rc = idxPopulateStat1(p, pzErr);
+ }
+
+ /* Formulate the EXPERT_REPORT_CANDIDATES text */
+ for(pEntry=p->hIdx.pFirst; pEntry; pEntry=pEntry->pNext){
+ p->zCandidates = idxAppendText(&rc, p->zCandidates,
+ "%s;%s%s\n", pEntry->zVal,
+ pEntry->zVal2 ? " -- stat1: " : "", pEntry->zVal2
+ );
+ }
+
+ /* Figure out which of the candidate indexes are preferred by the query
+ ** planner and report the results to the user. */
+ if( rc==SQLITE_OK ){
+ rc = idxFindIndexes(p, pzErr);
+ }
+
+ if( rc==SQLITE_OK ){
+ p->bRun = 1;
+ }
+ return rc;
+}
+
+/*
+** Return the total number of statements that have been added to this
+** sqlite3expert using sqlite3_expert_sql().
+*/
+int sqlite3_expert_count(sqlite3expert *p){
+ int nRet = 0;
+ if( p->pStatement ) nRet = p->pStatement->iId+1;
+ return nRet;
+}
+
+/*
+** Return a component of the report.
+*/
+const char *sqlite3_expert_report(sqlite3expert *p, int iStmt, int eReport){
+ const char *zRet = 0;
+ IdxStatement *pStmt;
+
+ if( p->bRun==0 ) return 0;
+ for(pStmt=p->pStatement; pStmt && pStmt->iId!=iStmt; pStmt=pStmt->pNext);
+ switch( eReport ){
+ case EXPERT_REPORT_SQL:
+ if( pStmt ) zRet = pStmt->zSql;
+ break;
+ case EXPERT_REPORT_INDEXES:
+ if( pStmt ) zRet = pStmt->zIdx;
+ break;
+ case EXPERT_REPORT_PLAN:
+ if( pStmt ) zRet = pStmt->zEQP;
+ break;
+ case EXPERT_REPORT_CANDIDATES:
+ zRet = p->zCandidates;
+ break;
+ }
+ return zRet;
+}
+
+/*
+** Free an sqlite3expert object.
+*/
+void sqlite3_expert_destroy(sqlite3expert *p){
+ if( p ){
+ sqlite3_close(p->dbm);
+ sqlite3_close(p->dbv);
+ idxScanFree(p->pScan, 0);
+ idxStatementFree(p->pStatement, 0);
+ idxTableFree(p->pTable);
+ idxWriteFree(p->pWrite);
+ idxHashClear(&p->hIdx);
+ sqlite3_free(p->zCandidates);
+ sqlite3_free(p);
+ }
+}
+
+#endif /* ifndef SQLITE_OMIT_VIRTUAL_TABLE */
+
+/************************* End ../ext/expert/sqlite3expert.c ********************/
+
+#if defined(SQLITE_ENABLE_SESSION)
+/*
+** State information for a single open session
+*/
+typedef struct OpenSession OpenSession;
+struct OpenSession {
+ char *zName; /* Symbolic name for this session */
+ int nFilter; /* Number of xFilter rejection GLOB patterns */
+ char **azFilter; /* Array of xFilter rejection GLOB patterns */
+ sqlite3_session *p; /* The open session */
+};
+#endif
+
+/*
+** Shell output mode information from before ".explain on",
+** saved so that it can be restored by ".explain off"
+*/
+typedef struct SavedModeInfo SavedModeInfo;
+struct SavedModeInfo {
+ int valid; /* Is there legit data in here? */
+ int mode; /* Mode prior to ".explain on" */
+ int showHeader; /* The ".header" setting prior to ".explain on" */
+ int colWidth[100]; /* Column widths prior to ".explain on" */
+};
+
+typedef struct ExpertInfo ExpertInfo;
+struct ExpertInfo {
+ sqlite3expert *pExpert;
+ int bVerbose;
+};
+
+/* A single line in the EQP output */
+typedef struct EQPGraphRow EQPGraphRow;
+struct EQPGraphRow {
+ int iEqpId; /* ID for this row */
+ int iParentId; /* ID of the parent row */
+ EQPGraphRow *pNext; /* Next row in sequence */
+ char zText[1]; /* Text to display for this row */
+};
+
+/* All EQP output is collected into an instance of the following */
+typedef struct EQPGraph EQPGraph;
+struct EQPGraph {
+ EQPGraphRow *pRow; /* Linked list of all rows of the EQP output */
+ EQPGraphRow *pLast; /* Last element of the pRow list */
+ char zPrefix[100]; /* Graph prefix */
+};
+
+/*
+** State information about the database connection is contained in an
+** instance of the following structure.
+*/
+typedef struct ShellState ShellState;
+struct ShellState {
+ sqlite3 *db; /* The database */
+ u8 autoExplain; /* Automatically turn on .explain mode */
+ u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */
+ u8 autoEQPtest; /* autoEQP is in test mode */
+ u8 autoEQPtrace; /* autoEQP is in trace mode */
+ u8 statsOn; /* True to display memory stats before each finalize */
+ u8 scanstatsOn; /* True to display scan stats before each finalize */
+ u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */
+ u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */
+ u8 nEqpLevel; /* Depth of the EQP output graph */
+ u8 eTraceType; /* SHELL_TRACE_* value for type of trace */
+ unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */
+ int outCount; /* Revert to stdout when reaching zero */
+ int cnt; /* Number of records displayed so far */
+ int lineno; /* Line number of last line read from in */
+ FILE *in; /* Read commands from this stream */
+ FILE *out; /* Write results here */
+ FILE *traceOut; /* Output for sqlite3_trace() */
+ int nErr; /* Number of errors seen */
+ int mode; /* An output mode setting */
+ int modePrior; /* Saved mode */
+ int cMode; /* temporary output mode for the current query */
+ int normalMode; /* Output mode before ".explain on" */
+ int writableSchema; /* True if PRAGMA writable_schema=ON */
+ int showHeader; /* True to show column names in List or Column mode */
+ int nCheck; /* Number of ".check" commands run */
+ unsigned nProgress; /* Number of progress callbacks encountered */
+ unsigned mxProgress; /* Maximum progress callbacks before failing */
+ unsigned flgProgress; /* Flags for the progress callback */
+ unsigned shellFlgs; /* Various flags */
+ sqlite3_int64 szMax; /* --maxsize argument to .open */
+ char *zDestTable; /* Name of destination table when MODE_Insert */
+ char *zTempFile; /* Temporary file that might need deleting */
+ char zTestcase[30]; /* Name of current test case */
+ char colSeparator[20]; /* Column separator character for several modes */
+ char rowSeparator[20]; /* Row separator character for MODE_Ascii */
+ char colSepPrior[20]; /* Saved column separator */
+ char rowSepPrior[20]; /* Saved row separator */
+ int colWidth[100]; /* Requested width of each column when in column mode*/
+ int actualWidth[100]; /* Actual width of each column */
+ char nullValue[20]; /* The text to print when a NULL comes back from
+ ** the database */
+ char outfile[FILENAME_MAX]; /* Filename for *out */
+ const char *zDbFilename; /* name of the database file */
+ char *zFreeOnClose; /* Filename to free when closing */
+ const char *zVfs; /* Name of VFS to use */
+ sqlite3_stmt *pStmt; /* Current statement if any. */
+ FILE *pLog; /* Write log output here */
+ int *aiIndent; /* Array of indents used in MODE_Explain */
+ int nIndent; /* Size of array aiIndent[] */
+ int iIndent; /* Index of current op in aiIndent[] */
+ EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */
+#if defined(SQLITE_ENABLE_SESSION)
+ int nSession; /* Number of active sessions */
+ OpenSession aSession[4]; /* Array of sessions. [0] is in focus. */
+#endif
+ ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */
+};
+
+
+/* Allowed values for ShellState.autoEQP
+*/
+#define AUTOEQP_off 0 /* Automatic EXPLAIN QUERY PLAN is off */
+#define AUTOEQP_on 1 /* Automatic EQP is on */
+#define AUTOEQP_trigger 2 /* On and also show plans for triggers */
+#define AUTOEQP_full 3 /* Show full EXPLAIN */
+
+/* Allowed values for ShellState.openMode
+*/
+#define SHELL_OPEN_UNSPEC 0 /* No open-mode specified */
+#define SHELL_OPEN_NORMAL 1 /* Normal database file */
+#define SHELL_OPEN_APPENDVFS 2 /* Use appendvfs */
+#define SHELL_OPEN_ZIPFILE 3 /* Use the zipfile virtual table */
+#define SHELL_OPEN_READONLY 4 /* Open a normal database read-only */
+#define SHELL_OPEN_DESERIALIZE 5 /* Open using sqlite3_deserialize() */
+#define SHELL_OPEN_HEXDB 6 /* Use "dbtotxt" output as data source */
+
+/* Allowed values for ShellState.eTraceType
+*/
+#define SHELL_TRACE_PLAIN 0 /* Show input SQL text */
+#define SHELL_TRACE_EXPANDED 1 /* Show expanded SQL text */
+#define SHELL_TRACE_NORMALIZED 2 /* Show normalized SQL text */
+
+/* Bits in the ShellState.flgProgress variable */
+#define SHELL_PROGRESS_QUIET 0x01 /* Omit announcing every progress callback */
+#define SHELL_PROGRESS_RESET 0x02 /* Reset the count when the progres
+ ** callback limit is reached, and for each
+ ** top-level SQL statement */
+#define SHELL_PROGRESS_ONCE 0x04 /* Cancel the --limit after firing once */
+
+/*
+** These are the allowed shellFlgs values
+*/
+#define SHFLG_Pagecache 0x00000001 /* The --pagecache option is used */
+#define SHFLG_Lookaside 0x00000002 /* Lookaside memory is used */
+#define SHFLG_Backslash 0x00000004 /* The --backslash option is used */
+#define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */
+#define SHFLG_Newlines 0x00000010 /* .dump --newline flag */
+#define SHFLG_CountChanges 0x00000020 /* .changes setting */
+#define SHFLG_Echo 0x00000040 /* .echo or --echo setting */
+
+/*
+** Macros for testing and setting shellFlgs
+*/
+#define ShellHasFlag(P,X) (((P)->shellFlgs & (X))!=0)
+#define ShellSetFlag(P,X) ((P)->shellFlgs|=(X))
+#define ShellClearFlag(P,X) ((P)->shellFlgs&=(~(X)))
+
+/*
+** These are the allowed modes.
+*/
+#define MODE_Line 0 /* One column per line. Blank line between records */
+#define MODE_Column 1 /* One record per line in neat columns */
+#define MODE_List 2 /* One record per line with a separator */
+#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */
+#define MODE_Html 4 /* Generate an XHTML table */
+#define MODE_Insert 5 /* Generate SQL "insert" statements */
+#define MODE_Quote 6 /* Quote values as for SQL */
+#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */
+#define MODE_Csv 8 /* Quote strings, numbers are plain */
+#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */
+#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */
+#define MODE_Pretty 11 /* Pretty-print schemas */
+#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */
+
+static const char *modeDescr[] = {
+ "line",
+ "column",
+ "list",
+ "semi",
+ "html",
+ "insert",
+ "quote",
+ "tcl",
+ "csv",
+ "explain",
+ "ascii",
+ "prettyprint",
+ "eqp"
+};
+
+/*
+** These are the column/row/line separators used by the various
+** import/export modes.
+*/
+#define SEP_Column "|"
+#define SEP_Row "\n"
+#define SEP_Tab "\t"
+#define SEP_Space " "
+#define SEP_Comma ","
+#define SEP_CrLf "\r\n"
+#define SEP_Unit "\x1F"
+#define SEP_Record "\x1E"
+
+/*
+** A callback for the sqlite3_log() interface.
+*/
+static void shellLog(void *pArg, int iErrCode, const char *zMsg){
+ ShellState *p = (ShellState*)pArg;
+ if( p->pLog==0 ) return;
+ utf8_printf(p->pLog, "(%d) %s\n", iErrCode, zMsg);
+ fflush(p->pLog);
+}
+
+/*
+** SQL function: shell_putsnl(X)
+**
+** Write the text X to the screen (or whatever output is being directed)
+** adding a newline at the end, and then return X.
+*/
+static void shellPutsFunc(
+ sqlite3_context *pCtx,
+ int nVal,
+ sqlite3_value **apVal
+){
+ ShellState *p = (ShellState*)sqlite3_user_data(pCtx);
+ (void)nVal;
+ utf8_printf(p->out, "%s\n", sqlite3_value_text(apVal[0]));
+ sqlite3_result_value(pCtx, apVal[0]);
+}
+
+/*
+** SQL function: edit(VALUE)
+** edit(VALUE,EDITOR)
+**
+** These steps:
+**
+** (1) Write VALUE into a temporary file.
+** (2) Run program EDITOR on that temporary file.
+** (3) Read the temporary file back and return its content as the result.
+** (4) Delete the temporary file
+**
+** If the EDITOR argument is omitted, use the value in the VISUAL
+** environment variable. If still there is no EDITOR, through an error.
+**
+** Also throw an error if the EDITOR program returns a non-zero exit code.
+*/
+#ifndef SQLITE_NOHAVE_SYSTEM
+static void editFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *zEditor;
+ char *zTempFile = 0;
+ sqlite3 *db;
+ char *zCmd = 0;
+ int bBin;
+ int rc;
+ int hasCRNL = 0;
+ FILE *f = 0;
+ sqlite3_int64 sz;
+ sqlite3_int64 x;
+ unsigned char *p = 0;
+
+ if( argc==2 ){
+ zEditor = (const char*)sqlite3_value_text(argv[1]);
+ }else{
+ zEditor = getenv("VISUAL");
+ }
+ if( zEditor==0 ){
+ sqlite3_result_error(context, "no editor for edit()", -1);
+ return;
+ }
+ if( sqlite3_value_type(argv[0])==SQLITE_NULL ){
+ sqlite3_result_error(context, "NULL input to edit()", -1);
+ return;
+ }
+ db = sqlite3_context_db_handle(context);
+ zTempFile = 0;
+ sqlite3_file_control(db, 0, SQLITE_FCNTL_TEMPFILENAME, &zTempFile);
+ if( zTempFile==0 ){
+ sqlite3_uint64 r = 0;
+ sqlite3_randomness(sizeof(r), &r);
+ zTempFile = sqlite3_mprintf("temp%llx", r);
+ if( zTempFile==0 ){
+ sqlite3_result_error_nomem(context);
+ return;
+ }
+ }
+ bBin = sqlite3_value_type(argv[0])==SQLITE_BLOB;
+ /* When writing the file to be edited, do \n to \r\n conversions on systems
+ ** that want \r\n line endings */
+ f = fopen(zTempFile, bBin ? "wb" : "w");
+ if( f==0 ){
+ sqlite3_result_error(context, "edit() cannot open temp file", -1);
+ goto edit_func_end;
+ }
+ sz = sqlite3_value_bytes(argv[0]);
+ if( bBin ){
+ x = fwrite(sqlite3_value_blob(argv[0]), 1, sz, f);
+ }else{
+ const char *z = (const char*)sqlite3_value_text(argv[0]);
+ /* Remember whether or not the value originally contained \r\n */
+ if( z && strstr(z,"\r\n")!=0 ) hasCRNL = 1;
+ x = fwrite(sqlite3_value_text(argv[0]), 1, sz, f);
+ }
+ fclose(f);
+ f = 0;
+ if( x!=sz ){
+ sqlite3_result_error(context, "edit() could not write the whole file", -1);
+ goto edit_func_end;
+ }
+ zCmd = sqlite3_mprintf("%s \"%s\"", zEditor, zTempFile);
+ if( zCmd==0 ){
+ sqlite3_result_error_nomem(context);
+ goto edit_func_end;
+ }
+ rc = system(zCmd);
+ sqlite3_free(zCmd);
+ if( rc ){
+ sqlite3_result_error(context, "EDITOR returned non-zero", -1);
+ goto edit_func_end;
+ }
+ f = fopen(zTempFile, "rb");
+ if( f==0 ){
+ sqlite3_result_error(context,
+ "edit() cannot reopen temp file after edit", -1);
+ goto edit_func_end;
+ }
+ fseek(f, 0, SEEK_END);
+ sz = ftell(f);
+ rewind(f);
+ p = sqlite3_malloc64( sz+(bBin==0) );
+ if( p==0 ){
+ sqlite3_result_error_nomem(context);
+ goto edit_func_end;
+ }
+ x = fread(p, 1, sz, f);
+ fclose(f);
+ f = 0;
+ if( x!=sz ){
+ sqlite3_result_error(context, "could not read back the whole file", -1);
+ goto edit_func_end;
+ }
+ if( bBin ){
+ sqlite3_result_blob64(context, p, sz, sqlite3_free);
+ }else{
+ sqlite3_int64 i, j;
+ if( hasCRNL ){
+ /* If the original contains \r\n then do no conversions back to \n */
+ j = sz;
+ }else{
+ /* If the file did not originally contain \r\n then convert any new
+ ** \r\n back into \n */
+ for(i=j=0; i<sz; i++){
+ if( p[i]=='\r' && p[i+1]=='\n' ) i++;
+ p[j++] = p[i];
+ }
+ sz = j;
+ p[sz] = 0;
+ }
+ sqlite3_result_text64(context, (const char*)p, sz,
+ sqlite3_free, SQLITE_UTF8);
+ }
+ p = 0;
+
+edit_func_end:
+ if( f ) fclose(f);
+ unlink(zTempFile);
+ sqlite3_free(zTempFile);
+ sqlite3_free(p);
+}
+#endif /* SQLITE_NOHAVE_SYSTEM */
+
+/*
+** Save or restore the current output mode
+*/
+static void outputModePush(ShellState *p){
+ p->modePrior = p->mode;
+ memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator));
+ memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator));
+}
+static void outputModePop(ShellState *p){
+ p->mode = p->modePrior;
+ memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator));
+ memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator));
+}
+
+/*
+** Output the given string as a hex-encoded blob (eg. X'1234' )
+*/
+static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){
+ int i;
+ char *zBlob = (char *)pBlob;
+ raw_printf(out,"X'");
+ for(i=0; i<nBlob; i++){ raw_printf(out,"%02x",zBlob[i]&0xff); }
+ raw_printf(out,"'");
+}
+
+/*
+** Find a string that is not found anywhere in z[]. Return a pointer
+** to that string.
+**
+** Try to use zA and zB first. If both of those are already found in z[]
+** then make up some string and store it in the buffer zBuf.
+*/
+static const char *unused_string(
+ const char *z, /* Result must not appear anywhere in z */
+ const char *zA, const char *zB, /* Try these first */
+ char *zBuf /* Space to store a generated string */
+){
+ unsigned i = 0;
+ if( strstr(z, zA)==0 ) return zA;
+ if( strstr(z, zB)==0 ) return zB;
+ do{
+ sqlite3_snprintf(20,zBuf,"(%s%u)", zA, i++);
+ }while( strstr(z,zBuf)!=0 );
+ return zBuf;
+}
+
+/*
+** Output the given string as a quoted string using SQL quoting conventions.
+**
+** See also: output_quoted_escaped_string()
+*/
+static void output_quoted_string(FILE *out, const char *z){
+ int i;
+ char c;
+ setBinaryMode(out, 1);
+ for(i=0; (c = z[i])!=0 && c!='\''; i++){}
+ if( c==0 ){
+ utf8_printf(out,"'%s'",z);
+ }else{
+ raw_printf(out, "'");
+ while( *z ){
+ for(i=0; (c = z[i])!=0 && c!='\''; i++){}
+ if( c=='\'' ) i++;
+ if( i ){
+ utf8_printf(out, "%.*s", i, z);
+ z += i;
+ }
+ if( c=='\'' ){
+ raw_printf(out, "'");
+ continue;
+ }
+ if( c==0 ){
+ break;
+ }
+ z++;
+ }
+ raw_printf(out, "'");
+ }
+ setTextMode(out, 1);
+}
+
+/*
+** Output the given string as a quoted string using SQL quoting conventions.
+** Additionallly , escape the "\n" and "\r" characters so that they do not
+** get corrupted by end-of-line translation facilities in some operating
+** systems.
+**
+** This is like output_quoted_string() but with the addition of the \r\n
+** escape mechanism.
+*/
+static void output_quoted_escaped_string(FILE *out, const char *z){
+ int i;
+ char c;
+ setBinaryMode(out, 1);
+ for(i=0; (c = z[i])!=0 && c!='\'' && c!='\n' && c!='\r'; i++){}
+ if( c==0 ){
+ utf8_printf(out,"'%s'",z);
+ }else{
+ const char *zNL = 0;
+ const char *zCR = 0;
+ int nNL = 0;
+ int nCR = 0;
+ char zBuf1[20], zBuf2[20];
+ for(i=0; z[i]; i++){
+ if( z[i]=='\n' ) nNL++;
+ if( z[i]=='\r' ) nCR++;
+ }
+ if( nNL ){
+ raw_printf(out, "replace(");
+ zNL = unused_string(z, "\\n", "\\012", zBuf1);
+ }
+ if( nCR ){
+ raw_printf(out, "replace(");
+ zCR = unused_string(z, "\\r", "\\015", zBuf2);
+ }
+ raw_printf(out, "'");
+ while( *z ){
+ for(i=0; (c = z[i])!=0 && c!='\n' && c!='\r' && c!='\''; i++){}
+ if( c=='\'' ) i++;
+ if( i ){
+ utf8_printf(out, "%.*s", i, z);
+ z += i;
+ }
+ if( c=='\'' ){
+ raw_printf(out, "'");
+ continue;
+ }
+ if( c==0 ){
+ break;
+ }
+ z++;
+ if( c=='\n' ){
+ raw_printf(out, "%s", zNL);
+ continue;
+ }
+ raw_printf(out, "%s", zCR);
+ }
+ raw_printf(out, "'");
+ if( nCR ){
+ raw_printf(out, ",'%s',char(13))", zCR);
+ }
+ if( nNL ){
+ raw_printf(out, ",'%s',char(10))", zNL);
+ }
+ }
+ setTextMode(out, 1);
+}
+
+/*
+** Output the given string as a quoted according to C or TCL quoting rules.
+*/
+static void output_c_string(FILE *out, const char *z){
+ unsigned int c;
+ fputc('"', out);
+ while( (c = *(z++))!=0 ){
+ if( c=='\\' ){
+ fputc(c, out);
+ fputc(c, out);
+ }else if( c=='"' ){
+ fputc('\\', out);
+ fputc('"', out);
+ }else if( c=='\t' ){
+ fputc('\\', out);
+ fputc('t', out);
+ }else if( c=='\n' ){
+ fputc('\\', out);
+ fputc('n', out);
+ }else if( c=='\r' ){
+ fputc('\\', out);
+ fputc('r', out);
+ }else if( !isprint(c&0xff) ){
+ raw_printf(out, "\\%03o", c&0xff);
+ }else{
+ fputc(c, out);
+ }
+ }
+ fputc('"', out);
+}
+
+/*
+** Output the given string with characters that are special to
+** HTML escaped.
+*/
+static void output_html_string(FILE *out, const char *z){
+ int i;
+ if( z==0 ) z = "";
+ while( *z ){
+ for(i=0; z[i]
+ && z[i]!='<'
+ && z[i]!='&'
+ && z[i]!='>'
+ && z[i]!='\"'
+ && z[i]!='\'';
+ i++){}
+ if( i>0 ){
+ utf8_printf(out,"%.*s",i,z);
+ }
+ if( z[i]=='<' ){
+ raw_printf(out,"<");
+ }else if( z[i]=='&' ){
+ raw_printf(out,"&");
+ }else if( z[i]=='>' ){
+ raw_printf(out,">");
+ }else if( z[i]=='\"' ){
+ raw_printf(out,""");
+ }else if( z[i]=='\'' ){
+ raw_printf(out,"'");
+ }else{
+ break;
+ }
+ z += i + 1;
+ }
+}
+
+/*
+** If a field contains any character identified by a 1 in the following
+** array, then the string must be quoted for CSV.
+*/
+static const char needCsvQuote[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+};
+
+/*
+** Output a single term of CSV. Actually, p->colSeparator is used for
+** the separator, which may or may not be a comma. p->nullValue is
+** the null value. Strings are quoted if necessary. The separator
+** is only issued if bSep is true.
+*/
+static void output_csv(ShellState *p, const char *z, int bSep){
+ FILE *out = p->out;
+ if( z==0 ){
+ utf8_printf(out,"%s",p->nullValue);
+ }else{
+ int i;
+ int nSep = strlen30(p->colSeparator);
+ for(i=0; z[i]; i++){
+ if( needCsvQuote[((unsigned char*)z)[i]]
+ || (z[i]==p->colSeparator[0] &&
+ (nSep==1 || memcmp(z, p->colSeparator, nSep)==0)) ){
+ i = 0;
+ break;
+ }
+ }
+ if( i==0 ){
+ char *zQuoted = sqlite3_mprintf("\"%w\"", z);
+ utf8_printf(out, "%s", zQuoted);
+ sqlite3_free(zQuoted);
+ }else{
+ utf8_printf(out, "%s", z);
+ }
+ }
+ if( bSep ){
+ utf8_printf(p->out, "%s", p->colSeparator);
+ }
+}
+
+/*
+** This routine runs when the user presses Ctrl-C
+*/
+static void interrupt_handler(int NotUsed){
+ UNUSED_PARAMETER(NotUsed);
+ seenInterrupt++;
+ if( seenInterrupt>2 ) exit(1);
+ if( globalDb ) sqlite3_interrupt(globalDb);
+}
+
+#if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
+/*
+** This routine runs for console events (e.g. Ctrl-C) on Win32
+*/
+static BOOL WINAPI ConsoleCtrlHandler(
+ DWORD dwCtrlType /* One of the CTRL_*_EVENT constants */
+){
+ if( dwCtrlType==CTRL_C_EVENT ){
+ interrupt_handler(0);
+ return TRUE;
+ }
+ return FALSE;
+}
+#endif
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+/*
+** When the ".auth ON" is set, the following authorizer callback is
+** invoked. It always returns SQLITE_OK.
+*/
+static int shellAuth(
+ void *pClientData,
+ int op,
+ const char *zA1,
+ const char *zA2,
+ const char *zA3,
+ const char *zA4
+){
+ ShellState *p = (ShellState*)pClientData;
+ static const char *azAction[] = { 0,
+ "CREATE_INDEX", "CREATE_TABLE", "CREATE_TEMP_INDEX",
+ "CREATE_TEMP_TABLE", "CREATE_TEMP_TRIGGER", "CREATE_TEMP_VIEW",
+ "CREATE_TRIGGER", "CREATE_VIEW", "DELETE",
+ "DROP_INDEX", "DROP_TABLE", "DROP_TEMP_INDEX",
+ "DROP_TEMP_TABLE", "DROP_TEMP_TRIGGER", "DROP_TEMP_VIEW",
+ "DROP_TRIGGER", "DROP_VIEW", "INSERT",
+ "PRAGMA", "READ", "SELECT",
+ "TRANSACTION", "UPDATE", "ATTACH",
+ "DETACH", "ALTER_TABLE", "REINDEX",
+ "ANALYZE", "CREATE_VTABLE", "DROP_VTABLE",
+ "FUNCTION", "SAVEPOINT", "RECURSIVE"
+ };
+ int i;
+ const char *az[4];
+ az[0] = zA1;
+ az[1] = zA2;
+ az[2] = zA3;
+ az[3] = zA4;
+ utf8_printf(p->out, "authorizer: %s", azAction[op]);
+ for(i=0; i<4; i++){
+ raw_printf(p->out, " ");
+ if( az[i] ){
+ output_c_string(p->out, az[i]);
+ }else{
+ raw_printf(p->out, "NULL");
+ }
+ }
+ raw_printf(p->out, "\n");
+ return SQLITE_OK;
+}
+#endif
+
+/*
+** Print a schema statement. Part of MODE_Semi and MODE_Pretty output.
+**
+** This routine converts some CREATE TABLE statements for shadow tables
+** in FTS3/4/5 into CREATE TABLE IF NOT EXISTS statements.
+*/
+static void printSchemaLine(FILE *out, const char *z, const char *zTail){
+ if( sqlite3_strglob("CREATE TABLE ['\"]*", z)==0 ){
+ utf8_printf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail);
+ }else{
+ utf8_printf(out, "%s%s", z, zTail);
+ }
+}
+static void printSchemaLineN(FILE *out, char *z, int n, const char *zTail){
+ char c = z[n];
+ z[n] = 0;
+ printSchemaLine(out, z, zTail);
+ z[n] = c;
+}
+
+/*
+** Return true if string z[] has nothing but whitespace and comments to the
+** end of the first line.
+*/
+static int wsToEol(const char *z){
+ int i;
+ for(i=0; z[i]; i++){
+ if( z[i]=='\n' ) return 1;
+ if( IsSpace(z[i]) ) continue;
+ if( z[i]=='-' && z[i+1]=='-' ) return 1;
+ return 0;
+ }
+ return 1;
+}
+
+/*
+** Add a new entry to the EXPLAIN QUERY PLAN data
+*/
+static void eqp_append(ShellState *p, int iEqpId, int p2, const char *zText){
+ EQPGraphRow *pNew;
+ int nText = strlen30(zText);
+ if( p->autoEQPtest ){
+ utf8_printf(p->out, "%d,%d,%s\n", iEqpId, p2, zText);
+ }
+ pNew = sqlite3_malloc64( sizeof(*pNew) + nText );
+ if( pNew==0 ) shell_out_of_memory();
+ pNew->iEqpId = iEqpId;
+ pNew->iParentId = p2;
+ memcpy(pNew->zText, zText, nText+1);
+ pNew->pNext = 0;
+ if( p->sGraph.pLast ){
+ p->sGraph.pLast->pNext = pNew;
+ }else{
+ p->sGraph.pRow = pNew;
+ }
+ p->sGraph.pLast = pNew;
+}
+
+/*
+** Free and reset the EXPLAIN QUERY PLAN data that has been collected
+** in p->sGraph.
+*/
+static void eqp_reset(ShellState *p){
+ EQPGraphRow *pRow, *pNext;
+ for(pRow = p->sGraph.pRow; pRow; pRow = pNext){
+ pNext = pRow->pNext;
+ sqlite3_free(pRow);
+ }
+ memset(&p->sGraph, 0, sizeof(p->sGraph));
+}
+
+/* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after
+** pOld, or return the first such line if pOld is NULL
+*/
+static EQPGraphRow *eqp_next_row(ShellState *p, int iEqpId, EQPGraphRow *pOld){
+ EQPGraphRow *pRow = pOld ? pOld->pNext : p->sGraph.pRow;
+ while( pRow && pRow->iParentId!=iEqpId ) pRow = pRow->pNext;
+ return pRow;
+}
+
+/* Render a single level of the graph that has iEqpId as its parent. Called
+** recursively to render sublevels.
+*/
+static void eqp_render_level(ShellState *p, int iEqpId){
+ EQPGraphRow *pRow, *pNext;
+ int n = strlen30(p->sGraph.zPrefix);
+ char *z;
+ for(pRow = eqp_next_row(p, iEqpId, 0); pRow; pRow = pNext){
+ pNext = eqp_next_row(p, iEqpId, pRow);
+ z = pRow->zText;
+ utf8_printf(p->out, "%s%s%s\n", p->sGraph.zPrefix, pNext ? "|--" : "`--", z);
+ if( n<(int)sizeof(p->sGraph.zPrefix)-7 ){
+ memcpy(&p->sGraph.zPrefix[n], pNext ? "| " : " ", 4);
+ eqp_render_level(p, pRow->iEqpId);
+ p->sGraph.zPrefix[n] = 0;
+ }
+ }
+}
+
+/*
+** Display and reset the EXPLAIN QUERY PLAN data
+*/
+static void eqp_render(ShellState *p){
+ EQPGraphRow *pRow = p->sGraph.pRow;
+ if( pRow ){
+ if( pRow->zText[0]=='-' ){
+ if( pRow->pNext==0 ){
+ eqp_reset(p);
+ return;
+ }
+ utf8_printf(p->out, "%s\n", pRow->zText+3);
+ p->sGraph.pRow = pRow->pNext;
+ sqlite3_free(pRow);
+ }else{
+ utf8_printf(p->out, "QUERY PLAN\n");
+ }
+ p->sGraph.zPrefix[0] = 0;
+ eqp_render_level(p, 0);
+ eqp_reset(p);
+ }
+}
+
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+/*
+** Progress handler callback.
+*/
+static int progress_handler(void *pClientData) {
+ ShellState *p = (ShellState*)pClientData;
+ p->nProgress++;
+ if( p->nProgress>=p->mxProgress && p->mxProgress>0 ){
+ raw_printf(p->out, "Progress limit reached (%u)\n", p->nProgress);
+ if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0;
+ if( p->flgProgress & SHELL_PROGRESS_ONCE ) p->mxProgress = 0;
+ return 1;
+ }
+ if( (p->flgProgress & SHELL_PROGRESS_QUIET)==0 ){
+ raw_printf(p->out, "Progress %u\n", p->nProgress);
+ }
+ return 0;
+}
+#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
+
+/*
+** This is the callback routine that the shell
+** invokes for each row of a query result.
+*/
+static int shell_callback(
+ void *pArg,
+ int nArg, /* Number of result columns */
+ char **azArg, /* Text of each result column */
+ char **azCol, /* Column names */
+ int *aiType /* Column types */
+){
+ int i;
+ ShellState *p = (ShellState*)pArg;
+
+ if( azArg==0 ) return 0;
+ switch( p->cMode ){
+ case MODE_Line: {
+ int w = 5;
+ if( azArg==0 ) break;
+ for(i=0; i<nArg; i++){
+ int len = strlen30(azCol[i] ? azCol[i] : "");
+ if( len>w ) w = len;
+ }
+ if( p->cnt++>0 ) utf8_printf(p->out, "%s", p->rowSeparator);
+ for(i=0; i<nArg; i++){
+ utf8_printf(p->out,"%*s = %s%s", w, azCol[i],
+ azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator);
+ }
+ break;
+ }
+ case MODE_Explain:
+ case MODE_Column: {
+ static const int aExplainWidths[] = {4, 13, 4, 4, 4, 13, 2, 13};
+ const int *colWidth;
+ int showHdr;
+ char *rowSep;
+ if( p->cMode==MODE_Column ){
+ colWidth = p->colWidth;
+ showHdr = p->showHeader;
+ rowSep = p->rowSeparator;
+ }else{
+ colWidth = aExplainWidths;
+ showHdr = 1;
+ rowSep = SEP_Row;
+ }
+ if( p->cnt++==0 ){
+ for(i=0; i<nArg; i++){
+ int w, n;
+ if( i<ArraySize(p->colWidth) ){
+ w = colWidth[i];
+ }else{
+ w = 0;
+ }
+ if( w==0 ){
+ w = strlenChar(azCol[i] ? azCol[i] : "");
+ if( w<10 ) w = 10;
+ n = strlenChar(azArg && azArg[i] ? azArg[i] : p->nullValue);
+ if( w<n ) w = n;
+ }
+ if( i<ArraySize(p->actualWidth) ){
+ p->actualWidth[i] = w;
+ }
+ if( showHdr ){
+ utf8_width_print(p->out, w, azCol[i]);
+ utf8_printf(p->out, "%s", i==nArg-1 ? rowSep : " ");
+ }
+ }
+ if( showHdr ){
+ for(i=0; i<nArg; i++){
+ int w;
+ if( i<ArraySize(p->actualWidth) ){
+ w = p->actualWidth[i];
+ if( w<0 ) w = -w;
+ }else{
+ w = 10;
+ }
+ utf8_printf(p->out,"%-*.*s%s",w,w,
+ "----------------------------------------------------------"
+ "----------------------------------------------------------",
+ i==nArg-1 ? rowSep : " ");
+ }
+ }
+ }
+ if( azArg==0 ) break;
+ for(i=0; i<nArg; i++){
+ int w;
+ if( i<ArraySize(p->actualWidth) ){
+ w = p->actualWidth[i];
+ }else{
+ w = 10;
+ }
+ if( p->cMode==MODE_Explain && azArg[i] && strlenChar(azArg[i])>w ){
+ w = strlenChar(azArg[i]);
+ }
+ if( i==1 && p->aiIndent && p->pStmt ){
+ if( p->iIndent<p->nIndent ){
+ utf8_printf(p->out, "%*.s", p->aiIndent[p->iIndent], "");
+ }
+ p->iIndent++;
+ }
+ utf8_width_print(p->out, w, azArg[i] ? azArg[i] : p->nullValue);
+ utf8_printf(p->out, "%s", i==nArg-1 ? rowSep : " ");
+ }
+ break;
+ }
+ case MODE_Semi: { /* .schema and .fullschema output */
+ printSchemaLine(p->out, azArg[0], ";\n");
+ break;
+ }
+ case MODE_Pretty: { /* .schema and .fullschema with --indent */
+ char *z;
+ int j;
+ int nParen = 0;
+ char cEnd = 0;
+ char c;
+ int nLine = 0;
+ assert( nArg==1 );
+ if( azArg[0]==0 ) break;
+ if( sqlite3_strlike("CREATE VIEW%", azArg[0], 0)==0
+ || sqlite3_strlike("CREATE TRIG%", azArg[0], 0)==0
+ ){
+ utf8_printf(p->out, "%s;\n", azArg[0]);
+ break;
+ }
+ z = sqlite3_mprintf("%s", azArg[0]);
+ j = 0;
+ for(i=0; IsSpace(z[i]); i++){}
+ for(; (c = z[i])!=0; i++){
+ if( IsSpace(c) ){
+ if( z[j-1]=='\r' ) z[j-1] = '\n';
+ if( IsSpace(z[j-1]) || z[j-1]=='(' ) continue;
+ }else if( (c=='(' || c==')') && j>0 && IsSpace(z[j-1]) ){
+ j--;
+ }
+ z[j++] = c;
+ }
+ while( j>0 && IsSpace(z[j-1]) ){ j--; }
+ z[j] = 0;
+ if( strlen30(z)>=79 ){
+ for(i=j=0; (c = z[i])!=0; i++){ /* Copy changes from z[i] back to z[j] */
+ if( c==cEnd ){
+ cEnd = 0;
+ }else if( c=='"' || c=='\'' || c=='`' ){
+ cEnd = c;
+ }else if( c=='[' ){
+ cEnd = ']';
+ }else if( c=='-' && z[i+1]=='-' ){
+ cEnd = '\n';
+ }else if( c=='(' ){
+ nParen++;
+ }else if( c==')' ){
+ nParen--;
+ if( nLine>0 && nParen==0 && j>0 ){
+ printSchemaLineN(p->out, z, j, "\n");
+ j = 0;
+ }
+ }
+ z[j++] = c;
+ if( nParen==1 && cEnd==0
+ && (c=='(' || c=='\n' || (c==',' && !wsToEol(z+i+1)))
+ ){
+ if( c=='\n' ) j--;
+ printSchemaLineN(p->out, z, j, "\n ");
+ j = 0;
+ nLine++;
+ while( IsSpace(z[i+1]) ){ i++; }
+ }
+ }
+ z[j] = 0;
+ }
+ printSchemaLine(p->out, z, ";\n");
+ sqlite3_free(z);
+ break;
+ }
+ case MODE_List: {
+ if( p->cnt++==0 && p->showHeader ){
+ for(i=0; i<nArg; i++){
+ utf8_printf(p->out,"%s%s",azCol[i],
+ i==nArg-1 ? p->rowSeparator : p->colSeparator);
+ }
+ }
+ if( azArg==0 ) break;
+ for(i=0; i<nArg; i++){
+ char *z = azArg[i];
+ if( z==0 ) z = p->nullValue;
+ utf8_printf(p->out, "%s", z);
+ if( i<nArg-1 ){
+ utf8_printf(p->out, "%s", p->colSeparator);
+ }else{
+ utf8_printf(p->out, "%s", p->rowSeparator);
+ }
+ }
+ break;
+ }
+ case MODE_Html: {
+ if( p->cnt++==0 && p->showHeader ){
+ raw_printf(p->out,"<TR>");
+ for(i=0; i<nArg; i++){
+ raw_printf(p->out,"<TH>");
+ output_html_string(p->out, azCol[i]);
+ raw_printf(p->out,"</TH>\n");
+ }
+ raw_printf(p->out,"</TR>\n");
+ }
+ if( azArg==0 ) break;
+ raw_printf(p->out,"<TR>");
+ for(i=0; i<nArg; i++){
+ raw_printf(p->out,"<TD>");
+ output_html_string(p->out, azArg[i] ? azArg[i] : p->nullValue);
+ raw_printf(p->out,"</TD>\n");
+ }
+ raw_printf(p->out,"</TR>\n");
+ break;
+ }
+ case MODE_Tcl: {
+ if( p->cnt++==0 && p->showHeader ){
+ for(i=0; i<nArg; i++){
+ output_c_string(p->out,azCol[i] ? azCol[i] : "");
+ if(i<nArg-1) utf8_printf(p->out, "%s", p->colSeparator);
+ }
+ utf8_printf(p->out, "%s", p->rowSeparator);
+ }
+ if( azArg==0 ) break;
+ for(i=0; i<nArg; i++){
+ output_c_string(p->out, azArg[i] ? azArg[i] : p->nullValue);
+ if(i<nArg-1) utf8_printf(p->out, "%s", p->colSeparator);
+ }
+ utf8_printf(p->out, "%s", p->rowSeparator);
+ break;
+ }
+ case MODE_Csv: {
+ setBinaryMode(p->out, 1);
+ if( p->cnt++==0 && p->showHeader ){
+ for(i=0; i<nArg; i++){
+ output_csv(p, azCol[i] ? azCol[i] : "", i<nArg-1);
+ }
+ utf8_printf(p->out, "%s", p->rowSeparator);
+ }
+ if( nArg>0 ){
+ for(i=0; i<nArg; i++){
+ output_csv(p, azArg[i], i<nArg-1);
+ }
+ utf8_printf(p->out, "%s", p->rowSeparator);
+ }
+ setTextMode(p->out, 1);
+ break;
+ }
+ case MODE_Insert: {
+ if( azArg==0 ) break;
+ utf8_printf(p->out,"INSERT INTO %s",p->zDestTable);
+ if( p->showHeader ){
+ raw_printf(p->out,"(");
+ for(i=0; i<nArg; i++){
+ if( i>0 ) raw_printf(p->out, ",");
+ if( quoteChar(azCol[i]) ){
+ char *z = sqlite3_mprintf("\"%w\"", azCol[i]);
+ utf8_printf(p->out, "%s", z);
+ sqlite3_free(z);
+ }else{
+ raw_printf(p->out, "%s", azCol[i]);
+ }
+ }
+ raw_printf(p->out,")");
+ }
+ p->cnt++;
+ for(i=0; i<nArg; i++){
+ raw_printf(p->out, i>0 ? "," : " VALUES(");
+ if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
+ utf8_printf(p->out,"NULL");
+ }else if( aiType && aiType[i]==SQLITE_TEXT ){
+ if( ShellHasFlag(p, SHFLG_Newlines) ){
+ output_quoted_string(p->out, azArg[i]);
+ }else{
+ output_quoted_escaped_string(p->out, azArg[i]);
+ }
+ }else if( aiType && aiType[i]==SQLITE_INTEGER ){
+ utf8_printf(p->out,"%s", azArg[i]);
+ }else if( aiType && aiType[i]==SQLITE_FLOAT ){
+ char z[50];
+ double r = sqlite3_column_double(p->pStmt, i);
+ sqlite3_uint64 ur;
+ memcpy(&ur,&r,sizeof(r));
+ if( ur==0x7ff0000000000000LL ){
+ raw_printf(p->out, "1e999");
+ }else if( ur==0xfff0000000000000LL ){
+ raw_printf(p->out, "-1e999");
+ }else{
+ sqlite3_snprintf(50,z,"%!.20g", r);
+ raw_printf(p->out, "%s", z);
+ }
+ }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){
+ const void *pBlob = sqlite3_column_blob(p->pStmt, i);
+ int nBlob = sqlite3_column_bytes(p->pStmt, i);
+ output_hex_blob(p->out, pBlob, nBlob);
+ }else if( isNumber(azArg[i], 0) ){
+ utf8_printf(p->out,"%s", azArg[i]);
+ }else if( ShellHasFlag(p, SHFLG_Newlines) ){
+ output_quoted_string(p->out, azArg[i]);
+ }else{
+ output_quoted_escaped_string(p->out, azArg[i]);
+ }
+ }
+ raw_printf(p->out,");\n");
+ break;
+ }
+ case MODE_Quote: {
+ if( azArg==0 ) break;
+ if( p->cnt==0 && p->showHeader ){
+ for(i=0; i<nArg; i++){
+ if( i>0 ) raw_printf(p->out, ",");
+ output_quoted_string(p->out, azCol[i]);
+ }
+ raw_printf(p->out,"\n");
+ }
+ p->cnt++;
+ for(i=0; i<nArg; i++){
+ if( i>0 ) raw_printf(p->out, ",");
+ if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
+ utf8_printf(p->out,"NULL");
+ }else if( aiType && aiType[i]==SQLITE_TEXT ){
+ output_quoted_string(p->out, azArg[i]);
+ }else if( aiType && aiType[i]==SQLITE_INTEGER ){
+ utf8_printf(p->out,"%s", azArg[i]);
+ }else if( aiType && aiType[i]==SQLITE_FLOAT ){
+ char z[50];
+ double r = sqlite3_column_double(p->pStmt, i);
+ sqlite3_snprintf(50,z,"%!.20g", r);
+ raw_printf(p->out, "%s", z);
+ }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){
+ const void *pBlob = sqlite3_column_blob(p->pStmt, i);
+ int nBlob = sqlite3_column_bytes(p->pStmt, i);
+ output_hex_blob(p->out, pBlob, nBlob);
+ }else if( isNumber(azArg[i], 0) ){
+ utf8_printf(p->out,"%s", azArg[i]);
+ }else{
+ output_quoted_string(p->out, azArg[i]);
+ }
+ }
+ raw_printf(p->out,"\n");
+ break;
+ }
+ case MODE_Ascii: {
+ if( p->cnt++==0 && p->showHeader ){
+ for(i=0; i<nArg; i++){
+ if( i>0 ) utf8_printf(p->out, "%s", p->colSeparator);
+ utf8_printf(p->out,"%s",azCol[i] ? azCol[i] : "");
+ }
+ utf8_printf(p->out, "%s", p->rowSeparator);
+ }
+ if( azArg==0 ) break;
+ for(i=0; i<nArg; i++){
+ if( i>0 ) utf8_printf(p->out, "%s", p->colSeparator);
+ utf8_printf(p->out,"%s",azArg[i] ? azArg[i] : p->nullValue);
+ }
+ utf8_printf(p->out, "%s", p->rowSeparator);
+ break;
+ }
+ case MODE_EQP: {
+ eqp_append(p, atoi(azArg[0]), atoi(azArg[1]), azArg[3]);
+ break;
+ }
+ }
+ return 0;
+}
+
+/*
+** This is the callback routine that the SQLite library
+** invokes for each row of a query result.
+*/
+static int callback(void *pArg, int nArg, char **azArg, char **azCol){
+ /* since we don't have type info, call the shell_callback with a NULL value */
+ return shell_callback(pArg, nArg, azArg, azCol, NULL);
+}
+
+/*
+** This is the callback routine from sqlite3_exec() that appends all
+** output onto the end of a ShellText object.
+*/
+static int captureOutputCallback(void *pArg, int nArg, char **azArg, char **az){
+ ShellText *p = (ShellText*)pArg;
+ int i;
+ UNUSED_PARAMETER(az);
+ if( azArg==0 ) return 0;
+ if( p->n ) appendText(p, "|", 0);
+ for(i=0; i<nArg; i++){
+ if( i ) appendText(p, ",", 0);
+ if( azArg[i] ) appendText(p, azArg[i], 0);
+ }
+ return 0;
+}
+
+/*
+** Generate an appropriate SELFTEST table in the main database.
+*/
+static void createSelftestTable(ShellState *p){
+ char *zErrMsg = 0;
+ sqlite3_exec(p->db,
+ "SAVEPOINT selftest_init;\n"
+ "CREATE TABLE IF NOT EXISTS selftest(\n"
+ " tno INTEGER PRIMARY KEY,\n" /* Test number */
+ " op TEXT,\n" /* Operator: memo run */
+ " cmd TEXT,\n" /* Command text */
+ " ans TEXT\n" /* Desired answer */
+ ");"
+ "CREATE TEMP TABLE [_shell$self](op,cmd,ans);\n"
+ "INSERT INTO [_shell$self](rowid,op,cmd)\n"
+ " VALUES(coalesce((SELECT (max(tno)+100)/10 FROM selftest),10),\n"
+ " 'memo','Tests generated by --init');\n"
+ "INSERT INTO [_shell$self]\n"
+ " SELECT 'run',\n"
+ " 'SELECT hex(sha3_query(''SELECT type,name,tbl_name,sql "
+ "FROM sqlite_master ORDER BY 2'',224))',\n"
+ " hex(sha3_query('SELECT type,name,tbl_name,sql "
+ "FROM sqlite_master ORDER BY 2',224));\n"
+ "INSERT INTO [_shell$self]\n"
+ " SELECT 'run',"
+ " 'SELECT hex(sha3_query(''SELECT * FROM \"' ||"
+ " printf('%w',name) || '\" NOT INDEXED'',224))',\n"
+ " hex(sha3_query(printf('SELECT * FROM \"%w\" NOT INDEXED',name),224))\n"
+ " FROM (\n"
+ " SELECT name FROM sqlite_master\n"
+ " WHERE type='table'\n"
+ " AND name<>'selftest'\n"
+ " AND coalesce(rootpage,0)>0\n"
+ " )\n"
+ " ORDER BY name;\n"
+ "INSERT INTO [_shell$self]\n"
+ " VALUES('run','PRAGMA integrity_check','ok');\n"
+ "INSERT INTO selftest(tno,op,cmd,ans)"
+ " SELECT rowid*10,op,cmd,ans FROM [_shell$self];\n"
+ "DROP TABLE [_shell$self];"
+ ,0,0,&zErrMsg);
+ if( zErrMsg ){
+ utf8_printf(stderr, "SELFTEST initialization failure: %s\n", zErrMsg);
+ sqlite3_free(zErrMsg);
+ }
+ sqlite3_exec(p->db, "RELEASE selftest_init",0,0,0);
+}
+
+
+/*
+** Set the destination table field of the ShellState structure to
+** the name of the table given. Escape any quote characters in the
+** table name.
+*/
+static void set_table_name(ShellState *p, const char *zName){
+ int i, n;
+ char cQuote;
+ char *z;
+
+ if( p->zDestTable ){
+ free(p->zDestTable);
+ p->zDestTable = 0;
+ }
+ if( zName==0 ) return;
+ cQuote = quoteChar(zName);
+ n = strlen30(zName);
+ if( cQuote ) n += n+2;
+ z = p->zDestTable = malloc( n+1 );
+ if( z==0 ) shell_out_of_memory();
+ n = 0;
+ if( cQuote ) z[n++] = cQuote;
+ for(i=0; zName[i]; i++){
+ z[n++] = zName[i];
+ if( zName[i]==cQuote ) z[n++] = cQuote;
+ }
+ if( cQuote ) z[n++] = cQuote;
+ z[n] = 0;
+}
+
+
+/*
+** Execute a query statement that will generate SQL output. Print
+** the result columns, comma-separated, on a line and then add a
+** semicolon terminator to the end of that line.
+**
+** If the number of columns is 1 and that column contains text "--"
+** then write the semicolon on a separate line. That way, if a
+** "--" comment occurs at the end of the statement, the comment
+** won't consume the semicolon terminator.
+*/
+static int run_table_dump_query(
+ ShellState *p, /* Query context */
+ const char *zSelect, /* SELECT statement to extract content */
+ const char *zFirstRow /* Print before first row, if not NULL */
+){
+ sqlite3_stmt *pSelect;
+ int rc;
+ int nResult;
+ int i;
+ const char *z;
+ rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0);
+ if( rc!=SQLITE_OK || !pSelect ){
+ utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n", rc,
+ sqlite3_errmsg(p->db));
+ if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++;
+ return rc;
+ }
+ rc = sqlite3_step(pSelect);
+ nResult = sqlite3_column_count(pSelect);
+ while( rc==SQLITE_ROW ){
+ if( zFirstRow ){
+ utf8_printf(p->out, "%s", zFirstRow);
+ zFirstRow = 0;
+ }
+ z = (const char*)sqlite3_column_text(pSelect, 0);
+ utf8_printf(p->out, "%s", z);
+ for(i=1; i<nResult; i++){
+ utf8_printf(p->out, ",%s", sqlite3_column_text(pSelect, i));
+ }
+ if( z==0 ) z = "";
+ while( z[0] && (z[0]!='-' || z[1]!='-') ) z++;
+ if( z[0] ){
+ raw_printf(p->out, "\n;\n");
+ }else{
+ raw_printf(p->out, ";\n");
+ }
+ rc = sqlite3_step(pSelect);
+ }
+ rc = sqlite3_finalize(pSelect);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n", rc,
+ sqlite3_errmsg(p->db));
+ if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++;
+ }
+ return rc;
+}
+
+/*
+** Allocate space and save off current error string.
+*/
+static char *save_err_msg(
+ sqlite3 *db /* Database to query */
+){
+ int nErrMsg = 1+strlen30(sqlite3_errmsg(db));
+ char *zErrMsg = sqlite3_malloc64(nErrMsg);
+ if( zErrMsg ){
+ memcpy(zErrMsg, sqlite3_errmsg(db), nErrMsg);
+ }
+ return zErrMsg;
+}
+
+#ifdef __linux__
+/*
+** Attempt to display I/O stats on Linux using /proc/PID/io
+*/
+static void displayLinuxIoStats(FILE *out){
+ FILE *in;
+ char z[200];
+ sqlite3_snprintf(sizeof(z), z, "/proc/%d/io", getpid());
+ in = fopen(z, "rb");
+ if( in==0 ) return;
+ while( fgets(z, sizeof(z), in)!=0 ){
+ static const struct {
+ const char *zPattern;
+ const char *zDesc;
+ } aTrans[] = {
+ { "rchar: ", "Bytes received by read():" },
+ { "wchar: ", "Bytes sent to write():" },
+ { "syscr: ", "Read() system calls:" },
+ { "syscw: ", "Write() system calls:" },
+ { "read_bytes: ", "Bytes read from storage:" },
+ { "write_bytes: ", "Bytes written to storage:" },
+ { "cancelled_write_bytes: ", "Cancelled write bytes:" },
+ };
+ int i;
+ for(i=0; i<ArraySize(aTrans); i++){
+ int n = strlen30(aTrans[i].zPattern);
+ if( strncmp(aTrans[i].zPattern, z, n)==0 ){
+ utf8_printf(out, "%-36s %s", aTrans[i].zDesc, &z[n]);
+ break;
+ }
+ }
+ }
+ fclose(in);
+}
+#endif
+
+/*
+** Display a single line of status using 64-bit values.
+*/
+static void displayStatLine(
+ ShellState *p, /* The shell context */
+ char *zLabel, /* Label for this one line */
+ char *zFormat, /* Format for the result */
+ int iStatusCtrl, /* Which status to display */
+ int bReset /* True to reset the stats */
+){
+ sqlite3_int64 iCur = -1;
+ sqlite3_int64 iHiwtr = -1;
+ int i, nPercent;
+ char zLine[200];
+ sqlite3_status64(iStatusCtrl, &iCur, &iHiwtr, bReset);
+ for(i=0, nPercent=0; zFormat[i]; i++){
+ if( zFormat[i]=='%' ) nPercent++;
+ }
+ if( nPercent>1 ){
+ sqlite3_snprintf(sizeof(zLine), zLine, zFormat, iCur, iHiwtr);
+ }else{
+ sqlite3_snprintf(sizeof(zLine), zLine, zFormat, iHiwtr);
+ }
+ raw_printf(p->out, "%-36s %s\n", zLabel, zLine);
+}
+
+/*
+** Display memory stats.
+*/
+static int display_stats(
+ sqlite3 *db, /* Database to query */
+ ShellState *pArg, /* Pointer to ShellState */
+ int bReset /* True to reset the stats */
+){
+ int iCur;
+ int iHiwtr;
+ FILE *out;
+ if( pArg==0 || pArg->out==0 ) return 0;
+ out = pArg->out;
+
+ if( pArg->pStmt && (pArg->statsOn & 2) ){
+ int nCol, i, x;
+ sqlite3_stmt *pStmt = pArg->pStmt;
+ char z[100];
+ nCol = sqlite3_column_count(pStmt);
+ raw_printf(out, "%-36s %d\n", "Number of output columns:", nCol);
+ for(i=0; i<nCol; i++){
+ sqlite3_snprintf(sizeof(z),z,"Column %d %nname:", i, &x);
+ utf8_printf(out, "%-36s %s\n", z, sqlite3_column_name(pStmt,i));
+#ifndef SQLITE_OMIT_DECLTYPE
+ sqlite3_snprintf(30, z+x, "declared type:");
+ utf8_printf(out, "%-36s %s\n", z, sqlite3_column_decltype(pStmt, i));
+#endif
+#ifdef SQLITE_ENABLE_COLUMN_METADATA
+ sqlite3_snprintf(30, z+x, "database name:");
+ utf8_printf(out, "%-36s %s\n", z, sqlite3_column_database_name(pStmt,i));
+ sqlite3_snprintf(30, z+x, "table name:");
+ utf8_printf(out, "%-36s %s\n", z, sqlite3_column_table_name(pStmt,i));
+ sqlite3_snprintf(30, z+x, "origin name:");
+ utf8_printf(out, "%-36s %s\n", z, sqlite3_column_origin_name(pStmt,i));
+#endif