/*
** 2017-08-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.
**
*************************************************************************
**
** This file implements a virtual table that prints diagnostic information
** on stdout when its key interfaces are called.  This is intended for
** interactive analysis and debugging of virtual table interfaces.
**
** To build this extension as a separately loaded shared library or
** DLL, use compiler command-lines similar to the following:
**
**   (linux)    gcc -fPIC -shared vtablog.c -o vtablog.so
**   (mac)      clang -fPIC -dynamiclib vtablog.c -o vtablog.dylib
**   (windows)  cl vtablog.c -link -dll -out:vtablog.dll
**
** Usage example:
**
**     .load ./vtablog
**     CREATE VIRTUAL TABLE temp.log USING vtablog(
**        schema='CREATE TABLE x(a,b,c)',
**        rows=25
**     );
**     SELECT * FROM log;
*/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>


/* vtablog_vtab is a subclass of sqlite3_vtab which will
** serve as the underlying representation of a vtablog virtual table
*/
typedef struct vtablog_vtab vtablog_vtab;
struct vtablog_vtab {
  sqlite3_vtab base;  /* Base class - must be first */
  char *zDb;          /* Schema name.  argv[1] of xConnect/xCreate */
  char *zName;        /* Table name.  argv[2] of xConnect/xCreate */
  int nRow;           /* Number of rows in the table */
  int nCursor;        /* Number of cursors created */
};

/* vtablog_cursor is a subclass of sqlite3_vtab_cursor which will
** serve as the underlying representation of a cursor that scans
** over rows of the result
*/
typedef struct vtablog_cursor vtablog_cursor;
struct vtablog_cursor {
  sqlite3_vtab_cursor base;  /* Base class - must be first */
  int iCursor;               /* Cursor number */
  sqlite3_int64 iRowid;      /* The rowid */
};

/* Skip leading whitespace.  Return a pointer to the first non-whitespace
** character, or to the zero terminator if the string has only whitespace */
static const char *vtablog_skip_whitespace(const char *z){
  while( isspace((unsigned char)z[0]) ) z++;
  return z;
}

/* Remove trailing whitespace from the end of string z[] */
static void vtablog_trim_whitespace(char *z){
  size_t n = strlen(z);
  while( n>0 && isspace((unsigned char)z[n]) ) n--;
  z[n] = 0;
}

/* Dequote the string */
static void vtablog_dequote(char *z){
  int j;
  char cQuote = z[0];
  size_t i, n;

  if( cQuote!='\'' && cQuote!='"' ) return;
  n = strlen(z);
  if( n<2 || z[n-1]!=z[0] ) return;
  for(i=1, j=0; i<n-1; i++){
    if( z[i]==cQuote && z[i+1]==cQuote ) i++;
    z[j++] = z[i];
  }
  z[j] = 0;
}

/* Check to see if the string is of the form:  "TAG = VALUE" with optional
** whitespace before and around tokens.  If it is, return a pointer to the
** first character of VALUE.  If it is not, return NULL.
*/
static const char *vtablog_parameter(const char *zTag, int nTag, const char *z){
  z = vtablog_skip_whitespace(z);
  if( strncmp(zTag, z, nTag)!=0 ) return 0;
  z = vtablog_skip_whitespace(z+nTag);
  if( z[0]!='=' ) return 0;
  return vtablog_skip_whitespace(z+1);
}

/* Decode a parameter that requires a dequoted string.
**
** Return non-zero on an error.
*/
static int vtablog_string_parameter(
  char **pzErr,            /* Leave the error message here, if there is one */
  const char *zParam,      /* Parameter we are checking for */
  const char *zArg,        /* Raw text of the virtual table argment */
  char **pzVal             /* Write the dequoted string value here */
){
  const char *zValue;
  zValue = vtablog_parameter(zParam,(int)strlen(zParam),zArg);
  if( zValue==0 ) return 0;
  if( *pzVal ){
    *pzErr = sqlite3_mprintf("more than one '%s' parameter", zParam);
    return 1;
  }
  *pzVal = sqlite3_mprintf("%s", zValue);
  if( *pzVal==0 ){
    *pzErr = sqlite3_mprintf("out of memory");
    return 1;
  }
  vtablog_trim_whitespace(*pzVal);
  vtablog_dequote(*pzVal);
  return 0;
}

#if 0 /* not used - yet */
/* Return 0 if the argument is false and 1 if it is true.  Return -1 if
** we cannot really tell.
*/
static int vtablog_boolean(const char *z){
  if( sqlite3_stricmp("yes",z)==0
   || sqlite3_stricmp("on",z)==0
   || sqlite3_stricmp("true",z)==0
   || (z[0]=='1' && z[1]==0)
  ){
    return 1;
  }
  if( sqlite3_stricmp("no",z)==0
   || sqlite3_stricmp("off",z)==0
   || sqlite3_stricmp("false",z)==0
   || (z[0]=='0' && z[1]==0)
  ){
    return 0;
  }
  return -1;
}
#endif

/*
** The vtablogConnect() method is invoked to create a new
** vtablog_vtab that describes the vtablog virtual table.
**
** Think of this routine as the constructor for vtablog_vtab objects.
**
** All this routine needs to do is:
**
**    (1) Allocate the vtablog_vtab object and initialize all fields.
**
**    (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
**        result set of queries against vtablog will look like.
*/
static int vtablogConnectCreate(
  sqlite3 *db,
  void *pAux,
  int argc, const char *const*argv,
  sqlite3_vtab **ppVtab,
  char **pzErr,
  int isCreate
){
  vtablog_vtab *pNew;
  int i;
  int rc;
  char *zSchema = 0;
  char *zNRow = 0;

  printf("%s.%s.%s():\n", argv[1], argv[2], 
         isCreate ? "xCreate" : "xConnect");
  printf("  argc=%d\n", argc);
  for(i=0; i<argc; i++){
    printf("  argv[%d] = ", i);
    if( argv[i] ){
      printf("[%s]\n", argv[i]);
    }else{
      printf("NULL\n");
    }
  }

  for(i=3; i<argc; i++){
    const char *z = argv[i];
    if( vtablog_string_parameter(pzErr, "schema", z, &zSchema) ){
      rc = SQLITE_ERROR;
      goto vtablog_end_connect;
    }
    if( vtablog_string_parameter(pzErr, "rows", z, &zNRow) ){
      rc = SQLITE_ERROR;
      goto vtablog_end_connect;
    }
  }
  if( zSchema==0 ){
    zSchema = sqlite3_mprintf("%s","CREATE TABLE x(a,b);");
    if( zSchema==0 ){
      rc = SQLITE_NOMEM;
      goto vtablog_end_connect;
    }
  }
  printf("  schema = '%s'\n", zSchema);
  rc = sqlite3_declare_vtab(db, zSchema);
  if( rc==SQLITE_OK ){
    pNew = sqlite3_malloc( sizeof(*pNew) );
    *ppVtab = (sqlite3_vtab*)pNew;
    if( pNew==0 ) return SQLITE_NOMEM;
    memset(pNew, 0, sizeof(*pNew));
    pNew->nRow = 10;
    if( zNRow ) pNew->nRow = atoi(zNRow);
    printf("  nrow = %d\n", pNew->nRow);
    pNew->zDb = sqlite3_mprintf("%s", argv[1]);
    pNew->zName = sqlite3_mprintf("%s", argv[2]);
  }

vtablog_end_connect:
  sqlite3_free(zSchema);
  sqlite3_free(zNRow);
  return rc;
}
static int vtablogCreate(
  sqlite3 *db,
  void *pAux,
  int argc, const char *const*argv,
  sqlite3_vtab **ppVtab,
  char **pzErr
){
  return vtablogConnectCreate(db,pAux,argc,argv,ppVtab,pzErr,1);
}
static int vtablogConnect(
  sqlite3 *db,
  void *pAux,
  int argc, const char *const*argv,
  sqlite3_vtab **ppVtab,
  char **pzErr
){
  return vtablogConnectCreate(db,pAux,argc,argv,ppVtab,pzErr,0);
}


/*
** This method is the destructor for vtablog_vtab objects.
*/
static int vtablogDisconnect(sqlite3_vtab *pVtab){
  vtablog_vtab *pTab = (vtablog_vtab*)pVtab;
  printf("%s.%s.xDisconnect()\n", pTab->zDb, pTab->zName);
  sqlite3_free(pTab->zDb);
  sqlite3_free(pTab->zName);
  sqlite3_free(pVtab);
  return SQLITE_OK;
}

/*
** This method is (also) the destructor for vtablog_vtab objects.
*/
static int vtablogDestroy(sqlite3_vtab *pVtab){
  vtablog_vtab *pTab = (vtablog_vtab*)pVtab;
  printf("%s.%s.xDestroy()\n", pTab->zDb, pTab->zName);
  sqlite3_free(pTab->zDb);
  sqlite3_free(pTab->zName);
  sqlite3_free(pVtab);
  return SQLITE_OK;
}

/*
** Constructor for a new vtablog_cursor object.
*/
static int vtablogOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
  vtablog_vtab *pTab = (vtablog_vtab*)p;
  vtablog_cursor *pCur;
  printf("%s.%s.xOpen(cursor=%d)\n", pTab->zDb, pTab->zName,
         ++pTab->nCursor);
  pCur = sqlite3_malloc( sizeof(*pCur) );
  if( pCur==0 ) return SQLITE_NOMEM;
  memset(pCur, 0, sizeof(*pCur));
  pCur->iCursor = pTab->nCursor;
  *ppCursor = &pCur->base;
  return SQLITE_OK;
}

/*
** Destructor for a vtablog_cursor.
*/
static int vtablogClose(sqlite3_vtab_cursor *cur){
  vtablog_cursor *pCur = (vtablog_cursor*)cur;
  vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
  printf("%s.%s.xClose(cursor=%d)\n", pTab->zDb, pTab->zName, pCur->iCursor);
  sqlite3_free(cur);
  return SQLITE_OK;
}


/*
** Advance a vtablog_cursor to its next row of output.
*/
static int vtablogNext(sqlite3_vtab_cursor *cur){
  vtablog_cursor *pCur = (vtablog_cursor*)cur;
  vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
  printf("%s.%s.xNext(cursor=%d)  rowid %d -> %d\n", 
         pTab->zDb, pTab->zName, pCur->iCursor,
         (int)pCur->iRowid, (int)pCur->iRowid+1);
  pCur->iRowid++;
  return SQLITE_OK;
}

/*
** Return values of columns for the row at which the vtablog_cursor
** is currently pointing.
*/
static int vtablogColumn(
  sqlite3_vtab_cursor *cur,   /* The cursor */
  sqlite3_context *ctx,       /* First argument to sqlite3_result_...() */
  int i                       /* Which column to return */
){
  vtablog_cursor *pCur = (vtablog_cursor*)cur;
  vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
  char zVal[50];

  if( i<26 ){
    sqlite3_snprintf(sizeof(zVal),zVal,"%c%d", 
                     "abcdefghijklmnopqrstuvwyz"[i], pCur->iRowid);
  }else{
    sqlite3_snprintf(sizeof(zVal),zVal,"{%d}%d", i, pCur->iRowid);
  }
  printf("%s.%s.xColumn(cursor=%d, i=%d): [%s]\n",
         pTab->zDb, pTab->zName, pCur->iCursor, i, zVal);
  sqlite3_result_text(ctx, zVal, -1, SQLITE_TRANSIENT);
  return SQLITE_OK;
}

/*
** Return the rowid for the current row.  In this implementation, the
** rowid is the same as the output value.
*/
static int vtablogRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
  vtablog_cursor *pCur = (vtablog_cursor*)cur;
  vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
  printf("%s.%s.xRowid(cursor=%d): %d\n",
         pTab->zDb, pTab->zName, pCur->iCursor, (int)pCur->iRowid);
  *pRowid = pCur->iRowid;
  return SQLITE_OK;
}

/*
** Return TRUE if the cursor has been moved off of the last
** row of output.
*/
static int vtablogEof(sqlite3_vtab_cursor *cur){
  vtablog_cursor *pCur = (vtablog_cursor*)cur;
  vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
  int rc = pCur->iRowid >= pTab->nRow;
  printf("%s.%s.xEof(cursor=%d): %d\n",
         pTab->zDb, pTab->zName, pCur->iCursor, rc);
  return rc;
}

/*
** Output an sqlite3_value object's value as an SQL literal.
*/
static void vtablogQuote(sqlite3_value *p){
  char z[50];
  switch( sqlite3_value_type(p) ){
    case SQLITE_NULL: {
      printf("NULL");
      break;
    }
    case SQLITE_INTEGER: {
      sqlite3_snprintf(50,z,"%lld", sqlite3_value_int64(p));
      printf("%s", z);
      break;
    }
    case SQLITE_FLOAT: {
      sqlite3_snprintf(50,z,"%!.20g", sqlite3_value_double(p));
      printf("%s", z);
      break;
    }
    case SQLITE_BLOB: {
      int n = sqlite3_value_bytes(p);
      const unsigned char *z = (const unsigned char*)sqlite3_value_blob(p);
      int i;
      printf("x'");
      for(i=0; i<n; i++) printf("%02x", z[i]);
      printf("'");
      break;
    }
    case SQLITE_TEXT: {
      const char *z = (const char*)sqlite3_value_text(p);
      int i;
      char c;
      for(i=0; (c = z[i])!=0 && c!='\''; i++){}
      if( c==0 ){
        printf("'%s'",z);
      }else{
        printf("'");
        while( *z ){
          for(i=0; (c = z[i])!=0 && c!='\''; i++){}
          if( c=='\'' ) i++;
          if( i ){
            printf("%.*s", i, z);
            z += i;
          }
          if( c=='\'' ){
            printf("'");
            continue;
          }
          if( c==0 ){
            break;
          }
          z++;
        }
        printf("'");
      }
      break;
    }
  }
}


/*
** This method is called to "rewind" the vtablog_cursor object back
** to the first row of output.  This method is always called at least
** once prior to any call to vtablogColumn() or vtablogRowid() or 
** vtablogEof().
*/
static int vtablogFilter(
  sqlite3_vtab_cursor *cur,
  int idxNum, const char *idxStr,
  int argc, sqlite3_value **argv
){
  vtablog_cursor *pCur = (vtablog_cursor *)cur;
  vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
  printf("%s.%s.xFilter(cursor=%d):\n", pTab->zDb, pTab->zName, pCur->iCursor);
  pCur->iRowid = 0;
  return SQLITE_OK;
}

/*
** Return an sqlite3_index_info operator name in static space.
** The name is possibly overwritten on subsequent calls.
*/
static char *vtablogOpName(unsigned char op){
  static char zUnknown[30];
  char *zOut;
  switch( op ){
    case SQLITE_INDEX_CONSTRAINT_EQ:        zOut = "EQ";        break;
    case SQLITE_INDEX_CONSTRAINT_GT:        zOut = "GT";        break;
    case SQLITE_INDEX_CONSTRAINT_LE:        zOut = "LE";        break;
    case SQLITE_INDEX_CONSTRAINT_LT:        zOut = "LT";        break;
    case SQLITE_INDEX_CONSTRAINT_GE:        zOut = "GE";        break;
    case SQLITE_INDEX_CONSTRAINT_MATCH:     zOut = "MATCH";     break;
    case SQLITE_INDEX_CONSTRAINT_LIKE:      zOut = "LIKE";      break;
    case SQLITE_INDEX_CONSTRAINT_GLOB:      zOut = "GLOB";      break;
    case SQLITE_INDEX_CONSTRAINT_REGEXP:    zOut = "REGEXP";    break;
    case SQLITE_INDEX_CONSTRAINT_NE:        zOut = "NE";        break;
    case SQLITE_INDEX_CONSTRAINT_ISNOT:     zOut = "ISNOT";     break;
    case SQLITE_INDEX_CONSTRAINT_ISNOTNULL: zOut = "ISNOTNULL"; break;
    case SQLITE_INDEX_CONSTRAINT_ISNULL:    zOut = "ISNULL";    break;
    case SQLITE_INDEX_CONSTRAINT_IS:        zOut = "IS";        break;
    case SQLITE_INDEX_CONSTRAINT_LIMIT:     zOut = "LIMIT";     break;
    case SQLITE_INDEX_CONSTRAINT_OFFSET:    zOut = "OFFSET";    break;
    case SQLITE_INDEX_CONSTRAINT_FUNCTION:  zOut = "FUNCTION";  break;
    default:
      sqlite3_snprintf(sizeof(zUnknown),zUnknown,"%d",op);
      zOut = zUnknown;
      break;
  }
  return zOut;
}

/*
** SQLite will invoke this method one or more times while planning a query
** that uses the vtablog virtual table.  This routine needs to create
** a query plan for each invocation and compute an estimated cost for that
** plan.
*/
static int vtablogBestIndex(
  sqlite3_vtab *tab,
  sqlite3_index_info *p
){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  int i;
  printf("%s.%s.xBestIndex():\n", pTab->zDb, pTab->zName);
  printf("  colUsed: 0x%016llx\n", p->colUsed);
  printf("  nConstraint: %d\n", p->nConstraint);
  for(i=0; i<p->nConstraint; i++){
    sqlite3_value *pVal = 0;
    int rc = sqlite3_vtab_rhs_value(p, i, &pVal);
    printf(
      "  constraint[%d]: col=%d termid=%d op=%s usabled=%d coll=%s rhs=",
       i,
       p->aConstraint[i].iColumn,
       p->aConstraint[i].iTermOffset,
       vtablogOpName(p->aConstraint[i].op),
       p->aConstraint[i].usable,
       sqlite3_vtab_collation(p,i)
    );
    if( rc==SQLITE_OK ){
      vtablogQuote(pVal);
      printf("\n");
    }else{
      printf("N/A\n");
    }
  }
  printf("  nOrderBy: %d\n", p->nOrderBy);
  for(i=0; i<p->nOrderBy; i++){
    printf("  orderby[%d]: col=%d desc=%d\n",
       i,
       p->aOrderBy[i].iColumn,
       p->aOrderBy[i].desc);
  }
  p->estimatedCost = (double)500;
  p->estimatedRows = 500;
  printf("  idxNum=%d\n", p->idxNum);
  printf("  idxStr=NULL\n");
  printf("  orderByConsumed=%d\n", p->orderByConsumed);
  printf("  estimatedCost=%g\n", p->estimatedCost);
  printf("  estimatedRows=%lld\n", p->estimatedRows);
  return SQLITE_OK;
}

/*
** SQLite invokes this method to INSERT, UPDATE, or DELETE content from
** the table. 
**
** This implementation does not actually make any changes to the table
** content.  It merely logs the fact that the method was invoked
*/
static int vtablogUpdate(
  sqlite3_vtab *tab,
  int argc,
  sqlite3_value **argv,
  sqlite_int64 *pRowid
){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  int i;
  printf("%s.%s.xUpdate():\n", pTab->zDb, pTab->zName);
  printf("  argc=%d\n", argc);
  for(i=0; i<argc; i++){
    printf("  argv[%d]=", i);
    vtablogQuote(argv[i]);
    printf("\n");
  }
  return SQLITE_OK;
}

static int vtablogBegin(sqlite3_vtab *tab){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  printf("%s.%s.xBegin()\n", pTab->zDb, pTab->zName);
  return SQLITE_OK;
}
static int vtablogSync(sqlite3_vtab *tab){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  printf("%s.%s.xSync()\n", pTab->zDb, pTab->zName);
  return SQLITE_OK;
}
static int vtablogCommit(sqlite3_vtab *tab){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  printf("%s.%s.xCommit()\n", pTab->zDb, pTab->zName);
  return SQLITE_OK;
}
static int vtablogRollback(sqlite3_vtab *tab){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  printf("%s.%s.xRollback()\n", pTab->zDb, pTab->zName);
  return SQLITE_OK;
}
static int vtablogSavepoint(sqlite3_vtab *tab, int N){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  printf("%s.%s.xSavepoint(%d)\n", pTab->zDb, pTab->zName, N);
  return SQLITE_OK;
}
static int vtablogRelease(sqlite3_vtab *tab, int N){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  printf("%s.%s.xRelease(%d)\n", pTab->zDb, pTab->zName, N);
  return SQLITE_OK;
}
static int vtablogRollbackTo(sqlite3_vtab *tab, int N){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  printf("%s.%s.xRollbackTo(%d)\n", pTab->zDb, pTab->zName, N);
  return SQLITE_OK;
}

static int vtablogFindMethod(
  sqlite3_vtab *tab,
  int nArg,
  const char *zName,
  void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
  void **ppArg
){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  printf("%s.%s.xFindMethod(nArg=%d, zName=%s)\n",
         pTab->zDb, pTab->zName, nArg, zName);
  return SQLITE_OK;
}
static int vtablogRename(sqlite3_vtab *tab, const char *zNew){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  printf("%s.%s.xRename('%s')\n", pTab->zDb, pTab->zName, zNew);
  sqlite3_free(pTab->zName);
  pTab->zName = sqlite3_mprintf("%s", zNew);
  return SQLITE_OK;
}

/* Any table name that contains the text "shadow" is seen as a
** shadow table.  Nothing else is.
*/
static int vtablogShadowName(const char *zName){
  printf("vtablog.xShadowName('%s')\n", zName);
  return sqlite3_strglob("*shadow*", zName)==0;
}

static int vtablogIntegrity(
  sqlite3_vtab *tab,
  const char *zSchema,
  const char *zTabName,
  int mFlags,
  char **pzErr
){
  vtablog_vtab *pTab = (vtablog_vtab*)tab;
  printf("%s.%s.xIntegrity(mFlags=0x%x)\n", pTab->zDb, pTab->zName, mFlags);
  return 0;
}

/*
** This following structure defines all the methods for the 
** vtablog virtual table.
*/
static sqlite3_module vtablogModule = {
  4,                         /* iVersion */
  vtablogCreate,             /* xCreate */
  vtablogConnect,            /* xConnect */
  vtablogBestIndex,          /* xBestIndex */
  vtablogDisconnect,         /* xDisconnect */
  vtablogDestroy,            /* xDestroy */
  vtablogOpen,               /* xOpen - open a cursor */
  vtablogClose,              /* xClose - close a cursor */
  vtablogFilter,             /* xFilter - configure scan constraints */
  vtablogNext,               /* xNext - advance a cursor */
  vtablogEof,                /* xEof - check for end of scan */
  vtablogColumn,             /* xColumn - read data */
  vtablogRowid,              /* xRowid - read data */
  vtablogUpdate,             /* xUpdate */
  vtablogBegin,              /* xBegin */
  vtablogSync,               /* xSync */
  vtablogCommit,             /* xCommit */
  vtablogRollback,           /* xRollback */
  vtablogFindMethod,         /* xFindMethod */
  vtablogRename,             /* xRename */
  vtablogSavepoint,          /* xSavepoint */
  vtablogRelease,            /* xRelease */
  vtablogRollbackTo,         /* xRollbackTo */
  vtablogShadowName,         /* xShadowName */
  vtablogIntegrity           /* xIntegrity */
};

#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_vtablog_init(
  sqlite3 *db, 
  char **pzErrMsg, 
  const sqlite3_api_routines *pApi
){
  int rc;
  SQLITE_EXTENSION_INIT2(pApi);
  rc = sqlite3_create_module(db, "vtablog", &vtablogModule, 0);
  return rc;
}
