wasCSharpSQLite – Rev 1
?pathlinks?
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
}