using System; using System.Diagnostics; using System.Text; using sqlite_int64 = System.Int64; namespace Community.CsharpSqlite { #if TCLSH using tcl.lang; using sqlite3_stmt = Sqlite3.Vdbe; using sqlite3_value = Sqlite3.Mem; using Tcl_CmdInfo = tcl.lang.WrappedCommand; using Tcl_DString = tcl.lang.TclString; using Tcl_Interp = tcl.lang.Interp; using Tcl_Obj = tcl.lang.TclObject; using ClientData = System.Object; public partial class Sqlite3 { /* ** 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. ************************************************************************* ** Included in SQLite3 port to C#-SQLite; 2008 Noah B Hart ** C#-SQLite is an independent reimplementation of the SQLite software library ** ** SQLITE_SOURCE_ID: 2011-06-23 19:49:22 4374b7e83ea0a3fbc3691f9c0c936272862f32f2 ** ************************************************************************* */ //#include "sqliteInt.h" //#include "tcl.h" //#include //#include #if !SQLITE_OMIT_VIRTUALTABLE //typedef struct echo_vtab echo_vtab; //typedef struct echo_cursor echo_cursor; /* ** The test module defined in this file uses four global Tcl variables to ** commicate with test-scripts: ** ** $::echo_module ** $::echo_module_sync_fail ** $::echo_module_begin_fail ** $::echo_module_cost ** ** The variable ::echo_module is a list. Each time one of the following ** methods is called, one or more elements are appended to the list. ** This is used for automated testing of virtual table modules. ** ** The ::echo_module_sync_fail variable is set by test scripts and read ** by code in this file. If it is set to the name of a real table in the ** the database, then all xSync operations on echo virtual tables that ** use the named table as a backing store will fail. */ /* ** Errors can be provoked within the following echo virtual table methods: ** ** xBestIndex xOpen xFilter xNext ** xColumn xRowid xUpdate xSync ** xBegin xRename ** ** This is done by setting the global tcl variable: ** ** echo_module_fail($method,$tbl) ** ** where $method is set to the name of the virtual table method to fail ** (i.e. "xBestIndex") and $tbl is the name of the table being echoed (not ** the name of the virtual table, the name of the underlying real table). */ /* ** 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 aCol[] contains copies of the column names of the real ** table. */ class echo_vtab : sqlite3_vtab { //public sqlite3_vtab base; public Tcl_Interp interp; /* Tcl interpreter containing debug variables */ public sqlite3 db; /* Database connection */ public int isPattern; public int inTransaction; /* True if within a transaction */ public string zThis; /* Name of the echo table */ public string zTableName; /* Name of the real table */ public string zLogName; /* Name of the log table */ public int nCol; /* Number of columns in the real table */ public int[] aIndex; /* Array of size nCol. True if column has an index */ public string[] aCol; /* Array of size nCol. Column names */ }; /* An echo cursor object */ class echo_cursor : sqlite3_vtab_cursor { //public sqlite3_vtab_cursor base; public sqlite3_stmt pStmt; }; static int simulateVtabError( echo_vtab p, string zMethod ) { string zErr; StringBuilder zVarname = new StringBuilder( 128 ); //zVarname[127] = '\0'; sqlite3_snprintf( 127, zVarname, "echo_module_fail(%s,%s)", zMethod, p.zTableName ); zErr = TCL.Tcl_GetVar( p.interp, zVarname.ToString(), (TCL.VarFlag)TCL.TCL_GLOBAL_ONLY ).ToString(); if ( zErr != "" ) { p.zErrMsg = sqlite3_mprintf( "echo-vtab-error: %s", zErr ); } return ( zErr != "" ? 1 : 0 ); } /* ** Convert an SQL-style quoted string into a normal string by removing ** the quote characters. The conversion is done in-place. If the ** input does not begin with a quote character, then this routine ** is a no-op. ** ** Examples: ** ** "abc" becomes abc ** 'xyz' becomes xyz ** [pqr] becomes pqr ** `mno` becomes mno */ static void dequoteString( ref string z ) { //int quote; //int i, j; if ( String.IsNullOrEmpty( z ) ) return; sqlite3Dequote( ref z ); //quote = z[0]; //switch( quote ){ // case '\'': break; // case '"': break; // case '`': break; /* For MySQL compatibility */ // case '[': quote = ']'; break; /* For MS SqlServer compatibility */ // default: return; //} //for(i=1, j=0; z[i]; i++){ // if( z[i]==quote ){ // if( z[i+1]==quote ){ // z[j++] = quote; // i++; // }else{ // z[j++] = 0; // break; // } // }else{ // z[j++] = z[i]; // } //} } /* ** Retrieve the column names for the table named zTab via database ** connection db. SQLITE_OK is returned on success, or an sqlite error ** code otherwise. ** ** If successful, the number of columns is written to pnCol. paCol is ** set to point at sqlite3_malloc()'d space containing the array of ** nCol column names. The caller is responsible for calling sqlite3_free ** on paCol. */ static int getColumnNames( sqlite3 db, string zTab, out string[] paCol, out int pnCol ) { string[] aCol = null; string zSql; sqlite3_stmt pStmt = null; int rc = SQLITE_OK; int nCol = 0; /* Prepare the statement "SELECT * FROM ". The column names ** of the result set of the compiled SELECT will be the same as ** the column names of table . */ zSql = sqlite3_mprintf( "SELECT * FROM %Q", zTab ); //if( null==zSql ){ // rc = SQLITE_NOMEM; // goto _out; //} rc = sqlite3_prepare( db, zSql, -1, ref pStmt, 0 ); //sqlite3_free(zSql); if ( rc == SQLITE_OK ) { int ii; int nBytes; string zSpace; nCol = sqlite3_column_count( pStmt ); /* Figure out how much space to allocate for the array of column names ** (including space for the strings themselves). Then allocate it. */ nBytes = sizeof( char ) * nCol; for ( ii = 0; ii < nCol; ii++ ) { string zName = sqlite3_column_name( pStmt, ii ); //if( null==zName ){ // rc = SQLITE_NOMEM; // goto _out; //} nBytes += zName.Length + 1;//strlen( zName ) + 1; } aCol = new string[nCol];//(char )sqlite3MallocZero(nBytes); //if( null==aCol ){ // rc = SQLITE_NOMEM; // goto out_; //} /* Copy the column names into the allocated space and set up the ** pointers in the aCol[] array. */ //zSpace = (char)( &aCol[nCol] ); for ( ii = 0; ii < nCol; ii++ ) { //aCol[ii] = zSpace; //zSpace += sprintf( zSpace, "%s", sqlite3_column_name( pStmt, ii ) ); //zSpace++; aCol[ii] = sqlite3_column_name( pStmt, ii ); } //Debug.Assert( (zSpace-nBytes)==(char )aCol ); } paCol = aCol; pnCol = nCol; //_out: sqlite3_finalize( pStmt ); return rc; } /* ** Parameter zTab is the name of a table in database db with nCol ** columns. This function allocates an array of integers nCol in ** size and populates it according to any implicit or explicit ** indices on table zTab. ** ** If successful, SQLITE_OK is returned and paIndex set to point ** at the allocated array. Otherwise, an error code is returned. ** ** See comments associated with the member variable aIndex above ** "struct echo_vtab" for details of the contents of the array. */ static int getIndexArray( sqlite3 db, /* Database connection */ string zTab, /* Name of table in database db */ int nCol, ref int[] paIndex ) { sqlite3_stmt pStmt = null; int[] aIndex = null; int rc = 0; string zSql; /* Allocate space for the index array */ aIndex = new int[nCol];//(int )sqlite3MallocZero(sizeof(int) * nCol); // if( null==aIndex ){ // rc = SQLITE_NOMEM; // goto get_index_array_out; // } /* Compile an sqlite pragma to loop through all indices on table zTab */ zSql = sqlite3_mprintf( "PRAGMA index_list(%s)", zTab ); // if( null==zSql ){ // rc = SQLITE_NOMEM; // goto get_index_array_out; // } rc = sqlite3_prepare( db, zSql, -1, ref pStmt, 0 ); // //sqlite3_free(zSql); /* For each index, figure out the left-most column and set the ** corresponding entry in aIndex[] to 1. */ while ( pStmt != null && sqlite3_step( pStmt ) == SQLITE_ROW ) { string zIdx = (string)sqlite3_column_text( pStmt, 1 ); sqlite3_stmt pStmt2 = null; zSql = sqlite3_mprintf( "PRAGMA index_info(%s)", zIdx ); //if ( null == zSql ) //{ // rc = SQLITE_NOMEM; // goto get_index_array_out; //} rc = sqlite3_prepare( db, zSql, -1, ref pStmt2, 0 ); //sqlite3_free(zSql); if ( pStmt2 != null && sqlite3_step( pStmt2 ) == SQLITE_ROW ) { int cid = sqlite3_column_int( pStmt2, 1 ); Debug.Assert( cid >= 0 && cid < nCol ); aIndex[cid] = 1; } if ( pStmt2 != null ) { rc = sqlite3_finalize( pStmt2 ); } if ( rc != SQLITE_OK ) { goto get_index_array_out; } } get_index_array_out: if ( pStmt != null ) { int rc2 = sqlite3_finalize( pStmt ); if ( rc == SQLITE_OK ) { rc = rc2; } } if ( rc != SQLITE_OK ) { //sqlite3_free(aIndex); aIndex = null; } paIndex = aIndex; return rc; } /* ** Global Tcl variable $echo_module is a list. This routine appends ** the string element zArg to that list in interpreter interp. */ static void appendToEchoModule( Tcl_Interp interp, string zArg ) { int flags = ( TCL.TCL_APPEND_VALUE | TCL.TCL_LIST_ELEMENT | TCL.TCL_GLOBAL_ONLY ); TCL.Tcl_SetVar( interp, "echo_module", ( zArg != null ? zArg : "" ), flags ); } /* ** This function is called from within the echo-modules xCreate and ** xConnect methods. The argc and argv arguments are copies of those ** passed to the calling method. This function is responsible for ** calling sqlite3_declare_vtab() to declare the schema of the virtual ** table being created or connected. ** ** If the constructor was passed just one argument, i.e.: ** ** CREATE TABLE t1 AS echo(t2); ** ** Then t2 is assumed to be the name of a *real* database table. The ** schema of the virtual table is declared by passing a copy of the ** CREATE TABLE statement for the real table to sqlite3_declare_vtab(). ** Hence, the virtual table should have exactly the same column names and ** types as the real table. */ static int echoDeclareVtab( echo_vtab pVtab, sqlite3 db ) { int rc = SQLITE_OK; if ( pVtab.zTableName != null ) { sqlite3_stmt pStmt = null; rc = sqlite3_prepare( db, "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?", -1, ref pStmt, 0 ); if ( rc == SQLITE_OK ) { sqlite3_bind_text( pStmt, 1, pVtab.zTableName, -1, null ); if ( sqlite3_step( pStmt ) == SQLITE_ROW ) { int rc2; string zCreateTable = (string)sqlite3_column_text( pStmt, 0 ); rc = sqlite3_declare_vtab( db, zCreateTable ); rc2 = sqlite3_finalize( pStmt ); if ( rc == SQLITE_OK ) { rc = rc2; } } else { rc = sqlite3_finalize( pStmt ); if ( rc == SQLITE_OK ) { rc = SQLITE_ERROR; } } if ( rc == SQLITE_OK ) { rc = getColumnNames( db, pVtab.zTableName, out pVtab.aCol, out pVtab.nCol ); } if ( rc == SQLITE_OK ) { rc = getIndexArray( db, pVtab.zTableName, pVtab.nCol, ref pVtab.aIndex ); } } } return rc; } /* ** This function frees all runtime structures associated with the virtual ** table pVtab. */ static int echoDestructor( ref object pVtab ) { //echo_vtab p = (echo_vtab)pVtab; //sqlite3_free(p.aIndex); //sqlite3_free(p.aCol); //sqlite3_free(p.zThis); //sqlite3_free(p.zTableName); //sqlite3_free(p.zLogName); //sqlite3_free(p); pVtab = null; return 0; } //typedef struct EchoModule EchoModule; class EchoModule : sqlite3_vtab { public Tcl_Interp interp; }; /* ** This function is called to do the work of the xConnect() method - ** to allocate the required in-memory structures for a newly connected ** virtual table. */ static int echoConstructor( sqlite3 db, object pAux, int argc, string[] argv, out sqlite3_vtab ppVtab, out string pzErr ) { int rc; int i; echo_vtab pVtab; pzErr = ""; /* Allocate the sqlite3_vtab/echo_vtab structure itself */ pVtab = new echo_vtab();//sqlite3MallocZero( sizeof(*pVtab) ); //if( null==pVtab ){ // return SQLITE_NOMEM; //} pVtab.interp = ( (EchoModule)pAux ).interp; pVtab.db = db; /* Allocate echo_vtab.zThis */ pVtab.zThis = sqlite3_mprintf( "%s", argv[2] ); //if( null==pVtab.zThis ){ // object obj = ppVtab; // echoDestructor( ref obj ); // obj = null; // return SQLITE_NOMEM; //} /* Allocate echo_vtab.zTableName */ if ( argc > 3 ) { pVtab.zTableName = sqlite3_mprintf( "%s", argv[3] ); dequoteString( ref pVtab.zTableName ); if ( !String.IsNullOrEmpty( pVtab.zTableName ) && pVtab.zTableName[0] == '*' ) { string z = sqlite3_mprintf( "%s%s", argv[2], ( pVtab.zTableName.Substring(1) ) ); //sqlite3_free(pVtab.zTableName); pVtab.zTableName = z; pVtab.isPattern = 1; } //if( null==pVtab.zTableName ){ // object obj = ppVtab; // echoDestructor( ref obj ); // obj = null; // return SQLITE_NOMEM; //} } /* Log the arguments to this function to Tcl var ::echo_module */ for ( i = 0; i < argc; i++ ) { appendToEchoModule( pVtab.interp, argv[i] ); } /* Invoke sqlite3_declare_vtab and set up other members of the echo_vtab ** structure. If an error occurs, delete the sqlite3_vtab structure and ** return an error code. */ rc = echoDeclareVtab( pVtab, db ); if ( rc != SQLITE_OK ) { //object obj = ppVtab; //echoDestructor( ref obj ); //obj = null; ppVtab = null; return rc; } /* Success. Set ppVtab and return */ ppVtab = pVtab;//.base; return SQLITE_OK; } /* ** Echo virtual table module xCreate method. */ static int echoCreate( sqlite3 db, object pAux, int argc, string[] argv, out sqlite3_vtab ppVtab, out string pzErr ) { int rc = SQLITE_OK; appendToEchoModule( ( (EchoModule)pAux ).interp, "xCreate" ); rc = echoConstructor( db, pAux, argc, argv, out ppVtab, out pzErr ); /* If there were two arguments passed to the module at the SQL level ** (i.e. "CREATE VIRTUAL TABLE tbl USING echo(arg1, arg2)"), then ** the second argument is used as a table name. Attempt to create ** such a table with a single column, "logmsg". This table will ** be used to log calls to the xUpdate method. It will be deleted ** when the virtual table is DROPed. ** ** Note: The main point of this is to test that we can drop tables ** from within an xDestroy method call. */ if ( rc == SQLITE_OK && argc == 5 ) { string zSql; echo_vtab pVtab = (echo_vtab)ppVtab; pVtab.zLogName = sqlite3_mprintf( "%s", argv[4] ); zSql = sqlite3_mprintf( "CREATE TABLE %Q(logmsg)", pVtab.zLogName ); rc = sqlite3_exec( db, zSql, 0, 0, 0 ); //sqlite3_free(zSql); if ( rc != SQLITE_OK ) { pzErr = sqlite3_mprintf( "%s", sqlite3_errmsg( db ) ); } } if ( ppVtab != null && rc != SQLITE_OK ) { //object obj = ppVtab; //echoDestructor( ref obj ); //obj = null; ppVtab = null; } if ( rc == SQLITE_OK ) { ( (echo_vtab)ppVtab ).inTransaction = 1; } return rc; } /* ** Echo virtual table module xConnect method. */ static int echoConnect( sqlite3 db, object pAux, int argc, string[] argv, out sqlite3_vtab ppVtab, out string pzErr ) { appendToEchoModule( ( (EchoModule)pAux ).interp, "xConnect" ); return echoConstructor( db, pAux, argc, argv, out ppVtab, out pzErr ); } /* ** Echo virtual table module xDisconnect method. */ static int echoDisconnect( ref object pVtab ) { appendToEchoModule( ( (echo_vtab)pVtab ).interp, "xDisconnect" ); return echoDestructor( ref pVtab ); } /* ** Echo virtual table module xDestroy method. */ static int echoDestroy( ref object pVtab ) { int rc = SQLITE_OK; echo_vtab p = (echo_vtab)pVtab; appendToEchoModule( ( (echo_vtab)pVtab ).interp, "xDestroy" ); /* Drop the "log" table, if one exists (see echoCreate() for details) */ if ( p != null && !String.IsNullOrEmpty( p.zLogName ) ) { string zSql; zSql = sqlite3_mprintf( "DROP TABLE %Q", p.zLogName ); rc = sqlite3_exec( p.db, zSql, 0, 0, 0 ); //sqlite3_free(zSql); } if ( rc == SQLITE_OK ) { rc = echoDestructor( ref pVtab ); } return rc; } /* ** Echo virtual table module xOpen method. */ static int echoOpen( sqlite3_vtab pVTab, out sqlite3_vtab_cursor ppCursor ) { echo_cursor pCur; if ( simulateVtabError( (echo_vtab)pVTab, "xOpen" ) != 0 ) { ppCursor = null; return SQLITE_ERROR; } pCur = new echo_cursor();//sqlite3MallocZero( sizeof( echo_cursor ) ); ppCursor = (sqlite3_vtab_cursor)pCur; return ( pCur != null ? SQLITE_OK : SQLITE_NOMEM ); } /* ** Echo virtual table module xClose method. */ static int echoClose( ref sqlite3_vtab_cursor cur ) { int rc = 0; echo_cursor pCur = (echo_cursor)cur; sqlite3_stmt pStmt = pCur.pStmt; pCur.pStmt = null; //sqlite3_free(pCur); pCur = null; rc = sqlite3_finalize( pStmt ); return rc; } /* ** 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 echoEof( sqlite3_vtab_cursor cur ) { return ( ( (echo_cursor)cur ).pStmt != null ? 0 : 1 ); } /* ** Echo virtual table module xNext method. */ static int echoNext( sqlite3_vtab_cursor cur ) { int rc = SQLITE_OK; echo_cursor pCur = (echo_cursor)cur; if ( simulateVtabError( (echo_vtab)( cur.pVtab ), "xNext" ) != 0 ) { return SQLITE_ERROR; } if ( pCur.pStmt != null ) { rc = sqlite3_step( pCur.pStmt ); if ( rc == SQLITE_ROW ) { rc = SQLITE_OK; } else { rc = sqlite3_finalize( pCur.pStmt ); pCur.pStmt = null; } } return rc; } /* ** Echo virtual table module xColumn method. */ static int echoColumn( sqlite3_vtab_cursor cur, sqlite3_context ctx, int i ) { int iCol = i + 1; sqlite3_stmt pStmt = ( (echo_cursor)cur ).pStmt; if ( simulateVtabError( (echo_vtab)( cur.pVtab ), "xColumn" ) != 0 ) { return SQLITE_ERROR; } if ( null == pStmt ) { sqlite3_result_null( ctx ); } else { Debug.Assert( sqlite3_data_count( pStmt ) > iCol ); sqlite3_result_value( ctx, sqlite3_column_value( pStmt, iCol ) ); } return SQLITE_OK; } /* ** Echo virtual table module xRowid method. */ static int echoRowid( sqlite3_vtab_cursor cur, out sqlite_int64 pRowid ) { sqlite3_stmt pStmt = ( (echo_cursor)cur ).pStmt; if ( simulateVtabError( (echo_vtab)( cur.pVtab ), "xRowid" ) != 0 ) { pRowid = 0; return SQLITE_ERROR; } 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 Debug.Assert()s that the supplied idxNum value is ** indeed the hash of the supplied idxStr. */ static int hashString( string zString ) { int val = 0; int ii; for ( ii = 0; ii < zString.Length; ii++ ) { val = ( val << 3 ) + (int)zString[ii]; } return val; } /* ** Echo virtual table module xFilter method. */ static int echoFilter( sqlite3_vtab_cursor pVtabCursor, int idxNum, string 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; if ( simulateVtabError( pVtab, "xFilter" ) != 0 ) { return SQLITE_ERROR; } /* Check that idxNum matches idxStr */ Debug.Assert( idxNum == hashString( idxStr ) ); /* Log arguments to the ::echo_module Tcl variable */ appendToEchoModule( pVtab.interp, "xFilter" ); appendToEchoModule( pVtab.interp, idxStr ); for ( i = 0; i < argc; i++ ) { appendToEchoModule( pVtab.interp, sqlite3_value_text( argv[i] ) ); } sqlite3_finalize( pCur.pStmt ); pCur.pStmt = null; /* Prepare the SQL statement created by echoBestIndex and bind the ** runtime parameters passed to this function to it. */ rc = sqlite3_prepare( db, idxStr, -1, ref pCur.pStmt, 0 ); Debug.Assert( pCur.pStmt != null || rc != SQLITE_OK ); for ( i = 0; rc == SQLITE_OK && i < argc; i++ ) { rc = sqlite3_bind_value( pCur.pStmt, i + 1, argv[i] ); } /* If everything was successful, advance to the first row of the scan */ if ( rc == SQLITE_OK ) { rc = echoNext( pVtabCursor ); } return rc; } /* ** A helper function used by echoUpdate() and echoBestIndex() for ** manipulating strings in concert with the sqlite3_mprintf() function. ** ** Parameter pzStr points to a pointer to a string allocated with ** sqlite3_mprintf. The second parameter, zAppend, points to another ** string. The two strings are concatenated together and pzStr ** set to point at the result. The initial buffer pointed to by pzStr ** is deallocated via sqlite3_free(). ** ** If the third argument, doFree, is true, then sqlite3_free() is ** also called to free the buffer pointed to by zAppend. */ static void string_concat( ref string pzStr, string zAppend, int doFree, ref int pRc ) { string zIn = pzStr; if ( null == zAppend && doFree != 0 && pRc == SQLITE_OK ) { pRc = SQLITE_NOMEM; } if ( pRc != SQLITE_OK ) { //sqlite3_free(zIn); zIn = null; } else { if ( zIn != null ) { string zTemp = zIn; zIn = sqlite3_mprintf( "%s%s", zIn, zAppend ); //sqlite3_free(zTemp); } else { zIn = sqlite3_mprintf( "%s", zAppend ); } //if ( null == zIn ) //{ // pRc = SQLITE_NOMEM; //} } pzStr = zIn; //if( doFree ){ // sqlite3_free(zAppend); //} } /* ** 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 its left most column is considered. ** ** This xBestIndex method encodes the proposed search strategy as ** an SQL query on the real table underlying the virtual echo module ** table and stores the query in sqlite3_index_info.idxStr. The SQL ** statement is of the form: ** ** SELECT rowid, * FROM ?? ?? ** ** where the and are determined ** by the contents of the structure pointed to by the pIdxInfo argument. */ static int echoBestIndex( sqlite3_vtab vtab, ref sqlite3_index_info pIdxInfo ) { int ii; string zQuery = ""; string zNew; int nArg = 0; string zSep = "WHERE"; echo_vtab pVtab = (echo_vtab)vtab; sqlite3_stmt pStmt = null; Tcl_Interp interp = pVtab.interp; int nRow = 0; int useIdx = 0; int rc = SQLITE_OK; int useCost = 0; double cost = 0; int isIgnoreUsable = 0; if ( TCL.Tcl_GetVar( interp, "echo_module_ignore_usable", (TCL.VarFlag)TCL.TCL_GLOBAL_ONLY ).ToString() != "" ) { isIgnoreUsable = 1; } if ( simulateVtabError( pVtab, "xBestIndex" ) != 0 ) { return SQLITE_ERROR; } /* Determine the number of rows in the table and store this value in local ** variable nRow. The 'estimated-cost' of the scan will be the number of ** rows in the table for a linear scan, or the log (base 2) of the ** number of rows if the proposed scan uses an index. */ if ( TCL.Tcl_GetVar( interp, "echo_module_cost", (TCL.VarFlag)TCL.TCL_GLOBAL_ONLY ).ToString() != "" ) { Double.TryParse( TCL.Tcl_GetVar( interp, "echo_module_cost", (TCL.VarFlag)TCL.TCL_GLOBAL_ONLY ).ToString(), out cost ); useCost = 1; } else { zQuery = sqlite3_mprintf( "SELECT count() FROM %Q", pVtab.zTableName ); //if ( null == zQuery ) //{ // return SQLITE_NOMEM; //} rc = sqlite3_prepare( pVtab.db, zQuery, -1, ref pStmt, 0 ); //sqlite3_free(zQuery); if ( rc != SQLITE_OK ) { return rc; } sqlite3_step( pStmt ); nRow = sqlite3_column_int( pStmt, 0 ); rc = sqlite3_finalize( pStmt ); if ( rc != SQLITE_OK ) { return rc; } } zQuery = sqlite3_mprintf( "SELECT rowid, * FROM %Q", pVtab.zTableName ); //if ( null == zQuery ) //{ // return SQLITE_NOMEM; //} for ( ii = 0; ii < pIdxInfo.nConstraint; ii++ ) { sqlite3_index_constraint pConstraint; sqlite3_index_constraint_usage pUsage; int iCol; pConstraint = pIdxInfo.aConstraint[ii]; pUsage = pIdxInfo.aConstraintUsage[ii]; if ( 0 == isIgnoreUsable && !pConstraint.usable ) continue; iCol = pConstraint.iColumn; if ( iCol < 0 || pVtab.aIndex[iCol] != 0 ) { string zCol; string zOp = ""; useIdx = 1; if ( iCol >= 0 ) { zCol = pVtab.aCol[iCol]; } else { 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 LIKE (SELECT '%%'||?||'%%')", zSep, zCol ); } else { zNew = sqlite3_mprintf( " %s %s %s ?", zSep, zCol, zOp ); } string_concat( ref zQuery, zNew, 1, ref rc ); zSep = "AND"; pUsage.argvIndex = ++nArg; pUsage.omit = true; } } /* 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 && ( pIdxInfo.aOrderBy[0].iColumn < 0 || pVtab.aIndex[pIdxInfo.aOrderBy[0].iColumn] != 0 ) ) { int iCol = pIdxInfo.aOrderBy[0].iColumn; string zCol; string zDir = pIdxInfo.aOrderBy[0].desc ? "DESC" : "ASC"; if ( iCol >= 0 ) { zCol = pVtab.aCol[iCol]; } else { zCol = "rowid"; } zNew = sqlite3_mprintf( " ORDER BY %s %s", zCol, zDir ); string_concat( ref zQuery, zNew, 1, ref rc ); pIdxInfo.orderByConsumed = true; } appendToEchoModule( pVtab.interp, "xBestIndex" ); ; appendToEchoModule( pVtab.interp, zQuery ); if ( null == zQuery ) { return rc; } pIdxInfo.idxNum = hashString( zQuery ); pIdxInfo.idxStr = zQuery; pIdxInfo.needToFreeIdxStr = 1; if ( useCost != 0 ) { pIdxInfo.estimatedCost = cost; } else if ( useIdx != 0 ) { /* Approximation of log2(nRow). */ for ( ii = 0; ii < ( sizeof( int ) * 8 ); ii++ ) { if ( ( nRow & ( 1 << ii ) ) != 0 ) { pIdxInfo.estimatedCost = (double)ii; } } } else { pIdxInfo.estimatedCost = (double)nRow; } return rc; } /* ** The xUpdate method for echo module virtual tables. ** ** 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) ** */ static int echoUpdate( sqlite3_vtab vtab, int nData, sqlite3_value[] apData, out sqlite_int64 pRowid ) { echo_vtab pVtab = (echo_vtab)vtab; sqlite3 db = pVtab.db; int rc = SQLITE_OK; pRowid = 0; sqlite3_stmt pStmt = null; string z = ""; /* 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 */ Debug.Assert( nData == pVtab.nCol + 2 || nData == 1 ); /* Ticket #3083 - make sure we always start a transaction prior to ** making any changes to a virtual table */ Debug.Assert( pVtab.inTransaction != 0 ); if ( simulateVtabError( pVtab, "xUpdate" ) != 0 ) { return SQLITE_ERROR; } /* If apData[0] is an integer and nData>1 then do an UPDATE */ if ( nData > 1 && sqlite3_value_type( apData[0] ) == SQLITE_INTEGER ) { string zSep = " SET"; z = sqlite3_mprintf( "UPDATE %Q", pVtab.zTableName ); //if ( null == z ) //{ // rc = SQLITE_NOMEM; //} bindArgOne = ( apData[1] != null && sqlite3_value_type( apData[1] ) == SQLITE_INTEGER )?1:0; bindArgZero = 1; if ( bindArgOne != 0 ) { string_concat( ref z, " SET rowid=?1 ", 0, ref rc ); zSep = ","; } for ( i = 2; i < nData; i++ ) { if ( apData[i] == null ) continue; string_concat( ref z, sqlite3_mprintf( "%s %Q=?%d", zSep, pVtab.aCol[i - 2], i ), 1, ref rc ); zSep = ","; } string_concat( ref z, sqlite3_mprintf( " WHERE rowid=?%d", nData ), 1, ref rc ); } /* 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 ); //if ( null == z ) //{ // rc = SQLITE_NOMEM; //} 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; string zInsert = ""; string zValues = ""; zInsert = sqlite3_mprintf( "INSERT INTO %Q (", pVtab.zTableName ); //if ( null == zInsert ) //{ // rc = SQLITE_NOMEM; //} if ( sqlite3_value_type( apData[1] ) == SQLITE_INTEGER ) { bindArgOne = 1; zValues = sqlite3_mprintf( "?" ); string_concat( ref zInsert, "rowid", 0, ref rc ); } Debug.Assert( ( pVtab.nCol + 2 ) == nData ); for ( ii = 2; ii < nData; ii++ ) { string_concat( ref zInsert, sqlite3_mprintf( "%s%Q", !String.IsNullOrEmpty(zValues) ? ", " : "", pVtab.aCol[ii - 2] ), 1, ref rc ); string_concat( ref zValues, sqlite3_mprintf( "%s?%d", !String.IsNullOrEmpty( zValues ) ? ", " : "", ii ), 1, ref rc ); } string_concat( ref z, zInsert, 1, ref rc ); string_concat( ref z, ") VALUES(", 0, ref rc ); string_concat( ref z, zValues, 1, ref rc ); string_concat( ref z, ")", 0, ref rc ); } /* Anything else is an error */ else { Debug.Assert( false ); return SQLITE_ERROR; } if ( rc == SQLITE_OK ) { rc = sqlite3_prepare( db, z, -1, ref pStmt, 0 ); } Debug.Assert( rc != SQLITE_OK || pStmt != null ); //sqlite3_free(z); if ( rc == SQLITE_OK ) { if ( bindArgZero != 0 ) { sqlite3_bind_value( pStmt, nData, apData[0] ); } if ( bindArgOne != 0 ) { sqlite3_bind_value( pStmt, 1, apData[1] ); } for ( i = 2; i < nData && rc == SQLITE_OK; i++ ) { if ( apData[i] != null ) rc = sqlite3_bind_value( pStmt, i, apData[i] ); } if ( rc == SQLITE_OK ) { sqlite3_step( pStmt ); rc = sqlite3_finalize( pStmt ); } else { sqlite3_finalize( pStmt ); } } if (/* pRowid != 0 && */ rc == SQLITE_OK ) { pRowid = sqlite3_last_insert_rowid( db ); } if ( rc != SQLITE_OK ) { vtab.zErrMsg = sqlite3_mprintf( "echo-vtab-error: %s", sqlite3_errmsg( db ) ); } return rc; } /* ** xBegin, xSync, xCommit and xRollback callbacks for echo module ** virtual tables. Do nothing other than add the name of the callback ** to the $::echo_module Tcl variable. */ static int echoTransactionCall( sqlite3_vtab vtab, string zCall ) { string z; echo_vtab pVtab = (echo_vtab)vtab; z = sqlite3_mprintf( "echo(%s)", pVtab.zTableName ); //if( z==null ) return SQLITE_NOMEM; appendToEchoModule( pVtab.interp, zCall ); appendToEchoModule( pVtab.interp, z ); //sqlite3_free(z); return SQLITE_OK; } static int echoBegin( sqlite3_vtab vtab ) { int rc; echo_vtab pVtab = (echo_vtab)vtab; Tcl_Interp interp = pVtab.interp; string zVal; /* Ticket #3083 - do not start a transaction if we are already in ** a transaction */ Debug.Assert( 0 == pVtab.inTransaction ); if ( simulateVtabError( pVtab, "xBegin" ) != 0 ) { return SQLITE_ERROR; } rc = echoTransactionCall( vtab, "xBegin" ); if ( rc == SQLITE_OK ) { /* Check if the $::echo_module_begin_fail variable is defined. If it is, ** and it is set to the name of the real table underlying this virtual ** echo module table, then cause this xSync operation to fail. */ zVal = TCL.Tcl_GetVar( interp, "echo_module_begin_fail", TCL.VarFlag.GLOBAL_ONLY ).ToString(); if ( zVal != null && 0 == zVal.CompareTo( pVtab.zTableName ) ) { rc = SQLITE_ERROR; } } if ( rc == SQLITE_OK ) { pVtab.inTransaction = 1; } return rc; } static int echoSync( sqlite3_vtab vtab ) { int rc; echo_vtab pVtab = (echo_vtab)vtab; Tcl_Interp interp = pVtab.interp; string zVal; /* Ticket #3083 - Only call xSync if we have previously started a ** transaction */ Debug.Assert( pVtab.inTransaction != 0 ); if ( simulateVtabError( pVtab, "xSync" ) != 0 ) { return SQLITE_ERROR; } rc = echoTransactionCall( vtab, "xSync" ); if ( rc == SQLITE_OK ) { /* Check if the $::echo_module_sync_fail variable is defined. If it is, ** and it is set to the name of the real table underlying this virtual ** echo module table, then cause this xSync operation to fail. */ zVal = TCL.Tcl_GetVar( interp, "echo_module_sync_fail", TCL.VarFlag.GLOBAL_ONLY ).ToString(); if ( zVal != "" && 0 == zVal.CompareTo( pVtab.zTableName ) ) { rc = -1; } } return rc; } static int echoCommit( sqlite3_vtab vtab ) { echo_vtab pVtab = (echo_vtab)vtab; int rc; /* Ticket #3083 - Only call xCommit if we have previously started ** a transaction */ Debug.Assert( pVtab.inTransaction != 0 ); if ( simulateVtabError( pVtab, "xCommit" ) != 0 ) { return SQLITE_ERROR; } sqlite3BeginBenignMalloc(); rc = echoTransactionCall( vtab, "xCommit" ); sqlite3EndBenignMalloc(); pVtab.inTransaction = 0; return rc; } static int echoRollback( sqlite3_vtab vtab ) { int rc; echo_vtab pVtab = (echo_vtab)vtab; /* Ticket #3083 - Only call xRollback if we have previously started ** a transaction */ Debug.Assert( pVtab.inTransaction != 0 ); rc = echoTransactionCall( vtab, "xRollback" ); pVtab.inTransaction = 0; return rc; } /* ** Implementation of "GLOB" function on the echo module. Pass ** all arguments to the ::echo_glob_overload procedure of TCL ** and return the result of that procedure as a string. */ static void overloadedGlobFunction( sqlite3_context pContext, int nArg, sqlite3_value[] apArg ) { Tcl_Interp interp = (Interp)sqlite3_user_data( pContext ); TclObject str = null; int i; int rc; TCL.Tcl_DStringInit( out str ); TCL.Tcl_DStringAppendElement( str, "::echo_glob_overload" ); for ( i = 0; i < nArg; i++ ) { TCL.Tcl_DStringAppendElement( str, " " + sqlite3_value_text( apArg[i] ) ); } rc = TCL.Tcl_EvalObjEx( interp, str, 0 );// rc = TCL.Tcl_Eval( interp, TCL.Tcl_DStringValue( ref str ) ); TCL.Tcl_DStringFree( ref str ); if ( rc != 0 ) { sqlite3_result_error( pContext, TCL.Tcl_GetStringResult( interp ), -1 ); } else { sqlite3_result_text( pContext, TCL.Tcl_GetStringResult( interp ), -1, SQLITE_TRANSIENT ); } TCL.Tcl_ResetResult( interp ); } /* ** This is the xFindFunction implementation for the echo module. ** SQLite calls this routine when the first argument of a function ** is a column of an echo virtual table. This routine can optionally ** override the implementation of that function. It will choose to ** do so if the function is named "glob", and a TCL command named ** ::echo_glob_overload exists. */ static int echoFindFunction( sqlite3_vtab vtab, int nArg, string zFuncName, ref dxFunc pxFunc, //void (**pxFunc)(sqlite3_context*,int,sqlite3_value), ref object ppArg ) { echo_vtab pVtab = (echo_vtab)vtab; Tcl_Interp interp = pVtab.interp; Tcl_CmdInfo info; if ( !zFuncName.StartsWith( "glob", StringComparison.InvariantCultureIgnoreCase ) ) { return 0; } TCL.Tcl_GetCommandInfo( interp, "::echo_glob_overload", out info ); if ( info ==null) { return 0; } pxFunc = overloadedGlobFunction; ppArg = interp; return 1; } static int echoRename( sqlite3_vtab vtab, string zNewName ) { int rc = SQLITE_OK; echo_vtab p = (echo_vtab)vtab; if ( simulateVtabError( p, "xRename" ) != 0 ) { return SQLITE_ERROR; } if ( p.isPattern != 0 ) { int nThis = p.zThis.Length; string zSql = sqlite3_mprintf( "ALTER TABLE %s RENAME TO %s%s", p.zTableName, zNewName, p.zTableName.Substring(nThis) ); rc = sqlite3_exec( p.db, zSql, 0, 0, 0 ); //sqlite3_free( zSql ); } return rc; } /* ** A virtual table module that merely "echos" the contents of another ** table (like an SQL VIEW). */ static sqlite3_module echoModule = new sqlite3_module( 0, /* iVersion */ echoCreate, echoConnect, echoBestIndex, echoDisconnect, echoDestroy, echoOpen, /* xOpen - open a cursor */ echoClose, /* xClose - close a cursor */ echoFilter, /* xFilter - configure scan constraints */ echoNext, /* xNext - advance a cursor */ echoEof, /* xEof */ echoColumn, /* xColumn - read data */ echoRowid, /* xRowid - read data */ echoUpdate, /* xUpdate - write data */ echoBegin, /* xBegin - begin transaction */ echoSync, /* xSync - sync transaction */ echoCommit, /* xCommit - commit transaction */ echoRollback, /* xRollback - rollback transaction */ echoFindFunction, /* xFindFunction - function overloading */ echoRename /* xRename - rename the table */ ); /* ** Decode a pointer to an sqlite3 object. */ //extern int getDbPointer(Tcl_Interp interp, string zA, sqlite3 ppDb); static int moduleDestroy( ref object p ) { p = null;// sqlite3_free( p ); return SQLITE_OK; } /* ** Register the echo virtual table module. */ static int register_echo_module( ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ Tcl_Interp interp, /* The TCL interpreter that invoked this command */ int objc, /* Number of arguments */ Tcl_Obj[] objv /* Command arguments */ ) { sqlite3 db = null; ; ; EchoModule pMod; if ( objc != 2 ) { TCL.Tcl_WrongNumArgs( interp, 1, objv, "DB" ); return TCL.TCL_ERROR; } if ( getDbPointer( interp, TCL.Tcl_GetString( objv[1] ), out db ) != 0 ) return TCL.TCL_ERROR; pMod = new EchoModule();//sqlite3_malloc(sizeof(EchoModule)); pMod.interp = interp; sqlite3_create_module_v2( db, "echo", echoModule, pMod, moduleDestroy ); return TCL.TCL_OK; } /* ** Tcl interface to sqlite3_declare_vtab, invoked as follows from Tcl: ** ** sqlite3_declare_vtab DB SQL */ static int declare_vtab( ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ Tcl_Interp interp, /* The TCL interpreter that invoked this command */ int objc, /* Number of arguments */ Tcl_Obj[] objv /* Command arguments */ ) { sqlite3 db = null; int rc; if ( objc != 3 ) { TCL.Tcl_WrongNumArgs( interp, 1, objv, "DB SQL" ); return TCL.TCL_ERROR; } if ( getDbPointer( interp, TCL.Tcl_GetString( objv[1] ), out db ) != 0 ) return TCL.TCL_ERROR; rc = sqlite3_declare_vtab( db, TCL.Tcl_GetString( objv[2] ) ); if ( rc != SQLITE_OK ) { TCL.Tcl_SetResult( interp, sqlite3_errmsg( db ), TCL.TCL_VOLATILE ); return TCL.TCL_ERROR; } return TCL.TCL_OK; } #endif //* ifndef SQLITE_OMIT_VIRTUALTABLE */ /* ** Register commands with the TCL interpreter. */ static public int Sqlitetest8_Init( Tcl_Interp interp ) { #if !SQLITE_OMIT_VIRTUALTABLE //static struct { // string zName; // Tcl_ObjCmdProc *xProc; // void *clientData; //} _aObjCmd[] aObjCmd = new _aObjCmd[]{ new _aObjCmd( "register_echo_module", register_echo_module, 0 ), new _aObjCmd( "sqlite3_declare_vtab", declare_vtab, 0 ), }; int i; for ( i = 0; i < aObjCmd.Length; i++ )//sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++) { TCL.Tcl_CreateObjCommand( interp, aObjCmd[i].zName, aObjCmd[i].xProc, aObjCmd[i].clientData, null ); } #endif return TCL.TCL_OK; } } #endif }