wasCSharpSQLite – Rev 1

Subversion Repositories:
Rev:
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 <stdlib.h>
    //#include <string.h>

#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 <tbl>". The column names
      ** of the result set of the compiled SELECT will be the same as
      ** the column names of table <tbl>.
      */
      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 <real-table> ?<where-clause>? ?<order-by-clause>?
    **
    ** where the <where-clause> and <order-by-clause> 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 = <arg1>)
    **
    **    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
}