/* ** 2006 June 10 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** Code for testing the virtual table interfaces. This code ** is not included in the SQLite library. It is used for automated ** testing of the SQLite library. ** ** $Id: test8.c,v 1.23 2006/06/15 15:38:42 danielk1977 Exp $ */ #include "sqliteInt.h" #include "tcl.h" #include "os.h" #include #include typedef struct echo_vtab echo_vtab; typedef struct echo_cursor echo_cursor; /* ** An echo virtual-table object. ** ** echo.vtab.aIndex is an array of booleans. The nth entry is true if ** the nth column of the real table is the left-most column of an index ** (implicit or otherwise). In other words, if SQLite can optimize ** a query like "SELECT * FROM real_table WHERE col = ?". ** ** Member variable contains copies of the column names of the real table. */ struct echo_vtab { sqlite3_vtab base; Tcl_Interp *interp; sqlite3 *db; char *zTableName; /* Name of the real table */ int nCol; /* Number of columns in the real table */ int *aIndex; /* Array of size nCol. True if column has an index */ char **aCol; /* Array of size nCol. Column names */ }; /* An echo cursor object */ struct echo_cursor { sqlite3_vtab_cursor base; sqlite3_stmt *pStmt; int errcode; /* Error code */ }; static int getColumnNames( sqlite3 *db, const char *zTab, char ***paCol, int *pnCol ){ char **aCol = 0; char zBuf[1024]; sqlite3_stmt *pStmt = 0; int rc = SQLITE_OK; int nCol; sprintf(zBuf, "SELECT * FROM %s", zTab); rc = sqlite3_prepare(db, zBuf, -1, &pStmt, 0); if( rc==SQLITE_OK ){ int ii; nCol = sqlite3_column_count(pStmt); aCol = sqliteMalloc(sizeof(char *) * nCol); if( !aCol ){ rc = SQLITE_NOMEM; goto fail; } for(ii=0; ii=0 && cidaIndex); } if( rc==SQLITE_OK ){ rc = getColumnNames(db, argv[1], &pVtab->aCol, &pVtab->nCol); } } return rc; } static int echoDestructor(sqlite3_vtab *pVtab){ int ii; echo_vtab *p = (echo_vtab*)pVtab; sqliteFree(p->aIndex); for(ii=0; iinCol; ii++){ sqliteFree(p->aCol[ii]); } sqliteFree(p->aCol); sqliteFree(p->zTableName); sqliteFree(p); return 0; } static int echoConstructor( sqlite3 *db, void *pAux, int argc, char **argv, sqlite3_vtab **ppVtab ){ int i; echo_vtab *pVtab; pVtab = sqliteMalloc( sizeof(*pVtab) ); pVtab->interp = (Tcl_Interp *)pAux; pVtab->db = db; pVtab->zTableName = sqlite3MPrintf("%s", argv[1]); for(i=0; iinterp, argv[i]); } if( echoDeclareVtab(pVtab, db, argc, argv) ){ echoDestructor((sqlite3_vtab *)pVtab); return SQLITE_ERROR; } *ppVtab = &pVtab->base; return SQLITE_OK; } /* Methods for the echo module */ static int echoCreate( sqlite3 *db, void *pAux, int argc, char **argv, sqlite3_vtab **ppVtab ){ appendToEchoModule((Tcl_Interp *)(pAux), "xCreate"); return echoConstructor(db, pAux, argc, argv, ppVtab); } static int echoConnect( sqlite3 *db, void *pAux, int argc, char **argv, sqlite3_vtab **ppVtab ){ appendToEchoModule((Tcl_Interp *)(pAux), "xConnect"); return echoConstructor(db, pAux, argc, argv, ppVtab); } static int echoDisconnect(sqlite3_vtab *pVtab){ appendToEchoModule(((echo_vtab *)pVtab)->interp, "xDisconnect"); return echoDestructor(pVtab); } static int echoDestroy(sqlite3_vtab *pVtab){ appendToEchoModule(((echo_vtab *)pVtab)->interp, "xDestroy"); return echoDestructor(pVtab); } static int echoOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ echo_cursor *pCur; pCur = sqliteMalloc(sizeof(echo_cursor)); *ppCursor = (sqlite3_vtab_cursor *)pCur; return SQLITE_OK; } static int echoClose(sqlite3_vtab_cursor *cur){ echo_cursor *pCur = (echo_cursor *)cur; sqlite3_finalize(pCur->pStmt); sqliteFree(pCur); return SQLITE_OK; } static int echoNext(sqlite3_vtab_cursor *cur){ int rc; echo_cursor *pCur = (echo_cursor *)cur; rc = sqlite3_step(pCur->pStmt); if( rc==SQLITE_ROW ){ rc = 1; } else { pCur->errcode = sqlite3_finalize(pCur->pStmt); pCur->pStmt = 0; rc = 0; } return rc; } static int echoColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ int iCol = i + 1; sqlite3_stmt *pStmt = ((echo_cursor *)cur)->pStmt; assert( sqlite3_data_count(pStmt)>iCol ); sqlite3_result_value(ctx, sqlite3_column_value(pStmt, iCol)); return SQLITE_OK; } static int echoRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ sqlite3_stmt *pStmt = ((echo_cursor *)cur)->pStmt; *pRowid = sqlite3_column_int64(pStmt, 0); return SQLITE_OK; } /* ** Compute a simple hash of the null terminated string zString. ** ** This module uses only sqlite3_index_info.idxStr, not ** sqlite3_index_info.idxNum. So to test idxNum, when idxStr is set ** in echoBestIndex(), idxNum is set to the corresponding hash value. ** In echoFilter(), code assert()s that the supplied idxNum value is ** indeed the hash of the supplied idxStr. */ static int hashString(const char *zString){ int val = 0; int ii; for(ii=0; zString[ii]; ii++){ val = (val << 3) + (int)zString[ii]; } return val; } static int echoFilter( sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ int rc; int i; echo_cursor *pCur = (echo_cursor *)pVtabCursor; echo_vtab *pVtab = (echo_vtab *)pVtabCursor->pVtab; sqlite3 *db = pVtab->db; assert( idxNum==hashString(idxStr) ); sqlite3_finalize(pCur->pStmt); pCur->pStmt = 0; rc = sqlite3_prepare(db, idxStr, -1, &pCur->pStmt, 0); for(i=0; ipStmt, i+1, sqlite3_value_int64(argv[i])); break; } case SQLITE_FLOAT: { sqlite3_bind_double(pCur->pStmt, i+1, sqlite3_value_double(argv[i])); break; } case SQLITE_NULL: { sqlite3_bind_null(pCur->pStmt, i+1); break; } case SQLITE_TEXT: { sqlite3_bind_text(pCur->pStmt, i+1, sqlite3_value_text(argv[i]), sqlite3_value_bytes(argv[i]), SQLITE_TRANSIENT); break; } case SQLITE_BLOB: { sqlite3_bind_blob(pCur->pStmt, i+1, sqlite3_value_blob(argv[i]), sqlite3_value_bytes(argv[i]), SQLITE_TRANSIENT); break; } } } if( rc==SQLITE_OK ){ rc = echoNext(pVtabCursor); } appendToEchoModule(pVtab->interp, "xFilter"); appendToEchoModule(pVtab->interp, idxStr); for(i=0; iinterp, sqlite3_value_text(argv[i])); } return rc; } /* ** The echo module implements the subset of query constraints and sort ** orders that may take advantage of SQLite indices on the underlying ** real table. For example, if the real table is declared as: ** ** CREATE TABLE real(a, b, c); ** CREATE INDEX real_index ON real(b); ** ** then the echo module handles WHERE or ORDER BY clauses that refer ** to the column "b", but not "a" or "c". If a multi-column index is ** present, only it's left most column is considered. */ static int echoBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int ii; char *zQuery = 0; char *zNew; int nArg = 0; const char *zSep = "WHERE"; echo_vtab *pVtab = (echo_vtab *)tab; zQuery = sqlite3_mprintf("SELECT rowid, * FROM %Q", pVtab->zTableName); for(ii=0; iinConstraint; ii++){ const struct sqlite3_index_constraint *pConstraint; struct sqlite3_index_constraint_usage *pUsage; pConstraint = &pIdxInfo->aConstraint[ii]; pUsage = &pIdxInfo->aConstraintUsage[ii]; int iCol = pConstraint->iColumn; if( pVtab->aIndex[iCol] ){ char *zCol = pVtab->aCol[iCol]; char *zOp = 0; if( iCol<0 ){ zCol = "rowid"; } switch( pConstraint->op ){ case SQLITE_INDEX_CONSTRAINT_EQ: zOp = "="; break; case SQLITE_INDEX_CONSTRAINT_LT: zOp = "<"; break; case SQLITE_INDEX_CONSTRAINT_GT: zOp = ">"; break; case SQLITE_INDEX_CONSTRAINT_LE: zOp = "<="; break; case SQLITE_INDEX_CONSTRAINT_GE: zOp = ">="; break; case SQLITE_INDEX_CONSTRAINT_MATCH: zOp = "LIKE"; break; } if( zOp[0]=='L' ){ zNew = sqlite3_mprintf("%s %s %s LIKE (SELECT '%%'||?||'%%')", zQuery, zSep, zCol); } else { zNew = sqlite3_mprintf("%s %s %s %s ?", zQuery, zSep, zCol, zOp); } sqlite3_free(zQuery); zQuery = zNew; zSep = "AND"; pUsage->argvIndex = ++nArg; pUsage->omit = 1; } } /* If there is only one term in the ORDER BY clause, and it is ** on a column that this virtual table has an index for, then consume ** the ORDER BY clause. */ if( pIdxInfo->nOrderBy==1 && pVtab->aIndex[pIdxInfo->aOrderBy->iColumn] ){ char *zCol = pVtab->aCol[pIdxInfo->aOrderBy->iColumn]; char *zDir = pIdxInfo->aOrderBy->desc?"DESC":"ASC"; zNew = sqlite3_mprintf("%s ORDER BY %s %s", zQuery, zCol, zDir); sqlite3_free(zQuery); zQuery = zNew; pIdxInfo->orderByConsumed = 1; } appendToEchoModule(pVtab->interp, "xBestIndex");; appendToEchoModule(pVtab->interp, zQuery); pIdxInfo->idxNum = hashString(zQuery); pIdxInfo->idxStr = zQuery; pIdxInfo->needToFreeIdxStr = 1; pIdxInfo->estimatedCost = 1.0; return SQLITE_OK; } static void string_concat(char **pzStr, char *zAppend, int doFree){ char *zIn = *pzStr; if( zIn ){ char *zTemp = zIn; zIn = sqlite3_mprintf("%s%s", zIn, zAppend); sqlite3_free(zTemp); }else{ zIn = sqlite3_mprintf("%s", zAppend); } *pzStr = zIn; if( doFree ){ sqlite3_free(zAppend); } } /* ** apData[0] apData[1] apData[2..] ** ** INTEGER DELETE ** ** INTEGER NULL (nCol args) UPDATE (do not set rowid) ** INTEGER INTEGER (nCol args) UPDATE (with SET rowid = ) ** ** NULL NULL (nCol args) INSERT INTO (automatic rowid value) ** NULL INTEGER (nCol args) INSERT (incl. rowid value) ** */ int echoUpdate(sqlite3_vtab *tab, int nData, sqlite3_value **apData){ echo_vtab *pVtab = (echo_vtab *)tab; sqlite3 *db = pVtab->db; int rc = SQLITE_OK; sqlite3_stmt *pStmt; char *z = 0; /* SQL statement to execute */ int bindArgZero = 0; /* True to bind apData[0] to sql var no. nData */ int bindArgOne = 0; /* True to bind apData[1] to sql var no. 1 */ int i; /* Counter variable used by for loops */ assert( nData==pVtab->nCol+2 || nData==1 ); /* If apData[0] is an integer and nData>1 then do an UPDATE */ if( nData>1 && sqlite3_value_type(apData[0])==SQLITE_INTEGER ){ z = sqlite3_mprintf("UPDATE %Q", pVtab->zTableName); char *zSep = " SET"; bindArgOne = (apData[1] && sqlite3_value_type(apData[1])==SQLITE_INTEGER); bindArgZero = 1; if( bindArgOne ){ string_concat(&z, " SET rowid=?1 ", 0); zSep = ","; } for(i=2; iaCol[i-2], i), 1); zSep = ","; } string_concat(&z, sqlite3_mprintf(" WHERE rowid=?%d", nData), 0); } /* If apData[0] is an integer and nData==1 then do a DELETE */ else if( nData==1 && sqlite3_value_type(apData[0])==SQLITE_INTEGER ){ z = sqlite3_mprintf("DELETE FROM %Q WHERE rowid = ?1", pVtab->zTableName); bindArgZero = 1; } /* If the first argument is NULL and there are more than two args, INSERT */ else if( nData>2 && sqlite3_value_type(apData[0])==SQLITE_NULL ){ int ii; char *zInsert = 0; char *zValues = 0; zInsert = sqlite3_mprintf("INSERT OR REPLACE INTO %Q (", pVtab->zTableName); if( sqlite3_value_type(apData[1])==SQLITE_INTEGER ){ bindArgOne = 1; zValues = sqlite3_mprintf("?"); string_concat(&zInsert, "rowid", 0); } assert((pVtab->nCol+2)==nData); for(ii=2; iiaCol[ii-2]), 1); string_concat(&zValues, sqlite3_mprintf("%s?%d", zValues?", ":"", ii), 1); } string_concat(&z, zInsert, 1); string_concat(&z, ") VALUES(", 0); string_concat(&z, zValues, 1); string_concat(&z, ")", 0); } /* Anything else is an error */ else{ assert(0); return SQLITE_ERROR; } rc = sqlite3_prepare(db, z, -1, &pStmt, 0); assert( rc!=SQLITE_OK || pStmt ); sqlite3_free(z); if( rc==SQLITE_OK ) { if( bindArgZero ){ sqlite3_bind_value(pStmt, nData, apData[0]); } if( bindArgOne ){ sqlite3_bind_value(pStmt, 1, apData[1]); } for(i=2; i